diff --git a/.gitignore b/.gitignore index 7f14f198..87413a78 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ build dist pypy OctoPrint.egg-info +*.orig diff --git a/.gitignore.orig b/.gitignore.orig deleted file mode 100644 index b4c7ad04..00000000 --- a/.gitignore.orig +++ /dev/null @@ -1,21 +0,0 @@ -*.tar.bz2 -*.tar.gz -*.7z -*.pyc -*.zip -*.exe -<<<<<<< HEAD -*.gcode -*.swp -darwin-Cura-* -win32-Cura-* -linux-Cura-* -Printrun -======= -*.iml ->>>>>>> devel -.idea -.DS_Store -build -dist -OctoPrint.egg-info diff --git a/octoprint/gcodefiles.py.orig b/octoprint/gcodefiles.py.orig deleted file mode 100644 index a97dbc21..00000000 --- a/octoprint/gcodefiles.py.orig +++ /dev/null @@ -1,467 +0,0 @@ -# coding=utf-8 -__author__ = "Gina Häußge " -__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' - -import os -import Queue -import threading -import datetime -import yaml -import time -import logging -import octoprint.util as util -import octoprint.util.gcodeInterpreter as gcodeInterpreter -from octoprint.settings import settings - -from werkzeug.utils import secure_filename - -class GcodeManager: - def __init__(self): - self._logger = logging.getLogger(__name__) - - self._settings = settings() - - self._uploadFolder = self._settings.getBaseFolder("uploads") - - self._callbacks = [] - - self._metadata = {} - self._metadataDirty = False - self._metadataFile = os.path.join(self._uploadFolder, "metadata.yaml") - self._metadataFileAccessMutex = threading.Lock() - - self._metadataAnalyzer = MetadataAnalyzer(getPathCallback=self.getAbsolutePath, loadedCallback=self._onMetadataAnalysisFinished) - - self._loadMetadata() - self._processAnalysisBacklog() - - def _processAnalysisBacklog(self): - for osFile in os.listdir(self._uploadFolder): - filename = self._getBasicFilename(osFile) - absolutePath = self.getAbsolutePath(filename) - if absolutePath is None: - continue - - fileData = self.getFileData(filename) - if fileData is not None and "gcodeAnalysis" in fileData.keys(): - continue - - self._metadataAnalyzer.addFileToBacklog(filename) - - def _onMetadataAnalysisFinished(self, filename, gcode): - if filename is None or gcode is None: - return - - basename = os.path.basename(filename) - - absolutePath = self.getAbsolutePath(basename) - if absolutePath is None: - return - - analysisResult = {} - dirty = False - if gcode.totalMoveTimeMinute: - analysisResult["estimatedPrintTime"] = util.getFormattedTimeDelta(datetime.timedelta(minutes=gcode.totalMoveTimeMinute)) - dirty = True - if gcode.extrusionAmount: - analysisResult["filament"] = "%.2fm" % (gcode.extrusionAmount / 1000) - if gcode.extrusionVolume: - analysisResult["filament"] += " / %.2fcm³" % gcode.extrusionVolume - dirty = True - - if dirty: - metadata = self.getFileMetadata(basename) - metadata["gcodeAnalysis"] = analysisResult - self._metadata[basename] = metadata - self._metadataDirty = True - self._saveMetadata() - - def _loadMetadata(self): - if os.path.exists(self._metadataFile) and os.path.isfile(self._metadataFile): - with self._metadataFileAccessMutex: - with open(self._metadataFile, "r") as f: - self._metadata = yaml.safe_load(f) - if self._metadata is None: - self._metadata = {} - - def _saveMetadata(self, force=False): - if not self._metadataDirty and not force: - return - - with self._metadataFileAccessMutex: - with open(self._metadataFile, "wb") as f: - yaml.safe_dump(self._metadata, f, default_flow_style=False, indent=" ", allow_unicode=True) - self._metadataDirty = False - self._loadMetadata() - self._sendUpdateTrigger("gcodeFiles") - - def _getBasicFilename(self, filename): - if filename.startswith(self._uploadFolder): - return filename[len(self._uploadFolder + os.path.sep):] - else: - return filename - - #~~ callback handling - - def registerCallback(self, callback): - self._callbacks.append(callback) - - def unregisterCallback(self, callback): - if callback in self._callbacks: - self._callbacks.remove(callback) - - def _sendUpdateTrigger(self, type): - for callback in self._callbacks: - try: callback.sendUpdateTrigger(type) - except: pass - - #~~ file handling - - def addFile(self, file): -<<<<<<< HEAD - from octoprint.util import isSTLFileName - from octoprint.util import isGcodeFileName - -======= ->>>>>>> devel - if not file: - return None - - absolutePath = self.getAbsolutePath(file.filename, mustExist=False) -<<<<<<< HEAD - - if absolutePath is None: - return None - - file.save(absolutePath) - filename = file.filename - - if isGcodeFileName(filename): - return self.processGcode(absolutePath) - - curaEnabled = self._settings.get(["curaEngine", "enabled"]) - - if isSTLFileName(filename) and curaEnabled: - gcodePath = util.genGcodeFileName(absolutePath) - logging.info("FILENAME: %s" % filename) - - callBackArgs = [gcodePath] - callBack = self.processGcode - - return self.processSTL( - filename, absolutePath, callBack, callBackArgs) - - def processSTL(self, filename, absolutePath, callBack, callBackArgs): - - from octoprint.cura import CuraFactory - - curaEngine = CuraFactory.create_slicer() - - gcodePath = util.genGcodeFileName(absolutePath) - - config = self._settings.get(["curaEngine", "config"]) - - curaEngine.process_file( - config, gcodePath, absolutePath, callBack, callBackArgs) - - def processGcode(self, absolutePath): - - filename = self._getBasicFilename(absolutePath) - - if filename in self._metadata.keys(): - # delete existing metadata entry, since the file is going to get overwritten - del self._metadata[filename] - self._metadataDirty = True - self._saveMetadata() - - self._metadataAnalyzer.addFileToQueue(os.path.basename(absolutePath)) - - return filename -======= - if absolutePath is None: - return None - - basename = self._getBasicFilename(absolutePath) - if basename in self._metadata.keys(): - # delete existing metadata entry, since the file is going to get overwritten - del self._metadata[basename] - self._metadataDirty = True - self._saveMetadata() - file.save(absolutePath) - self._metadataAnalyzer.addFileToQueue(basename) - return basename - - def getFutureFilename(self, file): - if not file: - return None - - absolutePath = self.getAbsolutePath(file.filename, mustExist=False) - if absolutePath is None: - return None - - return self._getBasicFilename(absolutePath) ->>>>>>> devel - - def removeFile(self, filename): - filename = self._getBasicFilename(filename) - absolutePath = self.getAbsolutePath(filename) - if absolutePath is None: - return - - os.remove(absolutePath) - if filename in self._metadata.keys(): - del self._metadata[filename] - self._metadataDirty = True - self._saveMetadata() - - def getAbsolutePath(self, filename, mustExist=True): - """ - Returns the absolute path of the given filename in the correct upload folder. - - Ensures that the file - - - @param filename the name of the file for which to determine the absolute path - @param mustExist if set to true, the method also checks if the file exists and is a file - @return the absolute path of the file or None if the file is not valid - """ - filename = self._getBasicFilename(filename) - - if not util.isAllowedFile(filename, set(["gcode", "stl"])): - return None - - # TODO: detect which type of file and add in the extra folder portion - secure = os.path.join(self._uploadFolder, secure_filename(self._getBasicFilename(filename))) - if mustExist and (not os.path.exists(secure) or not os.path.isfile(secure)): - return None - - return secure - - def getAllFileData(self): - files = [] - for osFile in os.listdir(self._uploadFolder): - fileData = self.getFileData(osFile) - if fileData is not None: - files.append(fileData) - return files - - def getFileData(self, filename): - from octoprint.util import isSTLFileName - - if not filename: - return - - filename = self._getBasicFilename(filename) - - # TODO: Make this more robust when STLs will be viewable from the client - if isSTLFileName(filename): - return - - absolutePath = self.getAbsolutePath(filename) - if absolutePath is None: - return None - - statResult = os.stat(absolutePath) - fileData = { - "name": filename, - "size": util.getFormattedSize(statResult.st_size), - "bytes": statResult.st_size, - "date": util.getFormattedDateTime(datetime.datetime.fromtimestamp(statResult.st_ctime)) - } - - # enrich with additional metadata from analysis if available - if filename in self._metadata.keys(): - for key in self._metadata[filename].keys(): - if key == "prints": - val = self._metadata[filename][key] - formattedLast = None - if val["last"] is not None: - formattedLast = { - "date": util.getFormattedDateTime(datetime.datetime.fromtimestamp(val["last"]["date"])), - "success": val["last"]["success"] - } - formattedPrints = { - "success": val["success"], - "failure": val["failure"], - "last": formattedLast - } - fileData["prints"] = formattedPrints - else: - fileData[key] = self._metadata[filename][key] - - return fileData - - def getFileMetadata(self, filename): - filename = self._getBasicFilename(filename) - if filename in self._metadata.keys(): - return self._metadata[filename] - else: - return { - "prints": { - "success": 0, - "failure": 0, - "last": None - } - } - - def setFileMetadata(self, filename, metadata): - filename = self._getBasicFilename(filename) - self._metadata[filename] = metadata - self._metadataDirty = True - - #~~ print job data - - def printSucceeded(self, filename): - filename = self._getBasicFilename(filename) - absolutePath = self.getAbsolutePath(filename) - if absolutePath is None: - return - - metadata = self.getFileMetadata(filename) - metadata["prints"]["success"] += 1 - metadata["prints"]["last"] = { - "date": time.time(), - "success": True - } - self.setFileMetadata(filename, metadata) - self._saveMetadata() - - def printFailed(self, filename): - filename = self._getBasicFilename(filename) - absolutePath = self.getAbsolutePath(filename) - if absolutePath is None: - return - - metadata = self.getFileMetadata(filename) - metadata["prints"]["failure"] += 1 - metadata["prints"]["last"] = { - "date": time.time(), - "success": False - } - self.setFileMetadata(filename, metadata) - self._saveMetadata() - - def changeLastPrintSuccess(self, filename, succeeded): - filename = self._getBasicFilename(filename) - absolutePath = self.getAbsolutePath(filename) - if absolutePath is None: - return - - metadata = self.getFileMetadata(filename) - if metadata is None: - return - - if "prints" in metadata.keys(): - if "last" in metadata.keys() and metadata["prints"]["last"] is not None: - currentSucceeded = metadata["prints"]["last"]["success"] - if currentSucceeded != succeeded: - metadata["prints"]["last"]["success"] = succeeded - if currentSucceeded: - # last print job was counted as success but actually failed - metadata["prints"]["success"] -= 1 - metadata["prints"]["failure"] += 1 - else: - # last print job was counted as a failure but actually succeeded - metadata["prints"]["success"] += 1 - metadata["prints"]["failure"] -= 1 - self.setFileMetadata(filename, metadata) - self._saveMetadata() - - #~~ analysis control - - def pauseAnalysis(self): - self._metadataAnalyzer.pause() - - def resumeAnalysis(self): - self._metadataAnalyzer.resume() - -class MetadataAnalyzer: - def __init__(self, getPathCallback, loadedCallback): - self._logger = logging.getLogger(__name__) - - self._getPathCallback = getPathCallback - self._loadedCallback = loadedCallback - - self._active = threading.Event() - self._active.set() - - self._currentFile = None - self._currentProgress = None - - self._queue = Queue.PriorityQueue() - self._gcode = None - - self._worker = threading.Thread(target=self._work) - self._worker.daemon = True - self._worker.start() - - def addFileToQueue(self, filename): - self._logger.debug("Adding file %s to analysis queue (high priority)" % filename) - self._queue.put((0, filename)) - - def addFileToBacklog(self, filename): - self._logger.debug("Adding file %s to analysis backlog (low priority)" % filename) - self._queue.put((100, filename)) - - def working(self): - return self.isActive() and not (self._queue.empty() and self._currentFile is None) - - def isActive(self): - return self._active.is_set() - - def pause(self): - self._logger.debug("Pausing Gcode analyzer") - self._active.clear() - if self._gcode is not None: - self._logger.debug("Aborting running analysis, will restart when Gcode analyzer is resumed") - self._gcode.abort() - - def resume(self): - self._logger.debug("Resuming Gcode analyzer") - self._active.set() - - def _work(self): - aborted = None - while True: - self._active.wait() - - if aborted is not None: - filename = aborted - aborted = None - self._logger.debug("Got an aborted analysis job for file %s, processing this instead of first item in queue" % filename) - else: - (priority, filename) = self._queue.get() - self._logger.debug("Processing file %s from queue (priority %d)" % (filename, priority)) - - try: - self._analyzeGcode(filename) - self._queue.task_done() - except gcodeInterpreter.AnalysisAborted: - aborted = filename - self._logger.debug("Running analysis of file %s aborted" % filename) - - def _analyzeGcode(self, filename): - path = self._getPathCallback(filename) - if path is None: - return - - self._currentFile = filename - self._currentProgress = 0 - - try: - self._logger.debug("Starting analysis of file %s" % filename) - self._gcode = gcodeInterpreter.gcode() - self._gcode.progressCallback = self._onParsingProgress - self._gcode.load(path) - self._logger.debug("Analysis of file %s finished, notifying callback" % filename) - self._loadedCallback(self._currentFile, self._gcode) - finally: - self._gcode = None - self._currentProgress = None - self._currentFile = None - - def _onParsingProgress(self, progress): - self._currentProgress = progress diff --git a/octoprint/printer.py.orig b/octoprint/printer.py.orig deleted file mode 100644 index 7575372b..00000000 --- a/octoprint/printer.py.orig +++ /dev/null @@ -1,719 +0,0 @@ -# coding=utf-8 -__author__ = "Gina Häußge " -__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' - -import time -import datetime -import threading -import copy -import os - -#import logging, logging.config - -import octoprint.util.comm as comm -import octoprint.util as util - -from octoprint.settings import settings -from octoprint.events import eventManager - -def getConnectionOptions(): - """ - Retrieves the available ports, baudrates, prefered port and baudrate for connecting to the printer. - """ - return { - "ports": comm.serialList(), - "baudrates": comm.baudrateList(), - "portPreference": settings().get(["serial", "port"]), - "baudratePreference": settings().getInt(["serial", "baudrate"]), - "autoconnect": settings().getBoolean(["serial", "autoconnect"]) - } - -class Printer(): - def __init__(self, gcodeManager): - from collections import deque - - self._gcodeManager = gcodeManager - self._gcodeManager.registerCallback(self) - - # state - self._temp = None - self._bedTemp = None - self._targetTemp = None - self._targetBedTemp = None - self._temps = { - "actual": deque([], 300), - "target": deque([], 300), - "actualBed": deque([], 300), - "targetBed": deque([], 300) - } - self._tempBacklog = [] - - self._latestMessage = None - self._messages = deque([], 300) - self._messageBacklog = [] - - self._latestLog = None - self._log = deque([], 300) - self._logBacklog = [] - - self._state = None - - self._currentZ = None - - self._progress = None - self._printTime = None - self._printTimeLeft = None - - self._printAfterSelect = False - - # sd handling - self._sdPrinting = False - self._sdStreaming = False - - self._selectedFile = None - - # comm - self._comm = None - - # callbacks - self._callbacks = [] - self._lastProgressReport = None - - self._stateMonitor = StateMonitor( - ratelimit=0.5, - updateCallback=self._sendCurrentDataCallbacks, - addTemperatureCallback=self._sendAddTemperatureCallbacks, - addLogCallback=self._sendAddLogCallbacks, - addMessageCallback=self._sendAddMessageCallbacks - ) - self._stateMonitor.reset( - state={"state": None, "stateString": self.getStateString(), "flags": self._getStateFlags()}, - jobData={"filename": None, "filesize": None, "estimatedPrintTime": None, "filament": None}, - progress={"progress": None, "filepos": None, "printTime": None, "printTimeLeft": None}, - currentZ=None - ) - - #~~ callback handling - - def registerCallback(self, callback): - self._callbacks.append(callback) - self._sendInitialStateUpdate(callback) - - def unregisterCallback(self, callback): - if callback in self._callbacks: - self._callbacks.remove(callback) - - def _sendAddTemperatureCallbacks(self, data): - for callback in self._callbacks: - try: callback.addTemperature(data) - except: pass - - def _sendAddLogCallbacks(self, data): - for callback in self._callbacks: - try: callback.addLog(data) - except: pass - - def _sendAddMessageCallbacks(self, data): - for callback in self._callbacks: - try: callback.addMessage(data) - except: pass - - def _sendCurrentDataCallbacks(self, data): - for callback in self._callbacks: - try: callback.sendCurrentData(copy.deepcopy(data)) - except: pass - - def _sendTriggerUpdateCallbacks(self, type): - for callback in self._callbacks: - try: callback.sendUpdateTrigger(type) - except: pass - - def _sendFeedbackCommandOutput(self, name, output): - for callback in self._callbacks: - try: callback.sendFeedbackCommandOutput(name, output) - except: pass - - #~~ callback from gcodemanager - - def sendUpdateTrigger(self, type): - if type == "gcodeFiles" and self._selectedFile: - self._setJobData(self._selectedFile["filename"], - self._selectedFile["filesize"], - self._selectedFile["sd"]) - - #~~ printer commands - - def connect(self, port=None, baudrate=None): - """ - Connects to the printer. If port and/or baudrate is provided, uses these settings, otherwise autodetection - will be attempted. - """ - if self._comm is not None: - self._comm.close() - self._comm = comm.MachineCom(port, baudrate, callbackObject=self) - - def disconnect(self): - """ - Closes the connection to the printer. - """ - if self._comm is not None: - self._comm.close() - self._comm = None - eventManager().fire("Disconnected") - - def command(self, command): - """ - Sends a single gcode command to the printer. - """ - self.commands([command]) - - def commands(self, commands): - """ - Sends multiple gcode commands (provided as a list) to the printer. - """ - for command in commands: - self._comm.sendCommand(command) - - def selectFile(self, filename, sd, printAfterSelect=False): - if self._comm is not None and (self._comm.isBusy() or self._comm.isStreaming()): - return - - self._printAfterSelect = printAfterSelect - self._comm.selectFile(filename, sd) - self._setProgressData(0, None, None, None) - self._setCurrentZ(None) - - def unselectFile(self): - if self._comm is not None and (self._comm.isBusy() or self._comm.isStreaming()): - return - - self._comm.unselectFile() - self._setProgressData(0, None, None, None) - self._setCurrentZ(None) - - def startPrint(self): - """ - Starts the currently loaded print job. - Only starts if the printer is connected and operational, not currently printing and a printjob is loaded - """ - if self._comm is None or not self._comm.isOperational() or self._comm.isPrinting(): - return - if self._selectedFile is None: - return - - self._setCurrentZ(None) - self._comm.startPrint() - - def togglePausePrint(self): - """ - Pause the current printjob. - """ - if self._comm is None: - return - - self._comm.setPause(not self._comm.isPaused()) - - def cancelPrint(self, disableMotorsAndHeater=True): - """ - Cancel the current printjob. - """ - if self._comm is None: - return - - self._comm.cancelPrint() - - if disableMotorsAndHeater: - self.commands(["M84", "M104 S0", "M140 S0", "M106 S0"]) # disable motors, switch off heaters and fan - - # reset progress, height, print time - self._setCurrentZ(None) - self._setProgressData(None, None, None, None) - - # mark print as failure - if self._selectedFile is not None: - self._gcodeManager.printFailed(self._selectedFile["filename"]) - eventManager().fire("PrintFailed", self._selectedFile["filename"]) - - #~~ state monitoring - - def _setCurrentZ(self, currentZ): - self._currentZ = currentZ - - formattedCurrentZ = None - if self._currentZ: - formattedCurrentZ = "%.2f mm" % (self._currentZ) - self._stateMonitor.setCurrentZ(formattedCurrentZ) - - def _setState(self, state): - self._state = state - self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()}) - - def _addLog(self, log): - self._log.append(log) - self._stateMonitor.addLog(log) - - def _addMessage(self, message): - self._messages.append(message) - self._stateMonitor.addMessage(message) - - def _setProgressData(self, progress, filepos, printTime, printTimeLeft): - self._progress = progress - self._printTime = printTime - self._printTimeLeft = printTimeLeft - - formattedPrintTime = None - if (self._printTime): - formattedPrintTime = util.getFormattedTimeDelta(datetime.timedelta(seconds=self._printTime)) - - formattedPrintTimeLeft = None - if (self._printTimeLeft): - formattedPrintTimeLeft = util.getFormattedTimeDelta(datetime.timedelta(minutes=self._printTimeLeft)) - - formattedFilePos = None - if (filepos): - formattedFilePos = util.getFormattedSize(filepos) - - self._stateMonitor.setProgress({"progress": self._progress, "filepos": formattedFilePos, "printTime": formattedPrintTime, "printTimeLeft": formattedPrintTimeLeft}) - - def _addTemperatureData(self, temp, bedTemp, targetTemp, bedTargetTemp): - currentTimeUtc = int(time.time() * 1000) - - self._temps["actual"].append((currentTimeUtc, temp)) - self._temps["target"].append((currentTimeUtc, targetTemp)) - self._temps["actualBed"].append((currentTimeUtc, bedTemp)) - self._temps["targetBed"].append((currentTimeUtc, bedTargetTemp)) - - self._temp = temp - self._bedTemp = bedTemp - self._targetTemp = targetTemp - self._targetBedTemp = bedTargetTemp - - self._stateMonitor.addTemperature({"currentTime": currentTimeUtc, "temp": self._temp, "bedTemp": self._bedTemp, "targetTemp": self._targetTemp, "targetBedTemp": self._targetBedTemp}) - - def _setJobData(self, filename, filesize, sd): - if filename is not None: - self._selectedFile = { - "filename": filename, - "filesize": filesize, - "sd": sd - } - else: - self._selectedFile = None - - formattedFilename = None - formattedFilesize = None - estimatedPrintTime = None - fileMTime = None - filament = None - if filename: - formattedFilename = os.path.basename(filename) - - # Use a string for mtime because it could be float and the - # javascript needs to exact match - if not sd: - fileMTime = str(os.stat(filename).st_mtime) - - if filesize: - formattedFilesize = util.getFormattedSize(filesize) - - fileData = self._gcodeManager.getFileData(filename) - if fileData is not None and "gcodeAnalysis" in fileData.keys(): - if "estimatedPrintTime" in fileData["gcodeAnalysis"].keys(): - estimatedPrintTime = fileData["gcodeAnalysis"]["estimatedPrintTime"] - if "filament" in fileData["gcodeAnalysis"].keys(): - filament = fileData["gcodeAnalysis"]["filament"] - - self._stateMonitor.setJobData({"filename": formattedFilename, "filesize": formattedFilesize, "estimatedPrintTime": estimatedPrintTime, "filament": filament, "sd": sd, "mtime": fileMTime}) - - def _sendInitialStateUpdate(self, callback): - try: - data = self._stateMonitor.getCurrentData() - # convert the dict of deques to a dict of lists - temps = {k: list(v) for (k,v) in self._temps.iteritems()} - data.update({ - "temperatureHistory": temps, - "logHistory": list(self._log), - "messageHistory": list(self._messages) - }) - callback.sendHistoryData(data) - except Exception, err: - import sys - sys.stderr.write("ERROR: %s\n" % str(err)) - pass - - def _getStateFlags(self): - if not settings().getBoolean(["feature", "sdSupport"]) or self._comm is None: - sdReady = False - else: - sdReady = self._comm.isSdReady() - - return { - "operational": self.isOperational(), - "printing": self.isPrinting(), - "closedOrError": self.isClosedOrError(), - "error": self.isError(), - "paused": self.isPaused(), - "ready": self.isReady(), - "sdReady": sdReady - } - - def getCurrentData(self): - return self._stateMonitor.getCurrentData() - - #~~ callbacks triggered from self._comm - - def mcLog(self, message): - """ - Callback method for the comm object, called upon log output. - """ - self._addLog(message) - - def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp): - self._addTemperatureData(temp, bedTemp, targetTemp, bedTargetTemp) - - def mcStateChange(self, state): - """ - Callback method for the comm object, called if the connection state changes. - """ - oldState = self._state - - # forward relevant state changes to gcode manager - if self._comm is not None and oldState == self._comm.STATE_PRINTING: - if self._selectedFile is not None: - if state == self._comm.STATE_OPERATIONAL: - self._gcodeManager.printSucceeded(self._selectedFile["filename"]) - elif state == self._comm.STATE_CLOSED or state == self._comm.STATE_ERROR or state == self._comm.STATE_CLOSED_WITH_ERROR: - self._gcodeManager.printFailed(self._selectedFile["filename"]) - self._gcodeManager.resumeAnalysis() # printing done, put those cpu cycles to good use - elif self._comm is not None and state == self._comm.STATE_PRINTING: - self._gcodeManager.pauseAnalysis() # do not analyse gcode while printing - - self._setState(state) - - def mcMessage(self, message): - """ - Callback method for the comm object, called upon message exchanges via serial. - Stores the message in the message buffer, truncates buffer to the last 300 lines. - """ - self._addMessage(message) - - def mcProgress(self): - """ - Callback method for the comm object, called upon any change in progress of the printjob. - Triggers storage of new values for printTime, printTimeLeft and the current progress. - """ - - self._setProgressData(self._comm.getPrintProgress(), self._comm.getPrintFilepos(), self._comm.getPrintTime(), self._comm.getPrintTimeRemainingEstimate()) - - def mcZChange(self, newZ): - """ - Callback method for the comm object, called upon change of the z-layer. - """ - oldZ = self._currentZ - if newZ != oldZ: - # we have to react to all z-changes, even those that might "go backward" due to a slicer's retraction or - # anti-backlash-routines. Event subscribes should individually take care to filter out "wrong" z-changes - eventManager().fire("ZChange", newZ) - - self._setCurrentZ(newZ) - - def mcSdStateChange(self, sdReady): - self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()}) - - def mcSdFiles(self, files): - self._sendTriggerUpdateCallbacks("gcodeFiles") - - def mcFileSelected(self, filename, filesize, sd): - self._setJobData(filename, filesize, sd) - self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()}) - - if self._printAfterSelect: - self.startPrint() - - def mcPrintjobDone(self): - self._setProgressData(1.0, self._selectedFile["filesize"], self._comm.getPrintTime(), 0) - self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()}) - - def mcFileTransferStarted(self, filename, filesize): - self._sdStreaming = True - - self._setJobData(filename, filesize, True) - self._setProgressData(0.0, 0, 0, None) - self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()}) - - def mcFileTransferDone(self): - self._sdStreaming = False - - self._setCurrentZ(None) - self._setJobData(None, None, None) - self._setProgressData(None, None, None, None) - self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()}) - - def mcReceivedRegisteredMessage(self, command, output): - self._sendFeedbackCommandOutput(command, output) - - #~~ sd file handling - - def getSdFiles(self): - if self._comm is None: - return - return self._comm.getSdFiles() - -<<<<<<< HEAD - def addSdFile(self, filename, absolutePath): - from octoprint.util import isGcodeFileName - from octoprint.util import isSTLFileName - - if not self._comm: - return - - if isGcodeFileName(filename): - self.streamSdFile(filename, absolutePath) - - if isSTLFileName(filename): - gcodePath = util.genGcodeFileName(absolutePath) - callBackArgs = [filename, absolutePath] - callBack = self.streamSdFile - - gcodeManager = GcodeManager() - gcodeManager.processSTL( - filename, absolutePath, callBack, callBackArgs) - - def streamSdFile(filename, absolutePath): - self._sdStreamer = SdFileStreamer(self._comm, filename, absolutePath, self._onSdFileStreamProgress, self._onSdFileStreamFinish) - self._sdStreamer.start() - logging.info("Stream file to SD started") -======= - def addSdFile(self, filename, path): - if not self._comm or self._comm.isBusy(): - return - self._comm.startFileTransfer(path, filename[:8].lower() + ".gco") ->>>>>>> devel - - def deleteSdFile(self, filename): - if not self._comm: - return - self._comm.deleteSdFile(filename) - - def initSdCard(self): - if not self._comm: - return - self._comm.initSdCard() - - def releaseSdCard(self): - if not self._comm: - return - self._comm.releaseSdCard() - - def refreshSdFiles(self): - if not self._comm: - return - self._comm.refreshSdFiles() - - #~~ state reports - - def getStateString(self): - """ - Returns a human readable string corresponding to the current communication state. - """ - if self._comm is None: - return "Offline" - else: - return self._comm.getStateString() - - def getCurrentData(self): - return self._stateMonitor.getCurrentData() - - def getCurrentJob(self): - currentData = self._stateMonitor.getCurrentData() - return currentData["job"] - - def getCurrentTemperatures(self): - return { - "extruder": { - "current": self._temp, - "target": self._targetTemp - }, - "bed": { - "current": self._bedTemp, - "target": self._targetBedTemp - } - } - - def isClosedOrError(self): - return self._comm is None or self._comm.isClosedOrError() - - def isOperational(self): - return self._comm is not None and self._comm.isOperational() - - def isPrinting(self): - return self._comm is not None and self._comm.isPrinting() - - def isPaused(self): - return self._comm is not None and self._comm.isPaused() - - def isError(self): - return self._comm is not None and self._comm.isError() - - def isReady(self): - return self.isOperational() and not self._comm.isStreaming() - - def isLoading(self): - return self._gcodeLoader is not None - -class GcodeLoader(threading.Thread): - """ - The GcodeLoader takes care of loading a gcode-File from disk and parsing it into a gcode object in a separate - thread while constantly notifying interested listeners about the current progress. - The progress is returned as a float value between 0 and 1 which is to be interpreted as the percentage of completion. - """ - - def __init__(self, filename, progressCallback, loadedCallback): - threading.Thread.__init__(self) - - self._progressCallback = progressCallback - self._loadedCallback = loadedCallback - - self._filename = filename - self._gcodeList = None - - def run(self): - #Send an initial M110 to reset the line counter to zero. - prevLineType = lineType = "CUSTOM" - gcodeList = ["M110 N0"] - filesize = os.stat(self._filename).st_size - with open(self._filename, "r") as file: - for line in file: - if line.startswith(";TYPE:"): - lineType = line[6:].strip() - if ";" in line: - line = line[0:line.find(";")] - line = line.strip() - if len(line) > 0: - if prevLineType != lineType: - gcodeList.append((line, lineType, )) - else: - gcodeList.append(line) - prevLineType = lineType - self._onLoadingProgress(float(file.tell()) / float(filesize)) - - self._gcodeList = gcodeList - self._loadedCallback(self._filename, self._gcodeList) - - def _onLoadingProgress(self, progress): - self._progressCallback(self._filename, progress, "loading") - - def _onParsingProgress(self, progress): - self._progressCallback(self._filename, progress, "parsing") - -class SdFileStreamer(threading.Thread): - def __init__(self, comm, filename, file, progressCallback, finishCallback): - threading.Thread.__init__(self) - - self._comm = comm - self._filename = filename - self._file = file - self._progressCallback = progressCallback - self._finishCallback = finishCallback - - def run(self): - if self._comm.isBusy(): - return - - name = self._filename[:self._filename.rfind(".")] - sdFilename = name[:8].lower() + ".gco" - try: - size = os.stat(self._file).st_size - with open(self._file, "r") as f: - self._comm.startSdFileTransfer(sdFilename) - for line in f: - if ";" in line: - line = line[0:line.find(";")] - line = line.strip() - if len(line) > 0: - self._comm.sendCommand(line) - time.sleep(0.001) # do not send too fast - self._progressCallback(sdFilename, float(f.tell()) / float(size)) - finally: - self._comm.endSdFileTransfer(sdFilename) - self._finishCallback(sdFilename) - -class StateMonitor(object): - def __init__(self, ratelimit, updateCallback, addTemperatureCallback, addLogCallback, addMessageCallback): - self._ratelimit = ratelimit - self._updateCallback = updateCallback - self._addTemperatureCallback = addTemperatureCallback - self._addLogCallback = addLogCallback - self._addMessageCallback = addMessageCallback - - self._state = None - self._jobData = None - self._gcodeData = None - self._sdUploadData = None - self._currentZ = None - self._progress = None - - self._changeEvent = threading.Event() - - self._lastUpdate = time.time() - self._worker = threading.Thread(target=self._work) - self._worker.daemon = True - self._worker.start() - - def reset(self, state=None, jobData=None, progress=None, currentZ=None): - self.setState(state) - self.setJobData(jobData) - self.setProgress(progress) - self.setCurrentZ(currentZ) - - def addTemperature(self, temperature): - self._addTemperatureCallback(temperature) - self._changeEvent.set() - - def addLog(self, log): - self._addLogCallback(log) - self._changeEvent.set() - - def addMessage(self, message): - self._addMessageCallback(message) - self._changeEvent.set() - - def setCurrentZ(self, currentZ): - self._currentZ = currentZ - self._changeEvent.set() - - def setState(self, state): - self._state = state - self._changeEvent.set() - - def setJobData(self, jobData): - self._jobData = jobData - self._changeEvent.set() - - def setProgress(self, progress): - self._progress = progress - self._changeEvent.set() - - def _work(self): - while True: - self._changeEvent.wait() - - now = time.time() - delta = now - self._lastUpdate - additionalWaitTime = self._ratelimit - delta - if additionalWaitTime > 0: - time.sleep(additionalWaitTime) - - data = self.getCurrentData() - self._updateCallback(data) - self._lastUpdate = time.time() - self._changeEvent.clear() - - def getCurrentData(self): - return { - "state": self._state, - "job": self._jobData, - "currentZ": self._currentZ, - "progress": self._progress - } - diff --git a/octoprint/server.py.orig b/octoprint/server.py.orig deleted file mode 100644 index b13f99f3..00000000 --- a/octoprint/server.py.orig +++ /dev/null @@ -1,1021 +0,0 @@ -# coding=utf-8 -__author__ = "Gina Häußge " -__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' - -from werkzeug.utils import secure_filename -import tornadio2 -from flask import Flask, request, render_template, jsonify, send_from_directory, url_for, current_app, session, abort, make_response -from flask.ext.login import LoginManager, login_user, logout_user, login_required, current_user -from flask.ext.principal import Principal, Permission, RoleNeed, Identity, identity_changed, AnonymousIdentity, identity_loaded, UserNeed - -import os -import threading -import logging, logging.config -import subprocess - -from octoprint.printer import Printer, getConnectionOptions -from octoprint.settings import settings, valid_boolean_trues -import octoprint.timelapse -import octoprint.gcodefiles as gcodefiles -import octoprint.util as util -import octoprint.users as users - -import octoprint.events as events - -SUCCESS = {} -BASEURL = "/ajax/" -APIBASEURL = "/api/" - -app = Flask("octoprint") -# Only instantiated by the Server().run() method -# In order that threads don't start too early when running as a Daemon -printer = None -timelapse = None - -gcodeManager = None -userManager = None -eventManager = None - -principals = Principal(app) -admin_permission = Permission(RoleNeed("admin")) -user_permission = Permission(RoleNeed("user")) - -#~~ Printer state - -class PrinterStateConnection(tornadio2.SocketConnection): - def __init__(self, printer, gcodeManager, userManager, eventManager, session, endpoint=None): - tornadio2.SocketConnection.__init__(self, session, endpoint) - - self._logger = logging.getLogger(__name__) - - self._temperatureBacklog = [] - self._temperatureBacklogMutex = threading.Lock() - self._logBacklog = [] - self._logBacklogMutex = threading.Lock() - self._messageBacklog = [] - self._messageBacklogMutex = threading.Lock() - - self._printer = printer - self._gcodeManager = gcodeManager - self._userManager = userManager - self._eventManager = eventManager - - def on_open(self, info): - self._logger.info("New connection from client") - # Use of global here is smelly - self._printer.registerCallback(self) - self._gcodeManager.registerCallback(self) - - self._eventManager.fire("ClientOpened") - self._eventManager.subscribe("MovieDone", self._onMovieDone) - - def on_close(self): - self._logger.info("Closed client connection") - # Use of global here is smelly - self._printer.unregisterCallback(self) - self._gcodeManager.unregisterCallback(self) - - self._eventManager.fire("ClientClosed") - self._eventManager.unsubscribe("MovieDone", self._onMovieDone) - - def on_message(self, message): - pass - - def sendCurrentData(self, data): - # add current temperature, log and message backlogs to sent data - with self._temperatureBacklogMutex: - temperatures = self._temperatureBacklog - self._temperatureBacklog = [] - - with self._logBacklogMutex: - logs = self._logBacklog - self._logBacklog = [] - - with self._messageBacklogMutex: - messages = self._messageBacklog - self._messageBacklog = [] - - data.update({ - "temperatures": temperatures, - "logs": logs, - "messages": messages - }) - self.emit("current", data) - - def sendHistoryData(self, data): - self.emit("history", data) - - def sendUpdateTrigger(self, type): - self.emit("updateTrigger", type) - - def sendFeedbackCommandOutput(self, name, output): - self.emit("feedbackCommandOutput", {"name": name, "output": output}) - - def addLog(self, data): - with self._logBacklogMutex: - self._logBacklog.append(data) - - def addMessage(self, data): - with self._messageBacklogMutex: - self._messageBacklog.append(data) - - def addTemperature(self, data): - with self._temperatureBacklogMutex: - self._temperatureBacklog.append(data) - - def _onMovieDone(self, event, payload): - self.sendUpdateTrigger("timelapseFiles") - -# Did attempt to make webserver an encapsulated class but ended up with __call__ failures - -@app.route("/") -def index(): - branch = None - commit = None - try: - branch, commit = util.getGitInfo() - except: - pass - - return render_template( - "index.jinja2", - ajaxBaseUrl=BASEURL, - webcamStream=settings().get(["webcam", "stream"]), - enableTimelapse=(settings().get(["webcam", "snapshot"]) is not None and settings().get(["webcam", "ffmpeg"]) is not None), - enableGCodeVisualizer=settings().get(["feature", "gCodeVisualizer"]), - enableSystemMenu=settings().get(["system"]) is not None and settings().get(["system", "actions"]) is not None and len(settings().get(["system", "actions"])) > 0, - enableAccessControl=userManager is not None, - enableSdSupport=settings().get(["feature", "sdSupport"]), - gitBranch=branch, - gitCommit=commit - ) - -#~~ Printer control - -@app.route(BASEURL + "control/connection/options", methods=["GET"]) -def connectionOptions(): - return jsonify(getConnectionOptions()) - -@app.route(BASEURL + "control/connection", methods=["POST"]) -@login_required -def connect(): - if "command" in request.values.keys() and request.values["command"] == "connect": - port = None - baudrate = None - if "port" in request.values.keys(): - port = request.values["port"] - if "baudrate" in request.values.keys(): - baudrate = request.values["baudrate"] - if "save" in request.values.keys(): - settings().set(["serial", "port"], port) - settings().setInt(["serial", "baudrate"], baudrate) - settings().save() - if "autoconnect" in request.values.keys(): - settings().setBoolean(["serial", "autoconnect"], True) - settings().save() - printer.connect(port=port, baudrate=baudrate) - elif "command" in request.values.keys() and request.values["command"] == "disconnect": - printer.disconnect() - - return jsonify(SUCCESS) - -@app.route(BASEURL + "control/command", methods=["POST"]) -@login_required -def printerCommand(): - if "application/json" in request.headers["Content-Type"]: - data = request.json - - parameters = {} - if "parameters" in data.keys(): parameters = data["parameters"] - - commands = [] - if "command" in data.keys(): commands = [data["command"]] - elif "commands" in data.keys(): commands = data["commands"] - - commandsToSend = [] - for command in commands: - commandToSend = command - if len(parameters) > 0: - commandToSend = command % parameters - commandsToSend.append(commandToSend) - - printer.commands(commandsToSend) - - return jsonify(SUCCESS) - -@app.route(BASEURL + "control/job", methods=["POST"]) -@login_required -def printJobControl(): - if "command" in request.values.keys(): - if request.values["command"] == "start": - printer.startPrint() - elif request.values["command"] == "pause": - printer.togglePausePrint() - elif request.values["command"] == "cancel": - printer.cancelPrint() - return jsonify(SUCCESS) - -@app.route(BASEURL + "control/temperature", methods=["POST"]) -@login_required -def setTargetTemperature(): - if "temp" in request.values.keys(): - # set target temperature - temp = request.values["temp"] - printer.command("M104 S" + temp) - - if "bedTemp" in request.values.keys(): - # set target bed temperature - bedTemp = request.values["bedTemp"] - printer.command("M140 S" + bedTemp) - - return jsonify(SUCCESS) - -@app.route(BASEURL + "control/jog", methods=["POST"]) -@login_required -def jog(): - if not printer.isOperational() or printer.isPrinting(): - # do not jog when a print job is running or we don't have a connection - return jsonify(SUCCESS) - - (movementSpeedX, movementSpeedY, movementSpeedZ, movementSpeedE) = settings().get(["printerParameters", "movementSpeed", ["x", "y", "z", "e"]]) - if "x" in request.values.keys(): - # jog x - x = request.values["x"] - printer.commands(["G91", "G1 X%s F%d" % (x, movementSpeedX), "G90"]) - if "y" in request.values.keys(): - # jog y - y = request.values["y"] - printer.commands(["G91", "G1 Y%s F%d" % (y, movementSpeedY), "G90"]) - if "z" in request.values.keys(): - # jog z - z = request.values["z"] - printer.commands(["G91", "G1 Z%s F%d" % (z, movementSpeedZ), "G90"]) - if "homeXY" in request.values.keys(): - # home x/y - printer.command("G28 X0 Y0") - if "homeZ" in request.values.keys(): - # home z - printer.command("G28 Z0") - if "extrude" in request.values.keys(): - # extrude/retract - length = request.values["extrude"] - printer.commands(["G91", "G1 E%s F%d" % (length, movementSpeedE), "G90"]) - - return jsonify(SUCCESS) - -@app.route(BASEURL + "control/custom", methods=["GET"]) -def getCustomControls(): - customControls = settings().get(["controls"]) - return jsonify(controls=customControls) - -@app.route(BASEURL + "control/sd", methods=["POST"]) -@login_required -def sdCommand(): - if not settings().getBoolean(["feature", "sdSupport"]) or not printer.isOperational() or printer.isPrinting(): - return jsonify(SUCCESS) - - if "command" in request.values.keys(): - command = request.values["command"] - if command == "init": - printer.initSdCard() - elif command == "refresh": - printer.refreshSdFiles() - elif command == "release": - printer.releaseSdCard() - - return jsonify(SUCCESS) - -#~~ GCODE file handling - -@app.route(BASEURL + "gcodefiles", methods=["GET"]) -def readGcodeFiles(): - files = gcodeManager.getAllFileData() - - sdFileList = printer.getSdFiles() - if sdFileList is not None: - for sdFile in sdFileList: - files.append({ - "name": sdFile, - "size": "n/a", - "bytes": 0, - "date": "n/a", - "origin": "sd" - }) - return jsonify(files=files, free=util.getFormattedSize(util.getFreeBytes(settings().getBaseFolder("uploads")))) - -@app.route(BASEURL + "gcodefiles/", methods=["GET"]) -def readGcodeFile(filename): - return send_from_directory(settings().getBaseFolder("uploads"), filename, as_attachment=True) - -@app.route(BASEURL + "gcodefiles/upload", methods=["POST"]) -@login_required -def uploadGcodeFile(): -<<<<<<< HEAD - filename = None - -======= ->>>>>>> devel - if "gcode_file" in request.files.keys(): - file = request.files["gcode_file"] - sd = "target" in request.values.keys() and request.values["target"] == "sd"; - - currentFilename = None - currentSd = None - currentJob = printer.getCurrentJob() - if currentJob is not None and "filename" in currentJob.keys() and "sd" in currentJob.keys(): - currentFilename = currentJob["filename"] - currentSd = currentJob["sd"] - - futureFilename = gcodeManager.getFutureFilename(file) - if futureFilename is None: - return make_response("Can not upload file %s, wrong format?" % file.filename, 400) - - if futureFilename == currentFilename and sd == currentSd and printer.isPrinting() or printer.isPaused(): - # trying to overwrite currently selected file, but it is being printed - return make_response("Trying to overwrite file that is currently being printed: %s" % currentFilename, 403) - - filename = gcodeManager.addFile(file) -<<<<<<< HEAD - if filename and "target" in request.values.keys() and request.values["target"] == "sd": - printer.addSdFile(filename, gcodeManager.getAbsolutePath(filename)) - -======= - if filename is None: - return make_response("Could not upload the file %s" % file.filename, 500) - - absFilename = gcodeManager.getAbsolutePath(filename) - if sd: - printer.addSdFile(filename, absFilename) - - if currentFilename == filename and currentSd == sd: - # reload file as it was updated - if sd: - printer.selectFile(filename, sd, False) - else: - printer.selectFile(absFilename, sd, False) - - global eventManager - eventManager.fire("Upload", filename) ->>>>>>> devel - return jsonify(files=gcodeManager.getAllFileData(), filename=filename) - - -@app.route(BASEURL + "gcodefiles/load", methods=["POST"]) -@login_required -def loadGcodeFile(): - if "filename" in request.values.keys(): - printAfterLoading = False - if "print" in request.values.keys() and request.values["print"] in valid_boolean_trues: - printAfterLoading = True - - sd = False - if "target" in request.values.keys() and request.values["target"] == "sd": - filename = request.values["filename"] - sd = True - else: - filename = gcodeManager.getAbsolutePath(request.values["filename"]) - printer.selectFile(filename, sd, printAfterLoading) - return jsonify(SUCCESS) - -@app.route(BASEURL + "gcodefiles/delete", methods=["POST"]) -@login_required -def deleteGcodeFile(): - if "filename" in request.values.keys(): - filename = request.values["filename"] - sd = "target" in request.values.keys() and request.values["target"] == "sd" - - currentJob = printer.getCurrentJob() - currentFilename = None - currentSd = None - if currentJob is not None and "filename" in currentJob.keys() and "sd" in currentJob.keys(): - currentFilename = currentJob["filename"] - currentSd = currentJob["sd"] - - if currentFilename is not None and filename == currentFilename and not (printer.isPrinting() or printer.isPaused()): - printer.unselectFile() - - if not (currentFilename == filename and currentSd == sd and (printer.isPrinting() or printer.isPaused())): - if currentSd: - printer.deleteSdFile(filename) - else: - gcodeManager.removeFile(filename) - return readGcodeFiles() - -@app.route(BASEURL + "gcodefiles/refresh", methods=["POST"]) -@login_required -def refreshFiles(): - printer.updateSdFiles() - return jsonify(SUCCESS) - -#-- very simple api routines -@app.route(APIBASEURL + "load", methods=["POST"]) -def apiLoad(): - logger = logging.getLogger(__name__) - - if not settings().get(["api", "enabled"]): - abort(401) - - if not "apikey" in request.values.keys(): - abort(401) - - if request.values["apikey"] != settings().get(["api", "key"]): - abort(403) - - if not "file" in request.files.keys(): - abort(400) - - # Perform an upload - file = request.files["file"] - filename = gcodeManager.addFile(file) - if filename is None: - logger.warn("Upload via API failed") - abort(500) - - # Immediately perform a file select and possibly print too - printAfterSelect = False - if "print" in request.values.keys() and request.values["print"] in valid_boolean_trues: - printAfterSelect = True - filepath = gcodeManager.getAbsolutePath(filename) - if filepath is not None: - printer.selectFile(filepath, False, printAfterSelect) - return jsonify(SUCCESS) - -@app.route(APIBASEURL + "state", methods=["GET"]) -def apiPrinterState(): - if not settings().get(["api", "enabled"]): - abort(401) - - if not "apikey" in request.values.keys(): - abort(401) - - if request.values["apikey"] != settings().get(["api", "key"]): - abort(403) - - currentData = printer.getCurrentData() - currentData.update({ - "temperatures": printer.getCurrentTemperatures() - }) - return jsonify(currentData) - -#~~ timelapse handling - -@app.route(BASEURL + "timelapse", methods=["GET"]) -def getTimelapseData(): - global timelapse - - type = "off" - additionalConfig = {} - if timelapse is not None and isinstance(timelapse, octoprint.timelapse.ZTimelapse): - type = "zchange" - elif timelapse is not None and isinstance(timelapse, octoprint.timelapse.TimedTimelapse): - type = "timed" - additionalConfig = { - "interval": timelapse.interval() - } - - files = octoprint.timelapse.getFinishedTimelapses() - for file in files: - file["url"] = url_for("downloadTimelapse", filename=file["name"]) - - return jsonify({ - "type": type, - "config": additionalConfig, - "files": files - }) - -@app.route(BASEURL + "timelapse/", methods=["GET"]) -def downloadTimelapse(filename): - if util.isAllowedFile(filename, set(["mpg"])): - return send_from_directory(settings().getBaseFolder("timelapse"), filename, as_attachment=True) - -@app.route(BASEURL + "timelapse/", methods=["DELETE"]) -@login_required -def deleteTimelapse(filename): - if util.isAllowedFile(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", methods=["POST"]) -@login_required -def setTimelapseConfig(): - global timelapse - - if request.values.has_key("type"): - type = request.values["type"] - if type in ["zchange", "timed"]: - # valid timelapse type, check if there is an old one we need to stop first - if timelapse is not None: - timelapse.unload() - timelapse = None - if "zchange" == type: - timelapse = octoprint.timelapse.ZTimelapse() - elif "timed" == type: - interval = 10 - if request.values.has_key("interval"): - try: - interval = int(request.values["interval"]) - except ValueError: - pass - timelapse = octoprint.timelapse.TimedTimelapse(interval) - - return getTimelapseData() - -#~~ settings - -@app.route(BASEURL + "settings", methods=["GET"]) -def getSettings(): - s = settings() - - [movementSpeedX, movementSpeedY, movementSpeedZ, movementSpeedE] = s.get(["printerParameters", "movementSpeed", ["x", "y", "z", "e"]]) - - connectionOptions = getConnectionOptions() - - return jsonify({ - "api": { - "enabled": s.getBoolean(["api", "enabled"]), - "key": s.get(["api", "key"]) - }, - "appearance": { - "name": s.get(["appearance", "name"]), - "color": s.get(["appearance", "color"]) - }, - "printer": { - "movementSpeedX": movementSpeedX, - "movementSpeedY": movementSpeedY, - "movementSpeedZ": movementSpeedZ, - "movementSpeedE": movementSpeedE, - }, - "webcam": { - "streamUrl": s.get(["webcam", "stream"]), - "snapshotUrl": s.get(["webcam", "snapshot"]), - "ffmpegPath": s.get(["webcam", "ffmpeg"]), - "bitrate": s.get(["webcam", "bitrate"]), - "watermark": s.getBoolean(["webcam", "watermark"]), - "flipH": s.getBoolean(["webcam", "flipH"]), - "flipV": s.getBoolean(["webcam", "flipV"]) - }, - "feature": { - "gcodeViewer": s.getBoolean(["feature", "gCodeVisualizer"]), - "waitForStart": s.getBoolean(["feature", "waitForStartOnConnect"]), - "alwaysSendChecksum": s.getBoolean(["feature", "alwaysSendChecksum"]), - "sdSupport": s.getBoolean(["feature", "sdSupport"]) - }, - "serial": { - "port": connectionOptions["portPreference"], - "baudrate": connectionOptions["baudratePreference"], - "portOptions": connectionOptions["ports"], - "baudrateOptions": connectionOptions["baudrates"], - "autoconnect": s.getBoolean(["serial", "autoconnect"]), - "timeoutConnection": s.getFloat(["serial", "timeout", "connection"]), - "timeoutDetection": s.getFloat(["serial", "timeout", "detection"]), - "timeoutCommunication": s.getFloat(["serial", "timeout", "communication"]), - "log": s.getBoolean(["serial", "log"]) - }, - "folder": { - "uploads": s.getBaseFolder("uploads"), - "timelapse": s.getBaseFolder("timelapse"), - "timelapseTmp": s.getBaseFolder("timelapse_tmp"), - "logs": s.getBaseFolder("logs") - }, - "temperature": { - "profiles": s.get(["temperature", "profiles"]) - }, - "system": { -<<<<<<< HEAD - "actions": s.get(["system", "actions"]) - }, - "curaEngine": { - "enabled": s.getBoolean(["curaEngine", "enabled"]), - "path": s.get(["curaEngine", "path"]), - "config": s.get(["curaEngine", "config"]) - } -======= - "actions": s.get(["system", "actions"]), - "events": s.get(["system", "events"]) - } ->>>>>>> devel - }) - -@app.route(BASEURL + "settings", methods=["POST"]) -@login_required -@admin_permission.require(403) -def setSettings(): - if "application/json" in request.headers["Content-Type"]: - data = request.json - s = settings() - - if "api" in data.keys(): - if "enabled" in data["api"].keys(): s.set(["api", "enabled"], data["api"]["enabled"]) - if "key" in data["api"].keys(): s.set(["api", "key"], data["api"]["key"], True) - - if "appearance" in data.keys(): - if "name" in data["appearance"].keys(): s.set(["appearance", "name"], data["appearance"]["name"]) - if "color" in data["appearance"].keys(): s.set(["appearance", "color"], data["appearance"]["color"]) - - if "printer" in data.keys(): - if "movementSpeedX" in data["printer"].keys(): s.setInt(["printerParameters", "movementSpeed", "x"], data["printer"]["movementSpeedX"]) - if "movementSpeedY" in data["printer"].keys(): s.setInt(["printerParameters", "movementSpeed", "y"], data["printer"]["movementSpeedY"]) - if "movementSpeedZ" in data["printer"].keys(): s.setInt(["printerParameters", "movementSpeed", "z"], data["printer"]["movementSpeedZ"]) - if "movementSpeedE" in data["printer"].keys(): s.setInt(["printerParameters", "movementSpeed", "e"], data["printer"]["movementSpeedE"]) - - if "webcam" in data.keys(): - if "streamUrl" in data["webcam"].keys(): s.set(["webcam", "stream"], data["webcam"]["streamUrl"]) - if "snapshotUrl" in data["webcam"].keys(): s.set(["webcam", "snapshot"], data["webcam"]["snapshotUrl"]) - if "ffmpegPath" in data["webcam"].keys(): s.set(["webcam", "ffmpeg"], data["webcam"]["ffmpegPath"]) - if "bitrate" in data["webcam"].keys(): s.set(["webcam", "bitrate"], data["webcam"]["bitrate"]) - if "watermark" in data["webcam"].keys(): s.setBoolean(["webcam", "watermark"], data["webcam"]["watermark"]) - if "flipH" in data["webcam"].keys(): s.setBoolean(["webcam", "flipH"], data["webcam"]["flipH"]) - if "flipV" in data["webcam"].keys(): s.setBoolean(["webcam", "flipV"], data["webcam"]["flipV"]) - - if "feature" in data.keys(): - if "gcodeViewer" in data["feature"].keys(): s.setBoolean(["feature", "gCodeVisualizer"], data["feature"]["gcodeViewer"]) - if "waitForStart" in data["feature"].keys(): s.setBoolean(["feature", "waitForStartOnConnect"], data["feature"]["waitForStart"]) - if "alwaysSendChecksum" in data["feature"].keys(): s.setBoolean(["feature", "alwaysSendChecksum"], data["feature"]["alwaysSendChecksum"]) - if "sdSupport" in data["feature"].keys(): s.setBoolean(["feature", "sdSupport"], data["feature"]["sdSupport"]) - - if "serial" in data.keys(): - if "autoconnect" in data["serial"].keys(): s.setBoolean(["serial", "autoconnect"], data["serial"]["autoconnect"]) - if "port" in data["serial"].keys(): s.set(["serial", "port"], data["serial"]["port"]) - if "baudrate" in data["serial"].keys(): s.setInt(["serial", "baudrate"], data["serial"]["baudrate"]) - if "timeoutConnection" in data["serial"].keys(): s.setFloat(["serial", "timeout", "connection"], data["serial"]["timeoutConnection"]) - if "timeoutDetection" in data["serial"].keys(): s.setFloat(["serial", "timeout", "detection"], data["serial"]["timeoutDetection"]) - if "timeoutCommunication" in data["serial"].keys(): s.setFloat(["serial", "timeout", "communication"], data["serial"]["timeoutCommunication"]) - - oldLog = s.getBoolean(["serial", "log"]) - if "log" in data["serial"].keys(): s.setBoolean(["serial", "log"], data["serial"]["log"]) - if oldLog and not s.getBoolean(["serial", "log"]): - # disable debug logging to serial.log - logging.getLogger("SERIAL").debug("Disabling serial logging") - logging.getLogger("SERIAL").setLevel(logging.CRITICAL) - elif not oldLog and s.getBoolean(["serial", "log"]): - # enable debug logging to serial.log - logging.getLogger("SERIAL").setLevel(logging.DEBUG) - logging.getLogger("SERIAL").debug("Enabling serial logging") - - if "folder" in data.keys(): - if "uploads" in data["folder"].keys(): s.setBaseFolder("uploads", data["folder"]["uploads"]) - if "timelapse" in data["folder"].keys(): s.setBaseFolder("timelapse", data["folder"]["timelapse"]) - if "timelapseTmp" in data["folder"].keys(): s.setBaseFolder("timelapse_tmp", data["folder"]["timelapseTmp"]) - if "logs" in data["folder"].keys(): s.setBaseFolder("logs", data["folder"]["logs"]) - - if "temperature" in data.keys(): - if "profiles" in data["temperature"].keys(): s.set(["temperature", "profiles"], data["temperature"]["profiles"]) - - if "system" in data.keys(): - if "actions" in data["system"].keys(): s.set(["system", "actions"], data["system"]["actions"]) -<<<<<<< HEAD - - curaEngine = data.get("curaEngine", None) - - if curaEngine: - path = curaEngine.get("path") - if path: - s.set(["curaEngine", "path"], path) - - config = curaEngine.get("config") - if config: - s.set(["curaEngine", "config"], config) - - # Enabled is a boolean so we cannot check that we have a result - enabled = curaEngine.get("enabled") - s.setBoolean(["curaEngine", "enabled"], enabled) - -======= - if "events" in data["system"].keys(): s.set(["system", "events"], data["system"]["events"]) ->>>>>>> devel - s.save() - - return getSettings() - -#~~ user settings - -@app.route(BASEURL + "users", methods=["GET"]) -@login_required -@admin_permission.require(403) -def getUsers(): - if userManager is None: - return jsonify(SUCCESS) - - return jsonify({"users": userManager.getAllUsers()}) - -@app.route(BASEURL + "users", methods=["POST"]) -@login_required -@admin_permission.require(403) -def addUser(): - if userManager is None: - return jsonify(SUCCESS) - - if "application/json" in request.headers["Content-Type"]: - data = request.json - - name = data["name"] - password = data["password"] - active = data["active"] - - roles = ["user"] - if "admin" in data.keys() and data["admin"]: - roles.append("admin") - - try: - userManager.addUser(name, password, active, roles) - except users.UserAlreadyExists: - abort(409) - return getUsers() - -@app.route(BASEURL + "users/", methods=["GET"]) -@login_required -def getUser(username): - if userManager is None: - return jsonify(SUCCESS) - - if current_user is not None and not current_user.is_anonymous() and (current_user.get_name() == username or current_user.is_admin()): - user = userManager.findUser(username) - if user is not None: - return jsonify(user.asDict()) - else: - abort(404) - else: - abort(403) - -@app.route(BASEURL + "users/", methods=["PUT"]) -@login_required -@admin_permission.require(403) -def updateUser(username): - if userManager is None: - return jsonify(SUCCESS) - - user = userManager.findUser(username) - if user is not None: - if "application/json" in request.headers["Content-Type"]: - data = request.json - - # change roles - roles = ["user"] - if "admin" in data.keys() and data["admin"]: - roles.append("admin") - userManager.changeUserRoles(username, roles) - - # change activation - if "active" in data.keys(): - userManager.changeUserActivation(username, data["active"]) - return getUsers() - else: - abort(404) - -@app.route(BASEURL + "users/", methods=["DELETE"]) -@login_required -@admin_permission.require(http_exception=403) -def removeUser(username): - if userManager is None: - return jsonify(SUCCESS) - - try: - userManager.removeUser(username) - return getUsers() - except users.UnknownUser: - abort(404) - -@app.route(BASEURL + "users//password", methods=["PUT"]) -@login_required -def changePasswordForUser(username): - if userManager is None: - return jsonify(SUCCESS) - - if current_user is not None and not current_user.is_anonymous() and (current_user.get_name() == username or current_user.is_admin()): - if "application/json" in request.headers["Content-Type"]: - data = request.json - if "password" in data.keys() and data["password"]: - try: - userManager.changeUserPassword(username, data["password"]) - except users.UnknownUser: - return app.make_response(("Unknown user: %s" % username, 404, [])) - return jsonify(SUCCESS) - else: - return app.make_response(("Forbidden", 403, [])) - -#~~ system control - -@app.route(BASEURL + "system", methods=["POST"]) -@login_required -@admin_permission.require(403) -def performSystemAction(): - logger = logging.getLogger(__name__) - if request.values.has_key("action"): - action = request.values["action"] - availableActions = settings().get(["system", "actions"]) - for availableAction in availableActions: - if availableAction["action"] == action: - logger.info("Performing command: %s" % availableAction["command"]) - try: - subprocess.check_output(availableAction["command"], shell=True) - except subprocess.CalledProcessError, e: - logger.warn("Command failed with return code %i: %s" % (e.returncode, e.message)) - return app.make_response(("Command failed with return code %i: %s" % (e.returncode, e.message), 500, [])) - except Exception, ex: - logger.exception("Command failed") - return app.make_response(("Command failed: %r" % ex, 500, [])) - return jsonify(SUCCESS) - -#~~ Login/user handling - -@app.route(BASEURL + "login", methods=["POST"]) -def login(): - if userManager is not None and "user" in request.values.keys() and "pass" in request.values.keys(): - username = request.values["user"] - password = request.values["pass"] - - if "remember" in request.values.keys() and request.values["remember"] == "true": - remember = True - else: - remember = False - - user = userManager.findUser(username) - if user is not None: - if user.check_password(users.UserManager.createPasswordHash(password)): - login_user(user, remember=remember) - identity_changed.send(current_app._get_current_object(), identity=Identity(user.get_id())) - return jsonify(user.asDict()) - return app.make_response(("User unknown or password incorrect", 401, [])) - elif "passive" in request.values.keys(): - user = current_user - if user is not None and not user.is_anonymous(): - identity_changed.send(current_app._get_current_object(), identity=Identity(user.get_id())) - return jsonify(user.asDict()) - return jsonify(SUCCESS) - -@app.route(BASEURL + "logout", methods=["POST"]) -@login_required -def logout(): - # Remove session keys set by Flask-Principal - for key in ('identity.id', 'identity.auth_type'): - del session[key] - identity_changed.send(current_app._get_current_object(), identity=AnonymousIdentity()) - - logout_user() - - return jsonify(SUCCESS) - -@identity_loaded.connect_via(app) -def on_identity_loaded(sender, identity): - user = load_user(identity.id) - if user is None: - return - - identity.provides.add(UserNeed(user.get_name())) - if user.is_user(): - identity.provides.add(RoleNeed("user")) - if user.is_admin(): - identity.provides.add(RoleNeed("admin")) - -def load_user(id): - if userManager is not None: - return userManager.findUser(id) - return users.DummyUser() - -#~~ startup code -class Server(): - def __init__(self, configfile=None, basedir=None, host="0.0.0.0", port=5000, debug=False): - self._configfile = configfile - self._basedir = basedir - self._host = host - self._port = port - self._debug = debug - - - def run(self): - # Global as I can't work out a way to get it into PrinterStateConnection - global printer - global gcodeManager - global userManager - global eventManager - - from tornado.wsgi import WSGIContainer - from tornado.httpserver import HTTPServer - from tornado.ioloop import IOLoop - from tornado.web import Application, FallbackHandler - - # first initialize the settings singleton and make sure it uses given configfile and basedir if available - self._initSettings(self._configfile, self._basedir) - - # then initialize logging - self._initLogging(self._debug) - logger = logging.getLogger(__name__) - - eventManager = events.eventManager() - gcodeManager = gcodefiles.GcodeManager() - printer = Printer(gcodeManager) - - # setup system and gcode command triggers - events.SystemCommandTrigger(printer) - events.GcodeCommandTrigger(printer) - if self._debug: - events.DebugEventListener() - - if settings().getBoolean(["accessControl", "enabled"]): - userManagerName = settings().get(["accessControl", "userManager"]) - try: - clazz = util.getClass(userManagerName) - userManager = clazz() - except AttributeError, e: - logger.exception("Could not instantiate user manager %s, will run with accessControl disabled!" % userManagerName) - - app.secret_key = "k3PuVYgtxNm8DXKKTw2nWmFQQun9qceV" - login_manager = LoginManager() - login_manager.session_protection = "strong" - login_manager.user_callback = load_user - if userManager is None: - login_manager.anonymous_user = users.DummyUser - principals.identity_loaders.appendleft(users.dummy_identity_loader) - login_manager.init_app(app) - - if self._host is None: - self._host = settings().get(["server", "host"]) - if self._port is None: - self._port = settings().getInt(["server", "port"]) - - logger.info("Listening on http://%s:%d" % (self._host, self._port)) - app.debug = self._debug - - self._router = tornadio2.TornadioRouter(self._createSocketConnection) - - self._tornado_app = Application(self._router.urls + [ - (".*", FallbackHandler, {"fallback": WSGIContainer(app)}) - ]) - self._server = HTTPServer(self._tornado_app) - self._server.listen(self._port, address=self._host) - - eventManager.fire("Startup") - if settings().getBoolean(["serial", "autoconnect"]): - (port, baudrate) = settings().get(["serial", "port"]), settings().getInt(["serial", "baudrate"]) - connectionOptions = getConnectionOptions() - if port in connectionOptions["ports"]: - printer.connect(port, baudrate) - IOLoop.instance().start() - - def _createSocketConnection(self, session, endpoint=None): - global printer, gcodeManager, userManager, eventManager - return PrinterStateConnection(printer, gcodeManager, userManager, eventManager, session, endpoint) - - def _initSettings(self, configfile, basedir): - s = settings(init=True, basedir=basedir, configfile=configfile) - - def _initLogging(self, debug): - config = { - "version": 1, - "formatters": { - "simple": { - "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - } - }, - "handlers": { - "console": { - "class": "logging.StreamHandler", - "level": "DEBUG", - "formatter": "simple", - "stream": "ext://sys.stdout" - }, - "file": { - "class": "logging.handlers.TimedRotatingFileHandler", - "level": "DEBUG", - "formatter": "simple", - "when": "D", - "backupCount": "1", - "filename": os.path.join(settings().getBaseFolder("logs"), "octoprint.log") - }, - "serialFile": { - "class": "logging.handlers.RotatingFileHandler", - "level": "DEBUG", - "formatter": "simple", - "maxBytes": 2 * 1024 * 1024, # let's limit the serial log to 2MB in size - "filename": os.path.join(settings().getBaseFolder("logs"), "serial.log") - } - }, - "loggers": { - #"octoprint.timelapse": { - # "level": "DEBUG" - #}, - #"octoprint.events": { - # "level": "DEBUG" - #}, - "SERIAL": { - "level": "CRITICAL", - "handlers": ["serialFile"], - "propagate": False - } - }, - "root": { - "level": "INFO", - "handlers": ["console", "file"] - } - } - - if debug: - config["root"]["level"] = "DEBUG" - - logging.config.dictConfig(config) - -if __name__ == "__main__": - octoprint = Server() - octoprint.run() diff --git a/octoprint/settings.py.orig b/octoprint/settings.py.orig deleted file mode 100644 index 3ab67251..00000000 --- a/octoprint/settings.py.orig +++ /dev/null @@ -1,391 +0,0 @@ -# coding=utf-8 -__author__ = "Gina Häußge " -__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' - -import sys -import os -import yaml -import logging -import re -import uuid - -APPNAME="OctoPrint" - -instance = None - -def settings(init=False, configfile=None, basedir=None): - global instance - if instance is None: - if init: - instance = Settings(configfile, basedir) - else: - raise ValueError("Settings not initialized yet") - return instance - -default_settings = { - "serial": { - "port": None, - "baudrate": None, - "autoconnect": False, - "log": False, - "timeout": { - "detection": 0.5, - "connection": 2, - "communication": 5 - } - }, - "server": { - "host": "0.0.0.0", - "port": 5000 - }, - "webcam": { - "stream": None, - "snapshot": None, - "ffmpeg": None, - "bitrate": "5000k", - "watermark": True, - "flipH": False, - "flipV": False - }, - "feature": { - "gCodeVisualizer": True, - "waitForStartOnConnect": False, - "alwaysSendChecksum": False, - "sdSupport": True - }, - "folder": { - "uploads": None, - "timelapse": None, - "timelapse_tmp": None, - "logs": None, - "virtualSd": None - }, - "temperature": { - "profiles": - [ - {"name": "ABS", "extruder" : 210, "bed" : 100 }, - {"name": "PLA", "extruder" : 180, "bed" : 60 } - ] - }, - "printerParameters": { - "movementSpeed": { - "x": 6000, - "y": 6000, - "z": 200, - "e": 300 - }, - "pauseTriggers": [] - }, - "appearance": { - "name": "", - "color": "default" - }, - "controls": [], - "system": { - "actions": [] - }, - "accessControl": { - "enabled": False, - "userManager": "octoprint.users.FilebasedUserManager", - "userfile": None - }, -<<<<<<< HEAD - "curaEngine": { - "enabled": False, - "path": "/default/path/to/curaEngine/dir/", - "config": "/default/path/to/your/curaEngine/config/file/" - } -======= - "events": { - "systemCommandTrigger": { - "enabled": False - }, - "gcodeCommandTrigger": { - "enabled": False - } - }, - "api": { - "enabled": False, - "key": ''.join('%02X' % ord(z) for z in uuid.uuid4().bytes) - } ->>>>>>> devel -} - -valid_boolean_trues = ["true", "yes", "y", "1"] - -class Settings(object): - - def __init__(self, configfile=None, basedir=None): - self._logger = logging.getLogger(__name__) - - self.settings_dir = None - - self._config = None - self._dirty = False - - self._init_settings_dir(basedir) - - if configfile is not None: - self._configfile = configfile - else: - self._configfile = os.path.join(self.settings_dir, "config.yaml") - self.load() - - def _init_settings_dir(self, basedir): - if basedir is not None: - self.settings_dir = basedir - else: - self.settings_dir = _resolveSettingsDir(APPNAME) - - def _getDefaultFolder(self, type): - folder = default_settings["folder"][type] - if folder is None: - folder = os.path.join(self.settings_dir, type.replace("_", os.path.sep)) - return folder - - #~~ load and save - - def load(self): - if os.path.exists(self._configfile) and os.path.isfile(self._configfile): - with open(self._configfile, "r") as f: - self._config = yaml.safe_load(f) - # chamged from else to handle cases where the file exists, but is empty / 0 bytes - if not self._config: - self._config = {} - - def save(self, force=False): - if not self._dirty and not force: - return - - with open(self._configfile, "wb") as configFile: - yaml.safe_dump(self._config, configFile, default_flow_style=False, indent=" ", allow_unicode=True) - self._dirty = False - self.load() - - #~~ getter - - def get(self, path): - if len(path) == 0: - return None - - config = self._config - defaults = default_settings - - while len(path) > 1: - key = path.pop(0) - if key in config.keys() and key in defaults.keys(): - config = config[key] - defaults = defaults[key] - elif key in defaults.keys(): - config = {} - defaults = defaults[key] - else: - return None - - k = path.pop(0) - if not isinstance(k, (list, tuple)): - keys = [k] - else: - keys = k - - results = [] - for key in keys: - if key in config.keys(): - results.append(config[key]) - elif key in defaults: - results.append(defaults[key]) - else: - results.append(None) - - if not isinstance(k, (list, tuple)): - return results.pop() - else: - return results - - def getInt(self, path): - value = self.get(path) - if value is None: - return None - - try: - return int(value) - except ValueError: - self._logger.warn("Could not convert %r to a valid integer when getting option %r" % (value, path)) - return None - - def getFloat(self, path): - value = self.get(path) - if value is None: - return None - - try: - return float(value) - except ValueError: - self._logger.warn("Could not convert %r to a valid integer when getting option %r" % (value, path)) - return None - - def getBoolean(self, path): - value = self.get(path) - if value is None: - return None - if isinstance(value, bool): - return value - return value.lower() in valid_boolean_trues - - def getBaseFolder(self, type): - if type not in default_settings["folder"].keys(): - return None - - folder = self.get(["folder", type]) - if folder is None: - folder = self._getDefaultFolder(type) - - if not os.path.isdir(folder): - os.makedirs(folder) - - return folder - - def getFeedbackControls(self): - feedbackControls = [] - for control in self.get(["controls"]): - feedbackControls.extend(self._getFeedbackControls(control)) - return feedbackControls - - def _getFeedbackControls(self, control=None): - if control["type"] == "feedback_command": - pattern = control["regex"] - try: - matcher = re.compile(pattern) - return [(control["name"], matcher, control["template"])] - except: - # invalid regex or something like this, we'll just skip this entry - pass - elif control["type"] == "section": - result = [] - for c in control["children"]: - result.extend(self._getFeedbackControls(c)) - return result - else: - return [] - - def getPauseTriggers(self): - triggers = { - "enable": [], - "disable": [], - "toggle": [] - } - for trigger in self.get(["printerParameters", "pauseTriggers"]): - try: - regex = trigger["regex"] - type = trigger["type"] - if type in triggers.keys(): - # make sure regex is valid - re.compile(regex) - # add to type list - triggers[type].append(regex) - except: - # invalid regex or something like this, we'll just skip this entry - pass - - result = {} - for type in triggers.keys(): - if len(triggers[type]) > 0: - result[type] = re.compile("|".join(map(lambda x: "(%s)" % x, triggers[type]))) - return result - - #~~ setter - - def set(self, path, value, force=False): - if len(path) == 0: - return - - config = self._config - defaults = default_settings - - while len(path) > 1: - key = path.pop(0) - if key in config.keys() and key in defaults.keys(): - config = config[key] - defaults = defaults[key] - elif key in defaults.keys(): - config[key] = {} - config = config[key] - defaults = defaults[key] - else: - return - - key = path.pop(0) - if not force and key in defaults.keys() and key in config.keys() and defaults[key] == value: - del config[key] - self._dirty = True - elif force or (not key in config.keys() and defaults[key] != value) or (key in config.keys() and config[key] != value): - if value is None: - del config[key] - else: - config[key] = value - self._dirty = True - - def setInt(self, path, value, force=False): - if value is None: - self.set(path, None, force) - return - - try: - intValue = int(value) - except ValueError: - self._logger.warn("Could not convert %r to a valid integer when setting option %r" % (value, path)) - return - - self.set(path, intValue, force) - - def setFloat(self, path, value, force=False): - if value is None: - self.set(path, None, force) - return - - try: - floatValue = float(value) - except ValueError: - self._logger.warn("Could not convert %r to a valid integer when setting option %r" % (value, path)) - return - - self.set(path, floatValue, force) - - def setBoolean(self, path, value, force=False): - if value is None or isinstance(value, bool): - self.set(path, value, force) - elif value.lower() in valid_boolean_trues: - self.set(path, True, force) - else: - self.set(path, False, force) - - def setBaseFolder(self, type, path, force=False): - if type not in default_settings["folder"].keys(): - return None - - currentPath = self.getBaseFolder(type) - defaultPath = self._getDefaultFolder(type) - if (path is None or path == defaultPath) and "folder" in self._config.keys() and type in self._config["folder"].keys(): - del self._config["folder"][type] - if not self._config["folder"]: - del self._config["folder"] - self._dirty = True - elif (path != currentPath and path != defaultPath) or force: - if not "folder" in self._config.keys(): - self._config["folder"] = {} - self._config["folder"][type] = path - self._dirty = True - -def _resolveSettingsDir(applicationName): - # taken from http://stackoverflow.com/questions/1084697/how-do-i-store-desktop-application-data-in-a-cross-platform-way-for-python - if sys.platform == "darwin": - from AppKit import NSSearchPathForDirectoriesInDomains - # http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains - # NSApplicationSupportDirectory = 14 - # NSUserDomainMask = 1 - # True for expanding the tilde into a fully qualified path - return os.path.join(NSSearchPathForDirectoriesInDomains(14, 1, True)[0], applicationName) - elif sys.platform == "win32": - return os.path.join(os.environ["APPDATA"], applicationName) - else: - return os.path.expanduser(os.path.join("~", "." + applicationName.lower())) diff --git a/octoprint/templates/settings.jinja2.orig b/octoprint/templates/settings.jinja2.orig deleted file mode 100644 index b75a8e8c..00000000 --- a/octoprint/templates/settings.jinja2.orig +++ /dev/null @@ -1,481 +0,0 @@ -