From a01c4bba50cfee5bcd799481ed4e43977c3410da Mon Sep 17 00:00:00 2001 From: Rhet Turnbull Date: Sat, 16 Jul 2022 18:33:16 -0400 Subject: [PATCH] Fixed backup database to be conformant JSON, #56 --- dev_requirements.txt | 4 +-- osxmetadata/_version.py | 2 +- osxmetadata/backup.py | 66 ++++++++++++++++++++++++++------------ osxmetadata/classes.py | 2 +- osxmetadata/osxmetadata.py | 6 ++-- 5 files changed, 52 insertions(+), 28 deletions(-) diff --git a/dev_requirements.txt b/dev_requirements.txt index 1af2b79..4a7e571 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -1,2 +1,2 @@ -pytest==6.2.4 -pyinstaller==4.3 +pytest==7.1.2 +pyinstaller==5.2 diff --git a/osxmetadata/_version.py b/osxmetadata/_version.py index dbaedb4..475805c 100644 --- a/osxmetadata/_version.py +++ b/osxmetadata/_version.py @@ -1,3 +1,3 @@ """ osxmetadata version """ -__version__ = "0.99.37" +__version__ = "0.99.38" diff --git a/osxmetadata/backup.py b/osxmetadata/backup.py index 3d95930..270e795 100644 --- a/osxmetadata/backup.py +++ b/osxmetadata/backup.py @@ -1,39 +1,63 @@ +""" Functions for writing and loading backup files """ + import json import logging import os +from enum import Enum -""" Functions for writing and loading backup files """ + +class BackupDatabaseType(Enum): + SINGLE_RECORD_JSON = 1 + JSON = 2 + + +def backup_database_type(path: str) -> BackupDatabaseType: + """Return BackupDatabaseType enum indicating type of backup file for path""" + with open(path) as fp: + line = fp.readline() + if not line: + raise ValueError("Unknown backup file type") + ch = line[0] + if ch == "{": + return BackupDatabaseType.SINGLE_RECORD_JSON + elif ch == "[": + return BackupDatabaseType.JSON + else: + raise ValueError("Unknown backup file type") def write_backup_file(backup_file, backup_data): - """ Write backup_data to backup_file as JSON - backup_data: dict where key is filename and value is dict of the attributes - as returned by json.loads(OSXMetaData.to_json()) """ + """Write backup_data to backup_file as JSON + backup_data: dict where key is filename and value is dict of the attributes + as returned by json.loads(OSXMetaData.to_json())""" with open(backup_file, mode="w") as fp: - for record in backup_data.values(): - json_str = json.dumps(record) - fp.write(json_str) - fp.write("\n") + json.dump(list(backup_data.values()), fp) def load_backup_file(backup_file): - """ Load attribute data from JSON in backup_file - Returns: backup_data dict """ + """Load attribute data from JSON in backup_file + Returns: backup_data dict""" if not os.path.isfile(backup_file): raise FileNotFoundError(f"Could not find backup file: {backup_file}") - backup_data = {} - with open(backup_file, mode="r") as fp: - for line in fp: - data = json.loads(line) - fname = data["_filename"] - if fname in backup_data: - logging.warning( - f"WARNING: duplicate filename {fname} found in {backup_file}" - ) - - backup_data[fname] = data + if backup_database_type(backup_file) == BackupDatabaseType.SINGLE_RECORD_JSON: + # old style single record of json per file + backup_data = {} + with open(backup_file, mode="r") as fp: + for line in fp: + data = json.loads(line) + fname = data["_filename"] + if fname in backup_data: + logging.warning( + f"WARNING: duplicate filename {fname} found in {backup_file}" + ) + + backup_data[fname] = data + else: + with open(backup_file, mode="r") as fp: + backup_records = json.load(fp) + backup_data = {data["_filename"]: data for data in backup_records} return backup_data diff --git a/osxmetadata/classes.py b/osxmetadata/classes.py index d8c71ea..835e3b5 100644 --- a/osxmetadata/classes.py +++ b/osxmetadata/classes.py @@ -557,4 +557,4 @@ def set_value(self, value): def __len__(self): self._load_data() - return self.data.__len__() if self.data else 0 \ No newline at end of file + return self.data.__len__() if self.data else 0 diff --git a/osxmetadata/osxmetadata.py b/osxmetadata/osxmetadata.py index 031779d..2b3b853 100644 --- a/osxmetadata/osxmetadata.py +++ b/osxmetadata/osxmetadata.py @@ -18,7 +18,7 @@ from ._version import __version__ from .attributes import ATTRIBUTES, Attribute, validate_attribute_value -from .classes import _AttributeOSXPhotosDetectedText, _AttributeList, _AttributeTagsList +from .classes import _AttributeList, _AttributeOSXPhotosDetectedText, _AttributeTagsList from .constants import ( _FINDER_COMMENT_NAMES, FINDER_COLOR_NONE, @@ -297,12 +297,12 @@ def get_attribute_str(self, attribute_name): e.g. if attribute is a datedate.datetime object, will format using datetime.isoformat() attribute_name: name of attribute""" - + attribute = ATTRIBUTES[attribute_name] value = self.get_attribute(attribute_name) if attribute.class_ == _AttributeOSXPhotosDetectedText: return json.dumps(value) - + if type(value) in [list, set]: if value: if type(value[0]) == datetime.datetime: