[DataIO] Atomically save json (#372)

Should put an end to the corruption issues
This commit is contained in:
Twentysix 2016-09-13 00:33:37 +02:00 committed by GitHub
parent f95b7c1fee
commit 60f4a16828

View File

@ -1,48 +1,45 @@
import json
import os
import logging
from shutil import copy
from random import randint
class InvalidFileIO(Exception):
pass
class CorruptedJSON(Exception):
pass
class DataIO():
def __init__(self):
self.logger = logging.getLogger("red")
def save_json(self, filename, data):
"""Saves and backups json file"""
bak_file = os.path.splitext(filename)[0]+'.bak'
self._save_json(filename, data)
copy(filename, bak_file) # Backup copy
"""Atomically saves json file"""
rnd = randint(1000, 9999)
path, ext = os.path.splitext(filename)
tmp_file = "{}-{}.tmp".format(path, rnd)
self._save_json(tmp_file, data)
try:
self._read_json(tmp_file)
except json.decoder.JSONDecodeError:
self.logger.exception("Attempted to write file {} but JSON "
"integrity check on tmp file has failed. "
"The original file is unaltered."
"".format(filename))
return False
os.replace(tmp_file, filename)
return True
def load_json(self, filename):
"""Loads json file and restores backup copy in case of corrupted file"""
try:
return self._read_json(filename)
except json.decoder.JSONDecodeError:
result = self._restore_json(filename)
if result:
return self._read_json(filename) # Which hopefully will work
else:
raise CorruptedJSON("{} is corrupted and no backup copy is"
" available.".format(filename))
"""Loads json file"""
return self._read_json(filename)
def is_valid_json(self, filename):
"""Returns True if readable json file, False if not existing.
Tries to restore backup copy if corrupted"""
"""Verifies if json file exists / is readable"""
try:
data = self._read_json(filename)
self._read_json(filename)
return True
except FileNotFoundError:
return False
except json.decoder.JSONDecodeError:
result = self._restore_json(filename)
return result # If False, no backup copy, might as well
else: # allow the overwrite
return True
return False
def _read_json(self, filename):
with open(filename, encoding='utf-8', mode="r") as f:
@ -55,18 +52,6 @@ class DataIO():
separators=(',',' : '))
return data
def _restore_json(self, filename):
bak_file = os.path.splitext(filename)[0]+'.bak'
if os.path.isfile(bak_file):
copy(bak_file, filename) # Restore last working copy
self.logger.warning("{} was corrupted. Restored "
"backup copy.".format(filename))
return True
else:
self.logger.critical("{} is corrupted and there is no "
"backup copy available.".format(filename))
return False
def _legacy_fileio(self, filename, IO, data=None):
"""Old fileIO provided for backwards compatibility"""
if IO == "save" and data != None: