diff --git a/src/octoprint/printer.py b/src/octoprint/printer.py index cd6f7103..e9bb8853 100644 --- a/src/octoprint/printer.py +++ b/src/octoprint/printer.py @@ -70,6 +70,7 @@ class Printer(): # sd handling self._sdPrinting = False self._sdStreaming = False + self._sdFilelistAvailable = threading.Event() self._selectedFile = None @@ -434,6 +435,7 @@ class Printer(): def mcSdFiles(self, files): self._sendTriggerUpdateCallbacks("gcodeFiles") + self._sdFilelistAvailable.set() def mcFileSelected(self, filename, filesize, sd): self._setJobData(filename, filesize, sd) @@ -467,15 +469,15 @@ class Printer(): #~~ sd file handling def getSdFiles(self): - if self._comm is None: - return + if self._comm is None or not self._comm.isSdReady(): + return [] return self._comm.getSdFiles() def addSdFile(self, filename, absolutePath): from octoprint.util import isGcodeFileName from octoprint.util import isSTLFileName - if not self._comm or self._comm.isBusy(): + if not self._comm or self._comm.isBusy() or not self._comm.isSdReady(): logging.error("No connection to printer or printer is busy") return @@ -492,32 +494,42 @@ class Printer(): absolutePath, callBack, callBackArgs) def streamSdFile(self, filename, path): - if not self._comm or self._comm.isBusy(): + if not self._comm or self._comm.isBusy() or not self._comm.isSdReady(): return - sdFilename = filename[:filename.find(".")].lower() - if len(sdFilename) > 8: - sdFilename = sdFilename[:8] - self._comm.startFileTransfer(path, sdFilename + ".gco") + + self.refreshSdFiles(blocking=True) + existingSdFiles = self._comm.getSdFiles() + + sdFilename = util.getDosFilename(filename, existingSdFiles) + self._comm.startFileTransfer(path, sdFilename) def deleteSdFile(self, filename): - if not self._comm: + if not self._comm or not self._comm.isSdReady(): return self._comm.deleteSdFile(filename) def initSdCard(self): - if not self._comm: + if not self._comm or self._comm.isSdReady(): return self._comm.initSdCard() def releaseSdCard(self): - if not self._comm: + if not self._comm or not self._comm.isSdReady(): return self._comm.releaseSdCard() - def refreshSdFiles(self): - if not self._comm: + def refreshSdFiles(self, blocking=False): + """ + Refreshs the list of file stored on the SD card attached to printer (if available and printer communication + available). Optional blocking parameter allows making the method block (max 10s) until the file list has been + received (and can be accessed via self._comm.getSdFiles()). Defaults to a asynchronous operation. + """ + if not self._comm or not self._comm.isSdReady(): return + self._sdFilelistAvailable.clear() self._comm.refreshSdFiles() + if blocking: + self._sdFilelistAvailable.wait(10000) #~~ state reports diff --git a/src/octoprint/server.py b/src/octoprint/server.py index c4a6cbcc..4d8f6334 100644 --- a/src/octoprint/server.py +++ b/src/octoprint/server.py @@ -471,7 +471,7 @@ def deleteGcodeFile(): printer.unselectFile() if not (currentFilename == filename and currentSd == sd and (printer.isPrinting() or printer.isPaused())): - if currentSd: + if sd: printer.deleteSdFile(filename) else: gcodeManager.removeFile(filename) diff --git a/src/octoprint/static/js/app/main.js b/src/octoprint/static/js/app/main.js index afa168c7..e4cd786e 100644 --- a/src/octoprint/static/js/app/main.js +++ b/src/octoprint/static/js/app/main.js @@ -84,23 +84,29 @@ $(function() { } } - var localTarget; - if (CONFIG_SD_SUPPORT) { - localTarget = $("#drop_locally"); - } else { - localTarget = $("#drop"); + function enable_local_dropzone() { + $("#gcode_upload").fileupload({ + dataType: "json", + dropZone: localTarget, + formData: {target: "local"}, + done: gcode_upload_done, + fail: gcode_upload_fail, + progressall: gcode_upload_progress + }); } - $("#gcode_upload").fileupload({ - dataType: "json", - dropZone: localTarget, - formData: {target: "local"}, - done: gcode_upload_done, - fail: gcode_upload_fail, - progressall: gcode_upload_progress - }); + function disable_local_dropzone() { + $("#gcode_upload").fileupload({ + dataType: "json", + dropZone: null, + formData: {target: "local"}, + done: gcode_upload_done, + fail: gcode_upload_fail, + progressall: gcode_upload_progress + }); + } - if (CONFIG_SD_SUPPORT) { + function enable_sd_dropzone() { $("#gcode_upload_sd").fileupload({ dataType: "json", dropZone: $("#drop_sd"), @@ -111,6 +117,62 @@ $(function() { }); } + function disable_sd_dropzone() { + $("#gcode_upload_sd").fileupload({ + dataType: "json", + dropZone: null, + formData: {target: "sd"}, + done: gcode_upload_done, + fail: gcode_upload_fail, + progressall: gcode_upload_progress + }); + } + + var localTarget; + if (CONFIG_SD_SUPPORT) { + localTarget = $("#drop_locally"); + } else { + localTarget = $("#drop"); + } + + loginStateViewModel.isUser.subscribe(function(newValue) { + if (newValue === true) { + enable_local_dropzone(); + } else { + disable_local_dropzone(); + } + }); + + if (loginStateViewModel.isUser()) { + enable_local_dropzone(); + } else { + disable_local_dropzone(); + } + + if (CONFIG_SD_SUPPORT) { + printerStateViewModel.isSdReady.subscribe(function(newValue) { + if (newValue === true && loginStateViewModel.isUser()) { + enable_sd_dropzone(); + } else { + disable_sd_dropzone(); + } + }); + + loginStateViewModel.isUser.subscribe(function(newValue) { + if (newValue === true && printerStateViewModel.isSdReady()) { + enable_sd_dropzone(); + } else { + disable_sd_dropzone(); + } + }); + + if (printerStateViewModel.isSdReady() && loginStateViewModel.isUser()) { + enable_sd_dropzone(); + } else { + disable_sd_dropzone(); + } + } + $(document).bind("dragover", function (e) { var dropOverlay = $("#drop_overlay"); var dropZone = $("#drop"); @@ -148,7 +210,7 @@ $(function() { if (foundLocal) { dropZoneLocalBackground.addClass("hover"); dropZoneSdBackground.removeClass("hover"); - } else if (foundSd) { + } else if (foundSd && printerStateViewModel.isSdReady()) { dropZoneSdBackground.addClass("hover"); dropZoneLocalBackground.removeClass("hover"); } else if (found) { @@ -203,7 +265,7 @@ $(function() { ko.applyBindings(settingsViewModel, document.getElementById("settings_dialog")); ko.applyBindings(navigationViewModel, document.getElementById("navbar")); ko.applyBindings(appearanceViewModel, document.getElementsByTagName("head")[0]); - ko.applyBindings(loginStateViewModel, document.getElementById("drop_overlay")); + ko.applyBindings(printerStateViewModel, document.getElementById("drop_overlay")); var timelapseElement = document.getElementById("timelapse"); if (timelapseElement) { diff --git a/src/octoprint/templates/dialogs.jinja2 b/src/octoprint/templates/dialogs.jinja2 index ca28988e..97a3ea09 100644 --- a/src/octoprint/templates/dialogs.jinja2 +++ b/src/octoprint/templates/dialogs.jinja2 @@ -17,13 +17,13 @@ -
+
{% if enableSdSupport %}

Upload locally
-

Upload to SD
+

Upload to SD
(SD not initialized)
{% else %}

Upload
diff --git a/src/octoprint/templates/index.jinja2 b/src/octoprint/templates/index.jinja2 index 320252a3..8ffec32d 100644 --- a/src/octoprint/templates/index.jinja2 +++ b/src/octoprint/templates/index.jinja2 @@ -211,10 +211,10 @@ Upload - + Upload to SD - + {% else %} diff --git a/src/octoprint/util/__init__.py b/src/octoprint/util/__init__.py index 48b3a55d..77e2e4bd 100644 --- a/src/octoprint/util/__init__.py +++ b/src/octoprint/util/__init__.py @@ -6,6 +6,7 @@ import os import traceback import sys import time +import re from octoprint.settings import settings @@ -151,3 +152,30 @@ def getRemoteAddress(request): if forwardedFor is not None: return forwardedFor.split(",")[0] return request.remote_addr + + +def getDosFilename(input, existingFilenames, extension=None): + if input is None: + return None + + if extension is None: + extension = "gco" + + filename, ext = input.rsplit(".", 1) + return findCollisionfreeName(filename, extension, existingFilenames) + + +def findCollisionfreeName(input, extension, existingFilenames): + filename = re.sub(r"\s+", "_", input.lower().translate(None, ".\"/\\[]:;=,")) + + counter = 1 + power = 1 + while counter < (10 * power): + result = filename[:(6 - power + 1)] + "~" + str(counter) + "." + extension + if result not in existingFilenames: + return result + counter += 1 + if counter == 10 * power: + power += 1 + + raise ValueError("Can't create a collision free filename") diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 200a08da..6274c2ea 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -611,6 +611,7 @@ class MachineCom(object): # anwer to M28, at least on Marlin, Repetier and Sprinter: "Writing to file: %s" self._printSection = "CUSTOM" self._changeState(self.STATE_PRINTING) + line = "ok" elif 'Done printing file' in line: # printer is reporting file finished printing self._sdFilePos = 0 @@ -645,7 +646,7 @@ class MachineCom(object): pass ##~~ Parsing for pause triggers - if pauseTriggers: + if pauseTriggers and not self.isStreaming(): if "enable" in pauseTriggers.keys() and pauseTriggers["enable"].search(line) is not None: self.setPause(True) elif "disable" in pauseTriggers.keys() and pauseTriggers["disable"].search(line) is not None: @@ -804,6 +805,7 @@ class MachineCom(object): self._callback.mcFileTransferDone() self._changeState(self.STATE_OPERATIONAL) eventManager().fire("TransferDone", filename) + self.refreshSdFiles() else: self._callback.mcPrintjobDone() self._changeState(self.STATE_OPERATIONAL) @@ -1162,4 +1164,4 @@ class PrintingGcodeFileInformation(PrintingFileInformation): class StreamingGcodeFileInformation(PrintingGcodeFileInformation): def __init__(self, filename): - PrintingGcodeFileInformation.__init__(self, filename, None) + PrintingGcodeFileInformation.__init__(self, filename, None) \ No newline at end of file diff --git a/src/octoprint/util/virtual.py b/src/octoprint/util/virtual.py index 6a445593..cc70578a 100644 --- a/src/octoprint/util/virtual.py +++ b/src/octoprint/util/virtual.py @@ -76,6 +76,7 @@ class VirtualPrinter(): return else: self.lastN = linenumber + data = data.split(None, 1)[1].strip() data += "\n"