Working on issue 206
This commit is contained in:
@@ -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
|
||||
"""
|
||||
|
||||
|
||||
@@ -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,
|
||||
[],
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user