Working on issue 206

This commit is contained in:
Rhet Turnbull
2020-08-20 06:39:48 -07:00
parent 2cf3b6bb67
commit ebd878a075
2 changed files with 71 additions and 21 deletions

View File

@@ -34,7 +34,12 @@ class FileUtilABC(ABC):
@classmethod
@abstractmethod
def cmp_sig(cls, file1, file2):
def cmp(cls, file1, file2, mtime1 = None):
pass
@classmethod
@abstractmethod
def cmp_file_sig(cls, file1, file2):
pass
@classmethod
@@ -114,11 +119,32 @@ class FileUtilMacOS(FileUtilABC):
os.utime(path, times)
@classmethod
def cmp_sig(cls, f1, s2):
def cmp(cls, f1, f2, mtime1 = None):
"""Does shallow compare (file signatures) of f1 to file f2.
Arguments:
f1 -- File name
f2 -- File name
mtime1 -- optional, pass alternate file modification timestamp for f1; will be converted to int
Return value:
True if the file signatures as returned by stat are the same, False otherwise.
Does not do a byte-by-byte comparison.
"""
s1 = cls._sig(os.stat(f1))
if mtime1 is not None:
s1 = (s1[0], s1[1], int(mtime1))
s2 = cls._sig(os.stat(f2))
if s1[0] != stat.S_IFREG or s2[0] != stat.S_IFREG:
return False
return s1 == s2
@classmethod
def cmp_file_sig(cls, f1, s2):
"""Compare file f1 to signature s2.
Arguments:
f1 -- File name
s2 -- stats as returned by sig
s2 -- stats as returned by _sig
Return value:
True if the files are the same, False otherwise.
@@ -140,7 +166,12 @@ class FileUtilMacOS(FileUtilABC):
@staticmethod
def _sig(st):
return (stat.S_IFMT(st.st_mode), st.st_size, st.st_mtime)
""" return tuple of (mode, size, mtime) of file based on os.stat
Args:
st: os.stat signature
"""
# use int(st.st_mtime) because ditto does not copy fractional portion of mtime
return (stat.S_IFMT(st.st_mode), st.st_size, int(st.st_mtime))
class FileUtil(FileUtilMacOS):
@@ -151,8 +182,8 @@ class FileUtil(FileUtilMacOS):
class FileUtilNoOp(FileUtil):
""" No-Op implementation of FileUtil for testing / dry-run mode
all methods with exception of cmp_sig and file_cmp are no-op
cmp_sig functions as FileUtil.cmp_sig does
all methods with exception of cmp, cmp_file_sig and file_cmp are no-op
cmp and cmp_file_sig functions as FileUtil methods do
file_cmp returns mock data
"""

View File

@@ -11,7 +11,6 @@
# TODO: should this be its own PhotoExporter class?
import filecmp
import glob
import json
import logging
@@ -484,7 +483,7 @@ def export2(
if update and dest.exists():
# destination exists, check to see if destination is the right UUID
dest_uuid = export_db.get_uuid_for_file(dest)
if dest_uuid is None and filecmp.cmp(src, dest):
if dest_uuid is None and fileutil.cmp(src, dest):
# might be exporting into a pre-ExportDB folder or the DB got deleted
logging.debug(
f"Found matching file with blank uuid: {self.uuid}, {dest}"
@@ -516,7 +515,7 @@ def export2(
dest = pathlib.Path(file_)
found_match = True
break
elif dest_uuid is None and filecmp.cmp(src, file_):
elif dest_uuid is None and fileutil.cmp(src, file_):
# files match, update the UUID
logging.debug(
f"Found matching file with blank uuid: {self.uuid}, {file_}"
@@ -836,21 +835,33 @@ def _export_photo(
op_desc = "export_by_copying"
if not update:
# not update, do the the hardlink
logging.debug(f"Not update: {op_desc} linking file {src} {dest}")
# not update, export the file
logging.debug(f"Exporting file with {op_desc} {src} {dest}")
exported_files.append(dest_str)
else: #updating
else: # updating
if not dest_exists:
# update, destination doesn't exist (new file)
logging.debug(f"Update: exporting new file with {op_desc} {src} {dest}")
update_new_files.append(dest_str)
else:
# update, destination exists, but we might not need to replace it...
if (( export_as_hardlink and dest.samefile(src)) or
(not export_as_hardlink and not dest.samefile(src) and (
( exiftool and fileutil.cmp_sig(dest_str, export_db.get_stat_exif_for_file(dest_str))) or
(not exiftool and filecmp.cmp(src, dest)))
)):
if touch_file:
sig_cmp = fileutil.cmp(src, dest, mtime1=self.date.timestamp())
else:
sig_cmp = fileutil.cmp(src, dest)
if (export_as_hardlink and dest.samefile(src)) or (
not export_as_hardlink
and not dest.samefile(src)
and (
(
exiftool
and fileutil.cmp_file_sig(
dest_str, export_db.get_stat_exif_for_file(dest_str)
)
)
or (not exiftool and sig_cmp)
)
):
# destination exists but its signature is "identical"
logging.debug(f"Update: skipping identical original files {src} {dest}")
# call set_stat because code can reach this spot if no export DB but exporting a RAW or live photo
@@ -859,13 +870,17 @@ def _export_photo(
update_skipped_files.append(dest_str)
else:
# destination exists but is different
logging.debug(f"Update: removing existing file prior to {op_desc} {src} {dest}")
logging.debug(
f"Update: removing existing file prior to {op_desc} {src} {dest}"
)
update_updated_files.append(dest_str)
if not update_skipped_files:
if dest_exists and (update or overwrite):
# need to remove the destination first
logging.debug(f"Update: removing existing file prior to export_as_hardlink {src} {dest}")
logging.debug(
f"Update: removing existing file prior to export_as_hardlink {src} {dest}"
)
# dest.unlink()
fileutil.unlink(dest)
if export_as_hardlink:
@@ -873,7 +888,7 @@ def _export_photo(
else:
fileutil.copy(src, dest_str, norsrc=no_xattr)
if touch_file:
ts=self.date.timestamp()
ts = self.date.timestamp()
fileutil.utime(dest, (ts, ts))
export_db.set_data(
@@ -886,7 +901,11 @@ def _export_photo(
)
return ExportResults(
exported_files + update_new_files + update_updated_files, update_new_files, update_updated_files, update_skipped_files, []
exported_files + update_new_files + update_updated_files,
update_new_files,
update_updated_files,
update_skipped_files,
[],
)