mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-08 04:08:56 -05:00
[DataIO] Atomically save json (#372)
Should put an end to the corruption issues
This commit is contained in:
parent
f95b7c1fee
commit
60f4a16828
@ -1,48 +1,45 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from shutil import copy
|
from random import randint
|
||||||
|
|
||||||
class InvalidFileIO(Exception):
|
class InvalidFileIO(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class CorruptedJSON(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class DataIO():
|
class DataIO():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.logger = logging.getLogger("red")
|
self.logger = logging.getLogger("red")
|
||||||
|
|
||||||
def save_json(self, filename, data):
|
def save_json(self, filename, data):
|
||||||
"""Saves and backups json file"""
|
"""Atomically saves json file"""
|
||||||
bak_file = os.path.splitext(filename)[0]+'.bak'
|
rnd = randint(1000, 9999)
|
||||||
self._save_json(filename, data)
|
path, ext = os.path.splitext(filename)
|
||||||
copy(filename, bak_file) # Backup copy
|
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):
|
def load_json(self, filename):
|
||||||
"""Loads json file and restores backup copy in case of corrupted file"""
|
"""Loads json file"""
|
||||||
try:
|
|
||||||
return self._read_json(filename)
|
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))
|
|
||||||
|
|
||||||
def is_valid_json(self, filename):
|
def is_valid_json(self, filename):
|
||||||
"""Returns True if readable json file, False if not existing.
|
"""Verifies if json file exists / is readable"""
|
||||||
Tries to restore backup copy if corrupted"""
|
|
||||||
try:
|
try:
|
||||||
data = self._read_json(filename)
|
self._read_json(filename)
|
||||||
|
return True
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return False
|
return False
|
||||||
except json.decoder.JSONDecodeError:
|
except json.decoder.JSONDecodeError:
|
||||||
result = self._restore_json(filename)
|
return False
|
||||||
return result # If False, no backup copy, might as well
|
|
||||||
else: # allow the overwrite
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _read_json(self, filename):
|
def _read_json(self, filename):
|
||||||
with open(filename, encoding='utf-8', mode="r") as f:
|
with open(filename, encoding='utf-8', mode="r") as f:
|
||||||
@ -55,18 +52,6 @@ class DataIO():
|
|||||||
separators=(',',' : '))
|
separators=(',',' : '))
|
||||||
return data
|
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):
|
def _legacy_fileio(self, filename, IO, data=None):
|
||||||
"""Old fileIO provided for backwards compatibility"""
|
"""Old fileIO provided for backwards compatibility"""
|
||||||
if IO == "save" and data != None:
|
if IO == "save" and data != None:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user