diff --git a/.gitignore b/.gitignore index 0dc31bc..0685502 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ __pycache__ /*.tmp /Twitch Drops Miner # Dev files +*.new /env /cache /*.jar diff --git a/utils.py b/utils.py index bfe5e93..7c8c8bf 100644 --- a/utils.py +++ b/utils.py @@ -242,20 +242,33 @@ def merge_json(obj: JsonType, template: Mapping[Any, Any]) -> None: def json_load(path: Path, defaults: _JSON_T, *, merge: bool = True) -> _JSON_T: - defaults_dict: JsonType = dict(defaults) - if path.exists(): - with open(path, 'r', encoding="utf8") as file: - combined: JsonType = _remove_missing(json.load(file, object_hook=_deserialize)) - if merge: - merge_json(combined, defaults_dict) - else: - combined = defaults_dict + new_path: Path = path.with_name(f"{path.name}.new") + combined: JsonType | None = None + # try new file first + if new_path.exists(): + try: + with new_path.open('r', encoding="utf8") as file: + combined = _remove_missing(json.load(file, object_hook=_deserialize)) + except json.JSONDecodeError: + # remove invalid file + new_path.unlink() + # try the old file + if combined is None and path.exists(): + with path.open('r', encoding="utf8") as file: + combined = _remove_missing(json.load(file, object_hook=_deserialize)) + # handle defaults and merging + if combined is None: + combined = dict(defaults) # always make a copy of defaults + elif merge: + merge_json(combined, dict(defaults)) return cast(_JSON_T, combined) def json_save(path: Path, contents: Mapping[Any, Any], *, sort: bool = False) -> None: - with open(path, 'w', encoding="utf8") as file: + new_path: Path = path.with_name(f"{path.name}.new") + with new_path.open('w', encoding="utf8") as file: json.dump(contents, file, default=_serialize, sort_keys=sort, indent=4) + new_path.replace(path) def webopen(url: URL | str):