diff --git a/source/config/__init__.py b/source/config/__init__.py index 97c2c055c5f..743625d53ed 100644 --- a/source/config/__init__.py +++ b/source/config/__init__.py @@ -25,6 +25,7 @@ import shlobj import baseObject import easeOfAccess +from fileUtils import FaultTolerantFile import winKernel import profileUpgrader from .configSpec import confspec @@ -498,10 +499,12 @@ def save(self): # Never save the config if running securely. return try: - self.profiles[0].write() + with FaultTolerantFile(self.profiles[0].filename) as f: + self.profiles[0].write(f) log.info("Base configuration saved") for name in self._dirtyProfiles: - self._profileCache[name].write() + with FaultTolerantFile(self._profileCache[name].filename) as f: + self._profileCache[name].write(f) log.info("Saved configuration profile %s" % name) self._dirtyProfiles.clear() except Exception as e: diff --git a/source/fileUtils.py b/source/fileUtils.py new file mode 100644 index 00000000000..cb34679af60 --- /dev/null +++ b/source/fileUtils.py @@ -0,0 +1,38 @@ +#fileUtils.py +#A part of NonVisual Desktop Access (NVDA) +#Copyright (C) 2017 NV Access Limited, Bram Duvigneau +#This file is covered by the GNU General Public License. +#See the file COPYING for more details. +import os +import ctypes +from contextlib import contextmanager +from tempfile import NamedTemporaryFile +from logHandler import log + +#: Constant; flag for MoveFileEx(). If a file with the destination filename already exists, it is overwritten. +MOVEFILE_REPLACE_EXISTING = 1 + +@contextmanager +def FaultTolerantFile(name): + '''Used to write out files in a more fault tolerant way. A temporary file is used, and replaces the + file `name' when the context manager scope ends and the the context manager __exit__ is called. This + means writing out the complete file can be performed with less concern of corrupting the original file + if the process is interrupted by windows shutting down. + + Usage: + with FaultTolerantFile("myFile.txt") as f: + f.write("This is a test") + + This creates a temporary file, and the writes actually happen on this temp file. At the end of the + `with` block, when `f` goes out of context the temporary file is closed and, this temporary file replaces "myFile.txt" + ''' + dirpath, filename = os.path.split(name) + with NamedTemporaryFile(dir=dirpath, prefix=filename, suffix='.tmp', delete=False) as f: + log.debug(f.name) + yield f + f.flush() + os.fsync(f) + f.close() + moveFileResult = ctypes.windll.kernel32.MoveFileExW(f.name, name, MOVEFILE_REPLACE_EXISTING) + if moveFileResult == 0: + raise ctypes.WinError() diff --git a/source/inputCore.py b/source/inputCore.py index a3b37420400..8b815b0f44c 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -23,6 +23,7 @@ import speech import characterProcessing import config +from fileUtils import FaultTolerantFile import watchdog from logHandler import log import globalVars @@ -360,7 +361,8 @@ def save(self): else: outSect[script] = [outVal, gesture] - out.write() + with FaultTolerantFile(out.filename) as f: + out.write(f) class InputManager(baseObject.AutoPropertyObject): """Manages functionality related to input from the user.