Skip to content

Commit

Permalink
Save configuration profiles and gesture maps as an atomic operation (…
Browse files Browse the repository at this point in the history
…refs issue nvaccess#3165)

Instead of directly writing the file, a temporary file is created and
moved to the final location. This prevents corrupt configuration files
being written.
Added a fileUtils module with just a FaultTolerantFile context manager for
now to facilitate this pattern.
Please update miscDependencies submodule to a revision that includes the
osmove module.
  • Loading branch information
bramd committed Apr 30, 2016
1 parent 69e86e8 commit 7fd86a9
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 5 deletions.
2 changes: 1 addition & 1 deletion miscDeps
Submodule miscDeps updated from 48109d to e7db10
10 changes: 7 additions & 3 deletions source/config/__init__.py
Expand Up @@ -6,6 +6,7 @@
import ctypes
import ctypes.wintypes
import os
import osreplace
import sys
from cStringIO import StringIO
import itertools
Expand All @@ -17,6 +18,7 @@
import shlobj
import baseObject
import easeOfAccess
from fileUtils import FaultTolerantFile
import winKernel

def validateConfig(configObj,validator,validationResult=None,keyList=None):
Expand Down Expand Up @@ -638,10 +640,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:
Expand Down Expand Up @@ -738,7 +742,7 @@ def renameProfile(self, oldName, newName):
if oldName.lower() != newName.lower() and os.path.isfile(newFn):
raise ValueError("A profile with the same name already exists: %s" % newName)

os.rename(oldFn, newFn)
osreplace.replace(oldFn, newFn)
# Update any associated triggers.
allTriggers = self.triggersToProfiles
saveTrigs = False
Expand Down
16 changes: 16 additions & 0 deletions source/fileUtils.py
@@ -0,0 +1,16 @@
import os
import osreplace
from contextlib import contextmanager
from tempfile import NamedTemporaryFile
from logHandler import log

@contextmanager
def FaultTolerantFile(name):
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()
osreplace.replace(f.name, name)
4 changes: 3 additions & 1 deletion source/inputCore.py
Expand Up @@ -23,6 +23,7 @@
import speech
import characterProcessing
import config
from fileUtils import FaultTolerantFile
import watchdog
from logHandler import log
import globalVars
Expand Down Expand Up @@ -359,7 +360,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.
Expand Down

0 comments on commit 7fd86a9

Please sign in to comment.