From ef0820a067bbd1fe0923d6a8c334615166d818f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 4 Jan 2013 13:11:00 +0100 Subject: [PATCH] Added timelapse file management --- printer_webui/server.py | 96 ++++++++++++++++++------------ printer_webui/static/css/ui.css | 31 +++++++++- printer_webui/static/js/ui.js | 14 ++++- printer_webui/templates/index.html | 89 +++++++++++++++------------ printer_webui/timelapse.py | 20 ++++++- 5 files changed, 168 insertions(+), 82 deletions(-) diff --git a/printer_webui/server.py b/printer_webui/server.py index eefbada..a163b77 100644 --- a/printer_webui/server.py +++ b/printer_webui/server.py @@ -2,12 +2,12 @@ __author__ = "Gina Häußge " __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/", 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/", 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 diff --git a/printer_webui/static/css/ui.css b/printer_webui/static/css/ui.css index e52db34..bfa7c2c 100644 --- a/printer_webui/static/css/ui.css +++ b/printer_webui/static/css/ui.css @@ -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; -} \ No newline at end of file +} + +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%; +} diff --git a/printer_webui/static/js/ui.js b/printer_webui/static/js/ui.js index 601e19e..774fd99 100644 --- a/printer_webui/static/js/ui.js +++ b/printer_webui/static/js/ui.js @@ -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, diff --git a/printer_webui/templates/index.html b/printer_webui/templates/index.html index 8493b24..1d75774 100644 --- a/printer_webui/templates/index.html +++ b/printer_webui/templates/index.html @@ -125,36 +125,32 @@
-
- Temperature +

Temperature

- + - + - -
- - °C -
- -
+ +
+ + °C +
+
-
- Bed Temperature +

Bed Temperature

- + - + - -
- - °C -
- -
+ +
+ + °C +
+
@@ -222,25 +218,42 @@ -
- Timelapse +

Timelapse Configuration

- - + + -
- - -
+
+ + +
-
- -
-
+
+ +
+ +

Finished Timelapses

+ + + + + + + + + + + + + + + + +
NameSizeAction
 | 
{% endif %} diff --git a/printer_webui/timelapse.py b/printer_webui/timelapse.py index 5843a76..7092733 100644 --- a/printer_webui/timelapse.py +++ b/printer_webui/timelapse.py @@ -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):