From 4739c71ee075f824ed854d02d6e9cc4dadcefd61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 16 May 2014 11:36:23 +0200 Subject: [PATCH] Now using watchdog to monitor a "watchdog" folder for gcode or stl files being added Use this to define a folder on your system from which to automatically import/move added files into OctoPrint. Also now monitoring the uploads folder so that if files are removed externally during runtime, the metadata.yaml will be cleaned up. --- src/octoprint/server/__init__.py | 13 +++++- src/octoprint/server/util.py | 73 +++++++++++++++++++++++++++++++- src/octoprint/settings.py | 3 +- 3 files changed, 86 insertions(+), 3 deletions(-) diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 407aa303..e2e138b3 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -10,6 +10,7 @@ from sockjs.tornado import SockJSRouter from flask import Flask, render_template, send_from_directory, make_response from flask.ext.login import LoginManager from flask.ext.principal import Principal, Permission, RoleNeed, identity_loaded, UserNeed +from watchdog.observers import Observer import os import logging @@ -33,7 +34,7 @@ user_permission = Permission(RoleNeed("user")) # only import the octoprint stuff down here, as it might depend on things defined above to be initialized already from octoprint.server.util import LargeResponseHandler, ReverseProxied, restricted_access, PrinterStateConnection, admin_validator, \ - UrlForwardHandler, user_validator + UrlForwardHandler, user_validator, GcodeWatchdogHandler, UploadCleanupWatchdogHandler from octoprint.printer import Printer, getConnectionOptions from octoprint.settings import settings import octoprint.gcodefiles as gcodefiles @@ -220,6 +221,13 @@ class Server(): connectionOptions = getConnectionOptions() if port in connectionOptions["ports"]: printer.connect(port, baudrate) + + # start up watchdogs + observer = Observer() + observer.schedule(GcodeWatchdogHandler(gcodeManager, printer), settings().getBaseFolder("watchdog")) + observer.schedule(UploadCleanupWatchdogHandler(gcodeManager), settings().getBaseFolder("uploads")) + observer.start() + try: IOLoop.instance().start() except KeyboardInterrupt: @@ -227,6 +235,9 @@ class Server(): except: logger.fatal("Now that is embarrassing... Something really really went wrong here. Please report this including the stacktrace below in OctoPrint's bugtracker. Thanks!") logger.exception("Stacktrace follows:") + finally: + observer.stop() + observer.join() def _createSocketConnection(self, session): global printer, gcodeManager, userManager, eventManager diff --git a/src/octoprint/server/util.py b/src/octoprint/server/util.py index 92ca725e..506d3108 100644 --- a/src/octoprint/server/util.py +++ b/src/octoprint/server/util.py @@ -1,4 +1,6 @@ # coding=utf-8 +from octoprint.filemanager.destinations import FileDestinations + __author__ = "Gina Häußge " __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' @@ -19,13 +21,15 @@ import os import threading import logging from functools import wraps +from watchdog.events import PatternMatchingEventHandler from octoprint.settings import settings import octoprint.timelapse import octoprint.server from octoprint.users import ApiUser from octoprint.events import Events - +from octoprint import gcodefiles +import octoprint.util as util def restricted_access(func, apiEnabled=True): """ @@ -485,3 +489,70 @@ def redirectToTornado(request, target): redirectUrl += fragment return redirect(redirectUrl) + +class UploadCleanupWatchdogHandler(PatternMatchingEventHandler): + """ + Takes care of automatically deleting metadata entries for files that get deleted from the uploads folder + """ + + patterns = map(lambda x: "*.%s" % x, gcodefiles.GCODE_EXTENSIONS) + + def __init__(self, gcode_manager): + PatternMatchingEventHandler.__init__(self) + self._gcode_manager = gcode_manager + + def on_deleted(self, event): + filename = self._gcode_manager._getBasicFilename(event.src_path) + if not filename: + return + + self._gcode_manager.removeFileFromMetadata(filename) + + +class GcodeWatchdogHandler(PatternMatchingEventHandler): + """ + Takes care of automatically "uploading" files that get added to the watchdog folder. + """ + + patterns = map(lambda x: "*.%s" % x, gcodefiles.SUPPORTED_EXTENSIONS) + + def __init__(self, gcodeManager, printer): + PatternMatchingEventHandler.__init__(self) + self._gcodeManager = gcodeManager + self._printer = printer + + def _upload(self, path): + class WatchdogFileWrapper(object): + + def __init__(self, path): + self._path = path + self.filename = os.path.basename(self._path) + + def save(self, target): + util.safeRename(self._path, target) + + fileWrapper = WatchdogFileWrapper(path) + + # determine current job + currentFilename = None + currentSd = None + currentJob = self._printer.getCurrentJob() + if currentJob is not None and "filename" in currentJob.keys() and "sd" in currentJob.keys(): + currentFilename = currentJob["filename"] + currentSd = currentJob["sd"] + + # determine future filename of file to be uploaded, abort if it can't be uploaded + futureFilename = self._gcodeManager.getFutureFilename(fileWrapper) + if futureFilename is None or (not settings().getBoolean(["cura", "enabled"]) and not gcodefiles.isGcodeFileName(futureFilename)): + return # Invalid file + + # prohibit overwriting currently selected file while it's being printed + if futureFilename == currentFilename and not currentSd and self._printer.isPrinting() or self._printer.isPaused(): + return # Trying to overwrite file that is currently being printed + + self._gcodeManager.addFile(fileWrapper, FileDestinations.LOCAL) + + def on_created(self, event): + self._upload(event.src_path) + + diff --git a/src/octoprint/settings.py b/src/octoprint/settings.py index 51dc84d2..0c3c4ded 100644 --- a/src/octoprint/settings.py +++ b/src/octoprint/settings.py @@ -77,7 +77,8 @@ default_settings = { "timelapse": None, "timelapse_tmp": None, "logs": None, - "virtualSd": None + "virtualSd": None, + "watchdog": None }, "temperature": { "profiles":