Added timelapse file management

master
Gina Häußge 2013-01-04 13:11:00 +01:00
parent 2db1cff224
commit ef0820a067
5 changed files with 168 additions and 82 deletions

View File

@ -2,12 +2,12 @@
__author__ = "Gina Häußge <osd@foosel.net>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
from flask import Flask, request, render_template, jsonify
from flask import Flask, request, render_template, jsonify, send_from_directory, abort, url_for
from werkzeug import secure_filename
from printer_webui.printer import Printer, getConnectionOptions
from printer_webui.settings import settings
from printer_webui.timelapse import ZTimelapse, TimedTimelapse
import timelapse
import os
import fnmatch
@ -16,7 +16,6 @@ BASEURL="/ajax/"
SUCCESS={}
UPLOAD_FOLDER = settings().getBaseFolder("uploads")
ALLOWED_EXTENSIONS = set(["gcode"])
app = Flask("printer_webui")
printer = Printer()
@ -205,70 +204,89 @@ def readGcodeFiles():
@app.route(BASEURL + "gcodefiles/upload", methods=["POST"])
def uploadGcodeFile():
file = request.files["gcode_file"]
if file and allowed_file(file.filename):
secure = secure_filename(file.filename)
filename = os.path.join(UPLOAD_FOLDER, secure)
file.save(filename)
if request.files.has_key("gcode_file"):
file = request.files["gcode_file"]
if file and allowed_file(file.filename, set(["gcode"])):
secure = secure_filename(file.filename)
filename = os.path.join(UPLOAD_FOLDER, secure)
file.save(filename)
return readGcodeFiles()
@app.route(BASEURL + "gcodefiles/load", methods=["POST"])
def loadGcodeFile():
filename = request.values["filename"]
printer.loadGcode(os.path.join(UPLOAD_FOLDER, filename))
if request.values.has_key("filename"):
filename = request.values["filename"]
printer.loadGcode(os.path.join(UPLOAD_FOLDER, filename))
return jsonify(SUCCESS)
@app.route(BASEURL + "gcodefiles/delete", methods=["POST"])
def deleteGcodeFile():
if request.values.has_key("filename"):
filename = request.values["filename"]
if allowed_file(filename):
if allowed_file(filename, set(["gcode"])):
secure = os.path.join(UPLOAD_FOLDER, secure_filename(filename))
if os.path.exists(secure):
os.remove(secure)
return readGcodeFiles()
#~~ timelapse configuration
#~~ timelapse handling
@app.route(BASEURL + "timelapse", methods=["GET"])
def getTimelapseConfig():
timelapse = printer.getTimelapse()
def getTimelapseData():
lapse = printer.getTimelapse()
type = "off"
additionalConfig = {}
if timelapse is not None and isinstance(timelapse, ZTimelapse):
if lapse is not None and isinstance(lapse, timelapse.ZTimelapse):
type = "zchange"
elif timelapse is not None and isinstance(timelapse, TimedTimelapse):
elif lapse is not None and isinstance(lapse, timelapse.TimedTimelapse):
type = "timed"
additionalConfig = {
"interval": timelapse.interval
"interval": lapse.interval
}
files = timelapse.getFinishedTimelapses()
for file in files:
file["size"] = sizeof_fmt(file["size"])
file["url"] = url_for("downloadTimelapse", filename=file["name"])
return jsonify({
"type": type,
"config": additionalConfig
"type": type,
"config": additionalConfig,
"files": files
})
@app.route(BASEURL + "timelapse", methods=["POST"])
@app.route(BASEURL + "timelapse/<filename>", methods=["GET"])
def downloadTimelapse(filename):
if allowed_file(filename, set(["mpg"])):
return send_from_directory(settings().getBaseFolder("timelapse"), filename, as_attachment=True)
@app.route(BASEURL + "timelapse/<filename>", methods=["DELETE"])
def deleteTimelapse(filename):
if allowed_file(filename, set(["mpg"])):
secure = os.path.join(settings().getBaseFolder("timelapse"), secure_filename(filename))
if os.path.exists(secure):
os.remove(secure)
return getTimelapseData()
@app.route(BASEURL + "timelapse/config", methods=["POST"])
def setTimelapseConfig():
if not request.values.has_key("type"):
return getTimelapseConfig()
if request.values.has_key("type"):
type = request.values["type"]
lapse = None
if "zchange" == type:
lapse = timelapse.ZTimelapse()
elif "timed" == type:
interval = 10
if request.values.has_key("interval"):
try:
interval = int(request.values["interval"])
except ValueError:
pass
lapse = timelapse.TimedTimelapse(interval)
printer.setTimelapse(lapse)
type = request.values["type"]
timelapse = None
if "zchange" == type:
timelapse = ZTimelapse()
elif "timed" == type:
interval = 10
if request.values.has_key("interval"):
try:
interval = int(request.values["interval"])
except ValueError:
pass
timelapse = TimedTimelapse(interval)
printer.setTimelapse(timelapse)
return getTimelapseConfig()
return getTimelapseData()
#~~ settings
@ -303,8 +321,8 @@ def sizeof_fmt(num):
num /= 1024.0
return "%3.1f%s" % (num, "TB")
def allowed_file(filename):
return "." in filename and filename.rsplit(".", 1)[1] in ALLOWED_EXTENSIONS
def allowed_file(filename, extensions):
return "." in filename and filename.rsplit(".", 1)[1] in extensions
#~~ startup code

View File

@ -20,6 +20,19 @@ body {
margin-bottom: 0px;
}
.tab-content h1 {
display: block;
width: 100%;
padding: 0;
margin-bottom: 20px;
font-size: 21px;
line-height: 40px;
color: #333;
border: 0;
border-bottom: 1px solid #E5E5E5;
font-weight: normal;
}
table {
table-layout: fixed;
}
@ -98,4 +111,20 @@ table th.gcode_files_action, table td.gcode_files_action {
height: 440px;
background-color: #000000;
margin-bottom: 20px;
}
}
table th.timelapse_files_name, table td.timelapse_files_name {
text-overflow: ellipsis;
text-align: left;
width: 55%;
}
table th.timelapse_files_size, table td.timelapse_files_size {
text-align: right;
width: 25%;
}
table th.timelapse_files_action, table td.timelapse_files_action {
text-align: center;
width: 20%;
}

View File

@ -389,6 +389,7 @@ function WebcamViewModel() {
self.timelapseType = ko.observable(undefined);
self.timelapseTimedInterval = ko.observable(undefined);
self.files = ko.observableArray([]);
self.isErrorOrClosed = ko.observable(undefined);
self.isOperational = ko.observable(undefined);
@ -417,6 +418,7 @@ function WebcamViewModel() {
self.fromResponse = function(response) {
self.timelapseType(response.type)
self.files(response.files)
if (response.type == "timed" && response.config && response.config.interval) {
self.timelapseTimedInterval(response.config.interval)
@ -435,6 +437,16 @@ function WebcamViewModel() {
self.isLoading(response.loading);
}
self.removeFile = function() {
var filename = this.name;
$.ajax({
url: AJAX_BASEURL + "timelapse/" + filename,
type: "DELETE",
dataType: "json",
success: self.requestData
})
}
self.save = function() {
var data = {
"type": self.timelapseType()
@ -445,7 +457,7 @@ function WebcamViewModel() {
}
$.ajax({
url: AJAX_BASEURL + "timelapse",
url: AJAX_BASEURL + "timelapse/config",
type: "POST",
dataType: "json",
data: data,

View File

@ -125,36 +125,32 @@
</div>
<div>
<div class="form-horizontal" style="width: 49%; float: left; margin-bottom: 20px;">
<fieldset>
<legend>Temperature</legend>
<h1>Temperature</h1>
<label>Current: <strong data-bind="text: tempString"></strong></label>
<label>Current: <strong data-bind="text: tempString"></strong></label>
<label>Target: <strong data-bind="text: targetTempString"></strong></label>
<label>Target: <strong data-bind="text: targetTempString"></strong></label>
<label for="temp_newTemp">New Target</label>
<div class="input-append">
<input class="span1" type="text" id="temp_newTemp" data-bind="attr: {placeholder: targetTemp}">
<span class="add-on">°C</span>
</div>
<button type="submit" class="btn" id="temp_newTemp_set">Set</button>
</fieldset>
<label for="temp_newTemp">New Target</label>
<div class="input-append">
<input class="span1" type="text" id="temp_newTemp" data-bind="attr: {placeholder: targetTemp}">
<span class="add-on">°C</span>
</div>
<button type="submit" class="btn" id="temp_newTemp_set">Set</button>
</div>
<div class="form-horizontal" style="width: 49%; margin-left: 2%; float: left; margin-bottom: 20px;">
<fieldset>
<legend>Bed Temperature</legend>
<h1>Bed Temperature</h1>
<label>Current: <strong data-bind="text: bedTempString"></strong></label>
<label>Current: <strong data-bind="text: bedTempString"></strong></label>
<label>Target: <strong data-bind="text: bedTargetTempString"></strong></label>
<label>Target: <strong data-bind="text: bedTargetTempString"></strong></label>
<label for="temp_newBedTemp">New Target</label>
<div class="input-append">
<input class="span1" type="text" id="temp_newBedTemp" data-bind="attr: {placeholder: bedTargetTemp}">
<span class="add-on">°C</span>
</div>
<button type="submit" class="btn" id="temp_newBedTemp_set">Set</button>
</fieldset>
<label for="temp_newBedTemp">New Target</label>
<div class="input-append">
<input class="span1" type="text" id="temp_newBedTemp" data-bind="attr: {placeholder: bedTargetTemp}">
<span class="add-on">°C</span>
</div>
<button type="submit" class="btn" id="temp_newBedTemp_set">Set</button>
</div>
</div>
</div>
@ -222,25 +218,42 @@
<img id="webcam_image" src="{{ webcamStream }}">
</div>
<fieldset>
<legend>Timelapse</legend>
<h1>Timelapse Configuration</h1>
<label for="webcam_timelapse_mode">Timelapse Mode</label>
<select id="webcam_timelapse_mode" data-bind="value: timelapseType, enable: isOperational() && !isPrinting()">
<option value="off">Off</option>
<option value="zchange">On Z Change</option>
<option value="timed">Timed</option>
</select>
<label for="webcam_timelapse_mode">Timelapse Mode</label>
<select id="webcam_timelapse_mode" data-bind="value: timelapseType, enable: isOperational() && !isPrinting()">
<option value="off">Off</option>
<option value="zchange">On Z Change</option>
<option value="timed">Timed</option>
</select>
<div id="webcam_timelapse_timedsettings" data-bind="visible: intervalInputEnabled()">
<label for="webcam_timelapse_interval">Interval</label>
<input type="text" id="webcam_timelapse_interval" data-bind="value: timelapseTimedInterval, enable: isOperational() && !isPrinting()">
</div>
<div id="webcam_timelapse_timedsettings" data-bind="visible: intervalInputEnabled()">
<label for="webcam_timelapse_interval">Interval</label>
<input type="text" id="webcam_timelapse_interval" data-bind="value: timelapseTimedInterval, enable: isOperational() && !isPrinting()">
</div>
<div>
<button class="btn" data-bind="click: save, enable: isOperational() && !isPrinting()">Save Settings</button>
</div>
</fieldset>
<div>
<button class="btn" data-bind="click: save, enable: isOperational() && !isPrinting()">Save Settings</button>
</div>
<h1>Finished Timelapses</h1>
<table class="table table-striped table-hover table-condensed table-hover" id="timelapse_files">
<thead>
<tr>
<th class="timelapse_files_name">Name</th>
<th class="timelapse_files_size">Size</th>
<th class="timelapse_files_action">Action</th>
</tr>
</thead>
<tbody data-bind="foreach: files">
<tr data-bind="attr: {title: name}">
<td class="timelapse_files_name" data-bind="text: name"></td>
<td class="timelapse_files_size" data-bind="text: size"></td>
<td class="timelapse_files_action"><a href="#" class="icon-trash" data-bind="click: $parent.removeFile"></a>&nbsp;|&nbsp;<a href="#" class="icon-download" data-bind="attr: {href: url}"></a></td>
</tr>
</tbody>
</table>
</div>
{% endif %}
</div>

View File

@ -9,7 +9,19 @@ import threading
import urllib
import time
import subprocess
import glob
import fnmatch
def getFinishedTimelapses():
files = []
basedir = settings().getBaseFolder("timelapse")
for osFile in os.listdir(basedir):
if not fnmatch.fnmatch(osFile, "*.mpg"):
continue
files.append({
"name": osFile,
"size": os.stat(os.path.join(basedir, osFile)).st_size
})
return files
class Timelapse(object):
def __init__(self):
@ -75,8 +87,10 @@ class Timelapse(object):
if not os.path.isdir(self.captureDir):
return
for filename in glob.glob(os.path.join(self.captureDir, "*.jpg")):
os.remove(filename)
for filename in os.listdir(self.captureDir):
if not fnmatch.fnmatch(filename, "*.jpg"):
continue
os.remove(os.path.join(self.captureDir, filename))
class ZTimelapse(Timelapse):
def __init__(self):