Added timelapse file management
parent
2db1cff224
commit
ef0820a067
|
@ -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,8 +204,9 @@ def readGcodeFiles():
|
|||
|
||||
@app.route(BASEURL + "gcodefiles/upload", methods=["POST"])
|
||||
def uploadGcodeFile():
|
||||
if request.files.has_key("gcode_file"):
|
||||
file = request.files["gcode_file"]
|
||||
if file and allowed_file(file.filename):
|
||||
if file and allowed_file(file.filename, set(["gcode"])):
|
||||
secure = secure_filename(file.filename)
|
||||
filename = os.path.join(UPLOAD_FOLDER, secure)
|
||||
file.save(filename)
|
||||
|
@ -214,6 +214,7 @@ def uploadGcodeFile():
|
|||
|
||||
@app.route(BASEURL + "gcodefiles/load", methods=["POST"])
|
||||
def loadGcodeFile():
|
||||
if request.values.has_key("filename"):
|
||||
filename = request.values["filename"]
|
||||
printer.loadGcode(os.path.join(UPLOAD_FOLDER, filename))
|
||||
return jsonify(SUCCESS)
|
||||
|
@ -222,42 +223,59 @@ def loadGcodeFile():
|
|||
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
|
||||
"config": additionalConfig,
|
||||
"files": files
|
||||
})
|
||||
|
||||
@app.route(BASEURL + "timelapse", methods=["POST"])
|
||||
def setTimelapseConfig():
|
||||
if not request.values.has_key("type"):
|
||||
return getTimelapseConfig()
|
||||
@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 request.values.has_key("type"):
|
||||
type = request.values["type"]
|
||||
timelapse = None
|
||||
lapse = None
|
||||
if "zchange" == type:
|
||||
timelapse = ZTimelapse()
|
||||
lapse = timelapse.ZTimelapse()
|
||||
elif "timed" == type:
|
||||
interval = 10
|
||||
if request.values.has_key("interval"):
|
||||
|
@ -265,10 +283,10 @@ def setTimelapseConfig():
|
|||
interval = int(request.values["interval"])
|
||||
except ValueError:
|
||||
pass
|
||||
timelapse = TimedTimelapse(interval)
|
||||
lapse = timelapse.TimedTimelapse(interval)
|
||||
printer.setTimelapse(lapse)
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
@ -99,3 +112,19 @@ table th.gcode_files_action, table td.gcode_files_action {
|
|||
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%;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -125,8 +125,7 @@
|
|||
</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>
|
||||
|
||||
|
@ -138,11 +137,9 @@
|
|||
<span class="add-on">°C</span>
|
||||
</div>
|
||||
<button type="submit" class="btn" id="temp_newTemp_set">Set</button>
|
||||
</fieldset>
|
||||
</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>
|
||||
|
||||
|
@ -154,7 +151,6 @@
|
|||
<span class="add-on">°C</span>
|
||||
</div>
|
||||
<button type="submit" class="btn" id="temp_newBedTemp_set">Set</button>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -222,8 +218,7 @@
|
|||
<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()">
|
||||
|
@ -240,7 +235,25 @@
|
|||
<div>
|
||||
<button class="btn" data-bind="click: save, enable: isOperational() && !isPrinting()">Save Settings</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<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> | <a href="#" class="icon-download" data-bind="attr: {href: url}"></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue