From 4d2213544ee780eecfd3dc120332242a5071a61f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 15 Nov 2013 20:54:13 +0100 Subject: [PATCH] Made metadata saving for gcode files more error resilient (hopefully that is) --- src/octoprint/gcodefiles.py | 9 ++++--- src/octoprint/util/__init__.py | 49 ++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/octoprint/gcodefiles.py b/src/octoprint/gcodefiles.py index 8894914f..42e1bdf8 100644 --- a/src/octoprint/gcodefiles.py +++ b/src/octoprint/gcodefiles.py @@ -70,6 +70,7 @@ class GcodeManager: self._metadata = {} self._metadataDirty = False self._metadataFile = os.path.join(self._uploadFolder, "metadata.yaml") + self._metadataTempFile = os.path.join(self._uploadFolder, "metadata.yaml.tmp") self._metadataFileAccessMutex = threading.Lock() self._metadataAnalyzer = MetadataAnalyzer(getPathCallback=self.getAbsolutePath, loadedCallback=self._onMetadataAnalysisFinished) @@ -111,7 +112,7 @@ class GcodeManager: if gcode.extrusionAmount: analysisResult["filament"] = "%.2fm" % (gcode.extrusionAmount / 1000) if gcode.calculateVolumeCm3(): - analysisResult["filament"] += " / %.2fcm³" % gcode.calculateVolumeCm3() + analysisResult["filament"] += " / %.2fcm³" % gcode.calculateVolumeCm3() dirty = True if dirty: @@ -134,9 +135,11 @@ class GcodeManager: return with self._metadataFileAccessMutex: - with open(self._metadataFile, "wb") as f: + with open(self._metadataTempFile, "wb") as f: yaml.safe_dump(self._metadata, f, default_flow_style=False, indent=" ", allow_unicode=True) self._metadataDirty = False + util.safeRename(self._metadataTempFile, self._metadataFile) + self._loadMetadata() self._sendUpdateTrigger("gcodeFiles") @@ -326,7 +329,7 @@ class GcodeManager: if key == "prints": val = self._metadata[filename][key] formattedLast = None - if val["last"] is not None: + if "last" in val and val["last"] is not None: formattedLast = { "date": util.getFormattedDateTime(datetime.datetime.fromtimestamp(val["last"]["date"])), "success": val["last"]["success"] diff --git a/src/octoprint/util/__init__.py b/src/octoprint/util/__init__.py index 5b102195..aa8b86f6 100644 --- a/src/octoprint/util/__init__.py +++ b/src/octoprint/util/__init__.py @@ -7,6 +7,7 @@ import traceback import sys import time import re +import tempfile from octoprint.settings import settings @@ -139,3 +140,51 @@ def findCollisionfreeName(input, extension, existingFilenames): power += 1 raise ValueError("Can't create a collision free filename") + + +def safeRename(old, new): + """ + Safely renames a file. + + On Windows this is achieved by first creating a backup file of the new file (if it + already exists), thus moving it, then renaming the old into the new file and finally removing the backup. If + anything goes wrong during those steps, the backup (if already there) will be renamed to its old name and thus + the operation hopefully result in a no-op. + + On other operating systems the atomic os.rename function will be used instead. + + @param old the path to the old file to be renamed + @param new the path to the new file to be created/replaced + """ + + if sys.platform == "win32": + fh, backup = tempfile.mkstemp() + os.close(fh) + + try: + if os.path.exists(new): + silentRemove(backup) + os.rename(new, backup) + os.rename(old, new) + os.remove(backup) + except OSError: + # if anything went wrong, try to rename the backup file to its original name + if os.path.exists(backup): + os.remove(new) + os.rename(backup, new) + else: + # on anything else than windows it's ooooh so much easier... + os.rename(old, new) + + +def silentRemove(file): + """ + Silently removes a file. Does not raise an error if the file doesn't exist. + + @param file the path of the file to be removed + """ + + try: + os.remove(file) + except OSError: + pass