From 256c2dfdbe49928e5f83f8bd1fdfa75a38c8a59c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Sat, 20 Dec 2014 01:06:40 +0100 Subject: [PATCH] More error resilient handling of .metadata.yaml writes in local storage Writes updated metadata to a backup file first and then copies it atomically (at least under anything that is not windows where the operation is a bit more complicated and hence not atomic) --- src/octoprint/filemanager/storage.py | 19 +++++++++++++------ src/octoprint/util/__init__.py | 10 ++++++---- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/octoprint/filemanager/storage.py b/src/octoprint/filemanager/storage.py index 54d13a43..b2cf6142 100644 --- a/src/octoprint/filemanager/storage.py +++ b/src/octoprint/filemanager/storage.py @@ -9,9 +9,12 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms import logging import os import pylru +import tempfile import octoprint.filemanager +from octoprint.util import safeRename + class StorageInterface(object): @@ -867,12 +870,16 @@ class LocalFileStorage(StorageInterface): def _save_metadata(self, path, metadata): metadata_path = os.path.join(path, ".metadata.yaml") + fh, metadata_temporary_path = tempfile.mkstemp() + os.close(fh) + with self._metadata_lock: - with open(metadata_path, "w") as f: - try: + try: + with open(metadata_temporary_path, "w") as f: import yaml yaml.safe_dump(metadata, stream=f, default_flow_style=False, indent=" ", allow_unicode=True) - except: - self._logger.exception("Error while writing .metadata.yaml to {path}".format(**locals())) - else: - self._metadata_cache[path] = metadata + safeRename(metadata_temporary_path, metadata_path, throw_error=True) + except: + self._logger.exception("Error while writing .metadata.yaml to {path}".format(**locals())) + else: + self._metadata_cache[path] = metadata diff --git a/src/octoprint/util/__init__.py b/src/octoprint/util/__init__.py index 8f6b1537..2c7b72bd 100644 --- a/src/octoprint/util/__init__.py +++ b/src/octoprint/util/__init__.py @@ -147,7 +147,7 @@ def findCollisionfreeName(input, extension, existingFilenames): raise ValueError("Can't create a collision free filename") -def safeRename(old, new): +def safeRename(old, new, throw_error=False): """ Safely renames a file. @@ -172,12 +172,14 @@ def safeRename(old, new): os.rename(new, backup) os.rename(old, new) os.remove(backup) - except OSError: + except OSError as e: # if anything went wrong, try to rename the backup file to its original name logger.error("Could not perform safe rename, trying to revert") if os.path.exists(backup): - os.remove(new) - os.rename(backup, new) + silentRemove(new) + os.rename(backup, new) + if throw_error: + raise e else: # on anything else than windows it's ooooh so much easier... os.rename(old, new)