diff --git a/src/octoprint/printer/profile.py b/src/octoprint/printer/profile.py index 06560d74..88bdfe3e 100644 --- a/src/octoprint/printer/profile.py +++ b/src/octoprint/printer/profile.py @@ -329,12 +329,14 @@ class PrinterProfileManager(object): if os.path.exists(path) and not allow_overwrite: raise SaveError("Profile %s already exists and not allowed to overwrite" % profile["id"]) + from octoprint.util import atomic_write import yaml - with open(path, "wb") as f: - try: + try: + with atomic_write(path, "wb") as f: yaml.safe_dump(profile, f, default_flow_style=False, indent=" ", allow_unicode=True) - except Exception as e: - raise SaveError("Cannot save profile %s: %s" % (profile["id"], str(e))) + except Exception as e: + self._logger.exception("Error while trying to save profile %s" % profile["id"]) + raise SaveError("Cannot save profile %s: %s" % (profile["id"], str(e))) def _remove_from_path(self, path): try: diff --git a/src/octoprint/settings.py b/src/octoprint/settings.py index f5fd85a2..a21b7f85 100644 --- a/src/octoprint/settings.py +++ b/src/octoprint/settings.py @@ -774,11 +774,17 @@ class Settings(object): if not self._dirty and not force: return False - with open(self._configfile, "wb") as configFile: - yaml.safe_dump(self._config, configFile, default_flow_style=False, indent=" ", allow_unicode=True) - self._dirty = False - self.load() - return True + from octoprint.util import atomic_write + try: + with atomic_write(self._configfile, "wb", prefix="octoprint-config-", suffix=".yaml") as configFile: + yaml.safe_dump(self._config, configFile, default_flow_style=False, indent=" ", allow_unicode=True) + self._dirty = False + except: + self._logger.exception("Error while saving config.yaml!") + raise + else: + self.load() + return True @property def last_modified(self): diff --git a/src/octoprint/util/__init__.py b/src/octoprint/util/__init__.py index 7d799fc2..ad5f8038 100644 --- a/src/octoprint/util/__init__.py +++ b/src/octoprint/util/__init__.py @@ -17,6 +17,7 @@ import shutil import threading from functools import wraps import warnings +import contextlib logger = logging.getLogger(__name__) @@ -513,6 +514,15 @@ def address_for_client(host, port): except: continue + +@contextlib.contextmanager +def atomic_write(filename, mode="w+b", prefix="tmp", suffix=""): + temp_config = tempfile.NamedTemporaryFile(mode=mode, prefix=prefix, suffix=suffix, delete=False) + yield temp_config + temp_config.close() + shutil.move(temp_config.name, filename) + + class RepeatedTimer(threading.Thread): """ This class represents an action that should be run repeatedly in an interval. It is similar to python's