diff --git a/src/octoprint/gcodefiles.py b/src/octoprint/gcodefiles.py index 42e1bdf8..52ac2cf9 100644 --- a/src/octoprint/gcodefiles.py +++ b/src/octoprint/gcodefiles.py @@ -14,6 +14,7 @@ import octoprint.util.gcodeInterpreter as gcodeInterpreter from octoprint.settings import settings from octoprint.events import eventManager +from octoprint.filemanager.destinations import FileDestinations from werkzeug.utils import secure_filename @@ -165,9 +166,15 @@ class GcodeManager: #~~ file handling - def addFile(self, file, destination): - from octoprint.filemanager.destinations import FileDestinations + def addFile(self, file, destination, uploadCallback=None): + """ + Adds the given file for the given destination to the systems. Takes care of slicing if enabled and + necessary. + If the file's processing won't be finished directly with the return from this method but happen + asynchronously in the background (e.g. due to slicing), returns a tuple containing the just added file's + filename and False. Otherwise returns a tuple (filename, True). + """ if not file or not destination: return None, True @@ -183,11 +190,10 @@ class GcodeManager: file.save(absolutePath) if gcode: - return self.processGcode(absolutePath), True + return self.processGcode(absolutePath, destination, uploadCallback), True else: - local = (destination == FileDestinations.LOCAL) - if curaEnabled and isSTLFileName(filename) and local: - self.processStl(absolutePath) + if curaEnabled and isSTLFileName(filename): + self.processStl(absolutePath, destination, uploadCallback) return filename, False @@ -202,7 +208,7 @@ class GcodeManager: return self._getBasicFilename(absolutePath) - def processStl(self, absolutePath): + def processStl(self, absolutePath, destination, uploadCallback=None): from octoprint.slicers.cura import CuraFactory cura = CuraFactory.create_slicer() @@ -210,6 +216,7 @@ class GcodeManager: config = self._settings.get(["cura", "config"]) slicingStart = time.time() + def stlProcessed(stlPath, gcodePath, error=None): if error: eventManager().fire("SlicingFailed", {"stl": self._getBasicFilename(stlPath), "gcode": self._getBasicFilename(gcodePath), "reason": error}) @@ -218,13 +225,13 @@ class GcodeManager: else: slicingStop = time.time() eventManager().fire("SlicingDone", {"stl": self._getBasicFilename(stlPath), "gcode": self._getBasicFilename(gcodePath), "time": "%.2f" % (slicingStop - slicingStart)}) - self.processGcode(gcodePath) + self.processGcode(gcodePath, destination, uploadCallback) eventManager().fire("SlicingStarted", {"stl": self._getBasicFilename(absolutePath), "gcode": self._getBasicFilename(gcodePath)}) cura.process_file(config, gcodePath, absolutePath, stlProcessed, [absolutePath, gcodePath]) - def processGcode(self, absolutePath): + def processGcode(self, absolutePath, destination, uploadCallback=None): if absolutePath is None: return None @@ -238,7 +245,9 @@ class GcodeManager: self._metadataAnalyzer.addFileToQueue(os.path.basename(absolutePath)) - return filename + if uploadCallback is not None: + uploadCallback(filename, absolutePath, destination) + return filename def getFutureFilename(self, file): if not file: diff --git a/src/octoprint/printer.py b/src/octoprint/printer.py index 99ceb4fb..1418d66a 100644 --- a/src/octoprint/printer.py +++ b/src/octoprint/printer.py @@ -17,6 +17,8 @@ import octoprint.util as util from octoprint.settings import settings from octoprint.events import eventManager +from octoprint.filemanager.destinations import FileDestinations + def getConnectionOptions(): """ Retrieves the available ports, baudrates, prefered port and baudrate for connecting to the printer. @@ -71,6 +73,8 @@ class Printer(): self._sdPrinting = False self._sdStreaming = False self._sdFilelistAvailable = threading.Event() + self._sdRemoteName = None + self._streamingFinishedCallback = None self._selectedFile = None @@ -458,6 +462,10 @@ class Printer(): def mcFileTransferDone(self): self._sdStreaming = False + if self._streamingFinishedCallback is not None: + self._streamingFinishedCallback(self._sdRemoteName, FileDestinations.SDCARD) + + self._sdRemoteName = None self._setCurrentZ(None) self._setJobData(None, None, None) self._setProgressData(None, None, None, None) @@ -473,35 +481,18 @@ class Printer(): return [] return self._comm.getSdFiles() - def addSdFile(self, filename, absolutePath): - from octoprint.gcodefiles import isGcodeFileName - from octoprint.gcodefiles import isSTLFileName - + def addSdFile(self, filename, absolutePath, streamingFinishedCallback): if not self._comm or self._comm.isBusy() or not self._comm.isSdReady(): logging.error("No connection to printer or printer is busy") return - if isGcodeFileName(filename): - self.streamSdFile(filename, absolutePath) - - if isSTLFileName(filename): - gcodePath = util.genGcodeFileName(absolutePath) - gcodeFileName = util.genGcodeFileName(filename) - callBackArgs = [gcodeFileName, gcodePath] - callBack = self.streamSdFile - - self._gcodeManager.processStl( - absolutePath, callBack, callBackArgs) - - def streamSdFile(self, filename, path): - if not self._comm or self._comm.isBusy() or not self._comm.isSdReady(): - return + self._streamingFinishedCallback = streamingFinishedCallback self.refreshSdFiles(blocking=True) existingSdFiles = self._comm.getSdFiles() - sdFilename = util.getDosFilename(filename, existingSdFiles) - self._comm.startFileTransfer(path, sdFilename) + self._sdRemoteName = util.getDosFilename(filename, existingSdFiles) + self._comm.startFileTransfer(absolutePath, self._sdRemoteName) def deleteSdFile(self, filename): if not self._comm or not self._comm.isSdReady(): diff --git a/src/octoprint/server/ajax/gcodefiles.py b/src/octoprint/server/ajax/gcodefiles.py index 870a996c..e13ae41f 100644 --- a/src/octoprint/server/ajax/gcodefiles.py +++ b/src/octoprint/server/ajax/gcodefiles.py @@ -33,18 +33,19 @@ def readGcodeFiles(): return jsonify(files=files, free=util.getFormattedSize(util.getFreeBytes(settings().getBaseFolder("uploads")))) -@ajax.route("/gcodefiles/", methods=["GET"]) -def readGcodeFile(filename): - return redirectToTornado(request, url_for("index") + "downloads/gcode/" + filename) - - -@ajax.route("/gcodefiles/upload", methods=["POST"]) +@ajax.route("/gcodefiles/", methods=["POST"]) @restricted_access -def uploadGcodeFile(): +def uploadGcodeFile(target): + if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]: + return make_response("Invalid target: %s" % target, 400) + if "gcode_file" in request.files.keys(): file = request.files["gcode_file"] - sd = "target" in request.values.keys() and request.values["target"] == "sd"; + sd = target == FileDestinations.SDCARD + selectAfterUpload = "select" in request.values.keys() and request.values["select"] in valid_boolean_trues + printAfterSelect = "print" in request.values.keys() and request.values["print"] in valid_boolean_trues + # determine current job currentFilename = None currentSd = None currentJob = printer.getCurrentJob() @@ -52,34 +53,52 @@ def uploadGcodeFile(): currentFilename = currentJob["filename"] currentSd = currentJob["sd"] + # determine future filename of file to be uploaded, abort if it can't be uploaded futureFilename = gcodeManager.getFutureFilename(file) if futureFilename is None or (not settings().getBoolean(["cura", "enabled"]) and not gcodefiles.isGcodeFileName(futureFilename)): return make_response("Can not upload file %s, wrong format?" % file.filename, 400) + # prohibit overwriting currently selected file while it's being printed 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 = None + + def fileProcessingFinished(filename, absFilename, destination): + """ + Callback for when the file processing (upload, optional slicing, addition to analysis queue) has + finished. + + Depending on the file's destination triggers either streaming to SD card or directly calls selectOrPrint. + """ + sd = destination == FileDestinations.SDCARD + if sd: + printer.addSdFile(filename, absFilename, selectAndOrPrint) + else: + selectAndOrPrint(absFilename, destination) + + def selectAndOrPrint(nameToSelect, destination): + """ + Callback for when the file is ready to be selected and optionally printed. For SD file uploads this only + the case after they have finished streaming to the printer, which is why this callback is also used + for the corresponding call to addSdFile. + + Selects the just uploaded file if either selectAfterUpload or printAfterSelect are True, or if the + exact file is already selected, such reloading it. + """ + sd = destination == FileDestinations.SDCARD + if selectAfterUpload or printAfterSelect or (currentFilename == filename and currentSd == sd): + printer.selectFile(nameToSelect, sd, printAfterSelect) + destination = FileDestinations.SDCARD if sd else FileDestinations.LOCAL - - filename, done = gcodeManager.addFile(file, destination) - + filename, done = gcodeManager.addFile(file, destination, fileProcessingFinished) 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) - eventManager.fire("Upload", filename) - return jsonify(files=gcodeManager.getAllFileData(), filename=filename, done=done) + return jsonify(files=gcodeManager.getAllFileData(), filename=filename, done=done) + else: + return make_response("No gcode_file included", 400) @ajax.route("/gcodefiles/load", methods=["POST"]) diff --git a/src/octoprint/static/js/app/main.js b/src/octoprint/static/js/app/main.js index 5779bbf4..ec9adf31 100644 --- a/src/octoprint/static/js/app/main.js +++ b/src/octoprint/static/js/app/main.js @@ -107,9 +107,9 @@ $(function() { function enable_local_dropzone() { $("#gcode_upload").fileupload({ + url: AJAX_BASEURL + "gcodefiles/local", dataType: "json", dropZone: localTarget, - formData: {target: "local"}, done: gcode_upload_done, fail: gcode_upload_fail, progressall: gcode_upload_progress @@ -118,9 +118,9 @@ $(function() { function disable_local_dropzone() { $("#gcode_upload").fileupload({ + url: AJAX_BASEURL + "gcodefiles/local", dataType: "json", dropZone: null, - formData: {target: "local"}, done: gcode_upload_done, fail: gcode_upload_fail, progressall: gcode_upload_progress @@ -129,9 +129,9 @@ $(function() { function enable_sd_dropzone() { $("#gcode_upload_sd").fileupload({ + url: AJAX_BASEURL + "gcodefiles/sdcard", dataType: "json", dropZone: $("#drop_sd"), - formData: {target: "sd"}, done: gcode_upload_done, fail: gcode_upload_fail, progressall: gcode_upload_progress @@ -140,6 +140,7 @@ $(function() { function disable_sd_dropzone() { $("#gcode_upload_sd").fileupload({ + url: AJAX_BASEURL + "gcodefiles/sdcard", dataType: "json", dropZone: null, formData: {target: "sd"}, diff --git a/src/octoprint/static/js/app/viewmodels/gcodefiles.js b/src/octoprint/static/js/app/viewmodels/gcodefiles.js index c14ad2a2..a643259b 100644 --- a/src/octoprint/static/js/app/viewmodels/gcodefiles.js +++ b/src/octoprint/static/js/app/viewmodels/gcodefiles.js @@ -43,10 +43,10 @@ function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel) { return !(file["prints"] && file["prints"]["success"] && file["prints"]["success"] > 0); }, "sd": function(file) { - return file["origin"] && file["origin"] == "sd"; + return file["origin"] && file["origin"] == "sdcard"; }, "local": function(file) { - return !(file["origin"] && file["origin"] == "sd"); + return !(file["origin"] && file["origin"] == "sdcard"); } }, "name",