Working on issue 206
This commit is contained in:
@@ -34,7 +34,12 @@ class FileUtilABC(ABC):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abstractmethod
|
@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
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -114,11 +119,32 @@ class FileUtilMacOS(FileUtilABC):
|
|||||||
os.utime(path, times)
|
os.utime(path, times)
|
||||||
|
|
||||||
@classmethod
|
@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.
|
"""Compare file f1 to signature s2.
|
||||||
Arguments:
|
Arguments:
|
||||||
f1 -- File name
|
f1 -- File name
|
||||||
s2 -- stats as returned by sig
|
s2 -- stats as returned by _sig
|
||||||
|
|
||||||
Return value:
|
Return value:
|
||||||
True if the files are the same, False otherwise.
|
True if the files are the same, False otherwise.
|
||||||
@@ -140,7 +166,12 @@ class FileUtilMacOS(FileUtilABC):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _sig(st):
|
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):
|
class FileUtil(FileUtilMacOS):
|
||||||
@@ -151,8 +182,8 @@ class FileUtil(FileUtilMacOS):
|
|||||||
|
|
||||||
class FileUtilNoOp(FileUtil):
|
class FileUtilNoOp(FileUtil):
|
||||||
""" No-Op implementation of FileUtil for testing / dry-run mode
|
""" No-Op implementation of FileUtil for testing / dry-run mode
|
||||||
all methods with exception of cmp_sig and file_cmp are no-op
|
all methods with exception of cmp, cmp_file_sig and file_cmp are no-op
|
||||||
cmp_sig functions as FileUtil.cmp_sig does
|
cmp and cmp_file_sig functions as FileUtil methods do
|
||||||
file_cmp returns mock data
|
file_cmp returns mock data
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
# TODO: should this be its own PhotoExporter class?
|
# TODO: should this be its own PhotoExporter class?
|
||||||
|
|
||||||
import filecmp
|
|
||||||
import glob
|
import glob
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
@@ -484,7 +483,7 @@ def export2(
|
|||||||
if update and dest.exists():
|
if update and dest.exists():
|
||||||
# destination exists, check to see if destination is the right UUID
|
# destination exists, check to see if destination is the right UUID
|
||||||
dest_uuid = export_db.get_uuid_for_file(dest)
|
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
|
# might be exporting into a pre-ExportDB folder or the DB got deleted
|
||||||
logging.debug(
|
logging.debug(
|
||||||
f"Found matching file with blank uuid: {self.uuid}, {dest}"
|
f"Found matching file with blank uuid: {self.uuid}, {dest}"
|
||||||
@@ -516,7 +515,7 @@ def export2(
|
|||||||
dest = pathlib.Path(file_)
|
dest = pathlib.Path(file_)
|
||||||
found_match = True
|
found_match = True
|
||||||
break
|
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
|
# files match, update the UUID
|
||||||
logging.debug(
|
logging.debug(
|
||||||
f"Found matching file with blank uuid: {self.uuid}, {file_}"
|
f"Found matching file with blank uuid: {self.uuid}, {file_}"
|
||||||
@@ -836,21 +835,33 @@ def _export_photo(
|
|||||||
op_desc = "export_by_copying"
|
op_desc = "export_by_copying"
|
||||||
|
|
||||||
if not update:
|
if not update:
|
||||||
# not update, do the the hardlink
|
# not update, export the file
|
||||||
logging.debug(f"Not update: {op_desc} linking file {src} {dest}")
|
logging.debug(f"Exporting file with {op_desc} {src} {dest}")
|
||||||
exported_files.append(dest_str)
|
exported_files.append(dest_str)
|
||||||
else: #updating
|
else: # updating
|
||||||
if not dest_exists:
|
if not dest_exists:
|
||||||
# update, destination doesn't exist (new file)
|
# update, destination doesn't exist (new file)
|
||||||
logging.debug(f"Update: exporting new file with {op_desc} {src} {dest}")
|
logging.debug(f"Update: exporting new file with {op_desc} {src} {dest}")
|
||||||
update_new_files.append(dest_str)
|
update_new_files.append(dest_str)
|
||||||
else:
|
else:
|
||||||
# update, destination exists, but we might not need to replace it...
|
# update, destination exists, but we might not need to replace it...
|
||||||
if (( export_as_hardlink and dest.samefile(src)) or
|
if touch_file:
|
||||||
(not export_as_hardlink and not dest.samefile(src) and (
|
sig_cmp = fileutil.cmp(src, dest, mtime1=self.date.timestamp())
|
||||||
( exiftool and fileutil.cmp_sig(dest_str, export_db.get_stat_exif_for_file(dest_str))) or
|
else:
|
||||||
(not exiftool and filecmp.cmp(src, dest)))
|
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"
|
# destination exists but its signature is "identical"
|
||||||
logging.debug(f"Update: skipping identical original files {src} {dest}")
|
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
|
# 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)
|
update_skipped_files.append(dest_str)
|
||||||
else:
|
else:
|
||||||
# destination exists but is different
|
# 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)
|
update_updated_files.append(dest_str)
|
||||||
|
|
||||||
if not update_skipped_files:
|
if not update_skipped_files:
|
||||||
if dest_exists and (update or overwrite):
|
if dest_exists and (update or overwrite):
|
||||||
# need to remove the destination first
|
# 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()
|
# dest.unlink()
|
||||||
fileutil.unlink(dest)
|
fileutil.unlink(dest)
|
||||||
if export_as_hardlink:
|
if export_as_hardlink:
|
||||||
@@ -873,7 +888,7 @@ def _export_photo(
|
|||||||
else:
|
else:
|
||||||
fileutil.copy(src, dest_str, norsrc=no_xattr)
|
fileutil.copy(src, dest_str, norsrc=no_xattr)
|
||||||
if touch_file:
|
if touch_file:
|
||||||
ts=self.date.timestamp()
|
ts = self.date.timestamp()
|
||||||
fileutil.utime(dest, (ts, ts))
|
fileutil.utime(dest, (ts, ts))
|
||||||
|
|
||||||
export_db.set_data(
|
export_db.set_data(
|
||||||
@@ -886,7 +901,11 @@ def _export_photo(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return ExportResults(
|
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