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)
This commit is contained in:
Gina Häußge 2014-12-20 01:06:40 +01:00
parent 0737ee65bd
commit 256c2dfdbe
2 changed files with 19 additions and 10 deletions

View file

@ -9,9 +9,12 @@ __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms
import logging import logging
import os import os
import pylru import pylru
import tempfile
import octoprint.filemanager import octoprint.filemanager
from octoprint.util import safeRename
class StorageInterface(object): class StorageInterface(object):
@ -867,12 +870,16 @@ class LocalFileStorage(StorageInterface):
def _save_metadata(self, path, metadata): def _save_metadata(self, path, metadata):
metadata_path = os.path.join(path, ".metadata.yaml") metadata_path = os.path.join(path, ".metadata.yaml")
fh, metadata_temporary_path = tempfile.mkstemp()
os.close(fh)
with self._metadata_lock: with self._metadata_lock:
with open(metadata_path, "w") as f: try:
try: with open(metadata_temporary_path, "w") as f:
import yaml import yaml
yaml.safe_dump(metadata, stream=f, default_flow_style=False, indent=" ", allow_unicode=True) yaml.safe_dump(metadata, stream=f, default_flow_style=False, indent=" ", allow_unicode=True)
except: safeRename(metadata_temporary_path, metadata_path, throw_error=True)
self._logger.exception("Error while writing .metadata.yaml to {path}".format(**locals())) except:
else: self._logger.exception("Error while writing .metadata.yaml to {path}".format(**locals()))
self._metadata_cache[path] = metadata else:
self._metadata_cache[path] = metadata

View file

@ -147,7 +147,7 @@ def findCollisionfreeName(input, extension, existingFilenames):
raise ValueError("Can't create a collision free filename") raise ValueError("Can't create a collision free filename")
def safeRename(old, new): def safeRename(old, new, throw_error=False):
""" """
Safely renames a file. Safely renames a file.
@ -172,12 +172,14 @@ def safeRename(old, new):
os.rename(new, backup) os.rename(new, backup)
os.rename(old, new) os.rename(old, new)
os.remove(backup) os.remove(backup)
except OSError: except OSError as e:
# if anything went wrong, try to rename the backup file to its original name # if anything went wrong, try to rename the backup file to its original name
logger.error("Could not perform safe rename, trying to revert") logger.error("Could not perform safe rename, trying to revert")
if os.path.exists(backup): if os.path.exists(backup):
os.remove(new) silentRemove(new)
os.rename(backup, new) os.rename(backup, new)
if throw_error:
raise e
else: else:
# on anything else than windows it's ooooh so much easier... # on anything else than windows it's ooooh so much easier...
os.rename(old, new) os.rename(old, new)