--touch-file now working with --update
This commit is contained in:
@@ -1623,7 +1623,7 @@ def export(
|
|||||||
# print summary results
|
# print summary results
|
||||||
if update:
|
if update:
|
||||||
photo_str_new = "photos" if len(results_new) != 1 else "photo"
|
photo_str_new = "photos" if len(results_new) != 1 else "photo"
|
||||||
photo_str_updated = "photos" if len(results_new) != 1 else "photo"
|
photo_str_updated = "photos" if len(results_updated) != 1 else "photo"
|
||||||
photo_str_skipped = "photos" if len(results_skipped) != 1 else "photo"
|
photo_str_skipped = "photos" if len(results_skipped) != 1 else "photo"
|
||||||
photo_str_touched = "photos" if len(results_touched) != 1 else "photo"
|
photo_str_touched = "photos" if len(results_touched) != 1 else "photo"
|
||||||
photo_str_exif_updated = (
|
photo_str_exif_updated = (
|
||||||
@@ -2217,6 +2217,8 @@ def export_photo(
|
|||||||
verbose(f"Exported updated file {updated}")
|
verbose(f"Exported updated file {updated}")
|
||||||
for skipped in export_results.skipped:
|
for skipped in export_results.skipped:
|
||||||
verbose(f"Skipped up to date file {skipped}")
|
verbose(f"Skipped up to date file {skipped}")
|
||||||
|
for touched in export_results.touched:
|
||||||
|
verbose(f"Touched date on file {touched}")
|
||||||
|
|
||||||
# if export-edited, also export the edited version
|
# if export-edited, also export the edited version
|
||||||
# verify the photo has adjustments and valid path to avoid raising an exception
|
# verify the photo has adjustments and valid path to avoid raising an exception
|
||||||
|
|||||||
@@ -850,6 +850,9 @@ def _export_photo(
|
|||||||
logging.debug(f"Exporting file with {op_desc} {src} {dest}")
|
logging.debug(f"Exporting file with {op_desc} {src} {dest}")
|
||||||
exported_files.append(dest_str)
|
exported_files.append(dest_str)
|
||||||
if touch_file:
|
if touch_file:
|
||||||
|
sig = fileutil.file_sig(src)
|
||||||
|
sig = (sig[0], sig[1], self.date.timestamp())
|
||||||
|
if not fileutil.cmp_file_sig(src, sig):
|
||||||
touched_files.append(dest_str)
|
touched_files.append(dest_str)
|
||||||
else: # updating
|
else: # updating
|
||||||
if not dest_exists:
|
if not dest_exists:
|
||||||
@@ -860,10 +863,9 @@ def _export_photo(
|
|||||||
touched_files.append(dest_str)
|
touched_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 touch_file:
|
cmp_orig = fileutil.cmp(src, dest)
|
||||||
sig_cmp = fileutil.cmp(src, dest, mtime1=self.date.timestamp())
|
cmp_touch = fileutil.cmp(src, dest, mtime1=self.date.timestamp())
|
||||||
else:
|
sig_cmp = cmp_touch if touch_file else cmp_orig
|
||||||
sig_cmp = fileutil.cmp(src, dest)
|
|
||||||
if (export_as_hardlink and dest.samefile(src)) or (
|
if (export_as_hardlink and dest.samefile(src)) or (
|
||||||
not export_as_hardlink
|
not export_as_hardlink
|
||||||
and not dest.samefile(src)
|
and not dest.samefile(src)
|
||||||
@@ -877,18 +879,19 @@ def _export_photo(
|
|||||||
or (not exiftool and sig_cmp)
|
or (not exiftool and sig_cmp)
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
# destination exists but its signature is "identical"
|
|
||||||
# call set_stat because code can reach this spot if no export DB but exporting a RAW or live photo
|
|
||||||
# potentially re-writes the data in the database but ensures database is complete
|
|
||||||
export_db.set_stat_orig_for_file(dest_str, fileutil.file_sig(dest_str))
|
|
||||||
update_skipped_files.append(dest_str)
|
update_skipped_files.append(dest_str)
|
||||||
else:
|
else:
|
||||||
# destination exists but signature is different
|
# destination exists but signature is different
|
||||||
if touch_file and fileutil.cmp(src, dest) and not sig_cmp:
|
if touch_file and cmp_orig and not cmp_touch:
|
||||||
# destination exists, signature matches original but does not match expected touch time
|
# destination exists, signature matches original but does not match expected touch time
|
||||||
# skip exporting but update touch time
|
# skip exporting but update touch time
|
||||||
update_skipped_files.append(dest_str)
|
update_skipped_files.append(dest_str)
|
||||||
touched_files.append(dest_str)
|
touched_files.append(dest_str)
|
||||||
|
elif not touch_file and cmp_touch and not cmp_orig:
|
||||||
|
# destination exists, signature matches expected touch but not original
|
||||||
|
# user likely exported with touch_file and is now exporting without touch_file
|
||||||
|
# don't update the file because it's same but leave touch time
|
||||||
|
update_skipped_files.append(dest_str)
|
||||||
else:
|
else:
|
||||||
# destination exists but is different
|
# destination exists but is different
|
||||||
update_updated_files.append(dest_str)
|
update_updated_files.append(dest_str)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ PLACES_PHOTOS_DB = "tests/Test-Places-Catalina-10_15_1.photoslibrary"
|
|||||||
PLACES_PHOTOS_DB_13 = "tests/Test-Places-High-Sierra-10.13.6.photoslibrary"
|
PLACES_PHOTOS_DB_13 = "tests/Test-Places-High-Sierra-10.13.6.photoslibrary"
|
||||||
PHOTOS_DB_15_4 = "tests/Test-10.15.4.photoslibrary"
|
PHOTOS_DB_15_4 = "tests/Test-10.15.4.photoslibrary"
|
||||||
PHOTOS_DB_15_5 = "tests/Test-10.15.5.photoslibrary"
|
PHOTOS_DB_15_5 = "tests/Test-10.15.5.photoslibrary"
|
||||||
|
PHOTOS_DB_15_6 = "tests/Test-10.15.5.photoslibrary"
|
||||||
PHOTOS_DB_14_6 = "tests/Test-10.14.6.photoslibrary"
|
PHOTOS_DB_14_6 = "tests/Test-10.14.6.photoslibrary"
|
||||||
|
|
||||||
UUID_FILE = "tests/uuid_from_file.txt"
|
UUID_FILE = "tests/uuid_from_file.txt"
|
||||||
@@ -182,6 +183,7 @@ CLI_EXPORT_UUID = "D79B8D77-BFFC-460B-9312-034F2877D35B"
|
|||||||
|
|
||||||
CLI_EXPORT_UUID_FILENAME = "Pumkins2.jpg"
|
CLI_EXPORT_UUID_FILENAME = "Pumkins2.jpg"
|
||||||
|
|
||||||
|
CLI_EXPORT_BY_DATE_TOUCH_TIMES = [1538165373, 1538163349]
|
||||||
CLI_EXPORT_BY_DATE = ["2018/09/28/Pumpkins3.jpg", "2018/09/28/Pumkins1.jpg"]
|
CLI_EXPORT_BY_DATE = ["2018/09/28/Pumpkins3.jpg", "2018/09/28/Pumkins1.jpg"]
|
||||||
|
|
||||||
CLI_EXPORT_SIDECAR_FILENAMES = ["Pumkins2.jpg", "Pumkins2.json", "Pumkins2.xmp"]
|
CLI_EXPORT_SIDECAR_FILENAMES = ["Pumkins2.jpg", "Pumkins2.json", "Pumkins2.xmp"]
|
||||||
@@ -706,7 +708,7 @@ def test_query_date_1():
|
|||||||
import time
|
import time
|
||||||
from osxphotos.__main__ import query
|
from osxphotos.__main__ import query
|
||||||
|
|
||||||
os.environ['TZ'] = "US/Pacific"
|
os.environ["TZ"] = "US/Pacific"
|
||||||
time.tzset()
|
time.tzset()
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
@@ -726,6 +728,7 @@ def test_query_date_1():
|
|||||||
json_got = json.loads(result.output)
|
json_got = json.loads(result.output)
|
||||||
assert len(json_got) == 4
|
assert len(json_got) == 4
|
||||||
|
|
||||||
|
|
||||||
def test_query_date_2():
|
def test_query_date_2():
|
||||||
""" Test --from-date and --to-date """
|
""" Test --from-date and --to-date """
|
||||||
import json
|
import json
|
||||||
@@ -735,7 +738,7 @@ def test_query_date_2():
|
|||||||
import time
|
import time
|
||||||
from osxphotos.__main__ import query
|
from osxphotos.__main__ import query
|
||||||
|
|
||||||
os.environ['TZ'] = "Asia/Jerusalem"
|
os.environ["TZ"] = "Asia/Jerusalem"
|
||||||
time.tzset()
|
time.tzset()
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
@@ -755,6 +758,7 @@ def test_query_date_2():
|
|||||||
json_got = json.loads(result.output)
|
json_got = json.loads(result.output)
|
||||||
assert len(json_got) == 2
|
assert len(json_got) == 2
|
||||||
|
|
||||||
|
|
||||||
def test_query_date_timezone():
|
def test_query_date_timezone():
|
||||||
""" Test --from-date, --to-date with ISO 8601 timezone """
|
""" Test --from-date, --to-date with ISO 8601 timezone """
|
||||||
import json
|
import json
|
||||||
@@ -764,7 +768,7 @@ def test_query_date_timezone():
|
|||||||
import time
|
import time
|
||||||
from osxphotos.__main__ import query
|
from osxphotos.__main__ import query
|
||||||
|
|
||||||
os.environ['TZ'] = "US/Pacific"
|
os.environ["TZ"] = "US/Pacific"
|
||||||
time.tzset()
|
time.tzset()
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
@@ -2587,6 +2591,162 @@ def test_export_directory_template_1_dry_run():
|
|||||||
assert not os.path.isfile(os.path.join(workdir, filepath))
|
assert not os.path.isfile(os.path.join(workdir, filepath))
|
||||||
|
|
||||||
|
|
||||||
|
def test_export_touch_files():
|
||||||
|
""" test export with --touch-files """
|
||||||
|
import os
|
||||||
|
|
||||||
|
import osxphotos
|
||||||
|
from osxphotos.__main__ import export
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
cwd = os.getcwd()
|
||||||
|
# pylint: disable=not-context-manager
|
||||||
|
with runner.isolated_filesystem():
|
||||||
|
result = runner.invoke(
|
||||||
|
export,
|
||||||
|
[
|
||||||
|
os.path.join(cwd, PHOTOS_DB_15_6),
|
||||||
|
".",
|
||||||
|
"-V",
|
||||||
|
"--touch-file",
|
||||||
|
"--export-by-date",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
assert "Exported: 16 photos, touched date: 14 photos" in result.output
|
||||||
|
|
||||||
|
for fname, mtime in zip(CLI_EXPORT_BY_DATE, CLI_EXPORT_BY_DATE_TOUCH_TIMES):
|
||||||
|
st = os.stat(fname)
|
||||||
|
assert int(st.st_mtime) == mtime
|
||||||
|
|
||||||
|
|
||||||
|
def test_export_touch_files_update():
|
||||||
|
""" test complex export scenario with --update and --touch-files """
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import time
|
||||||
|
|
||||||
|
import osxphotos
|
||||||
|
from osxphotos.__main__ import export
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
cwd = os.getcwd()
|
||||||
|
# pylint: disable=not-context-manager
|
||||||
|
with runner.isolated_filesystem():
|
||||||
|
# basic export with dry-run
|
||||||
|
result = runner.invoke(
|
||||||
|
export,
|
||||||
|
[os.path.join(cwd, PHOTOS_DB_15_6), ".", "--export-by-date", "--dry-run"],
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
assert "Exported: 16 photos" in result.output
|
||||||
|
|
||||||
|
assert not pathlib.Path(CLI_EXPORT_BY_DATE[0]).is_file()
|
||||||
|
|
||||||
|
# without dry-run
|
||||||
|
result = runner.invoke(
|
||||||
|
export, [os.path.join(cwd, PHOTOS_DB_15_6), ".", "--export-by-date"]
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
assert "Exported: 16 photos" in result.output
|
||||||
|
|
||||||
|
assert pathlib.Path(CLI_EXPORT_BY_DATE[0]).is_file()
|
||||||
|
|
||||||
|
# --update
|
||||||
|
result = runner.invoke(
|
||||||
|
export,
|
||||||
|
[os.path.join(cwd, PHOTOS_DB_15_6), ".", "--export-by-date", "--update"],
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"Exported: 0 photos, updated: 0 photos, skipped: 16 photos, updated EXIF data: 0 photos"
|
||||||
|
in result.output
|
||||||
|
)
|
||||||
|
|
||||||
|
# --update --touch-file --dry-run
|
||||||
|
result = runner.invoke(
|
||||||
|
export,
|
||||||
|
[
|
||||||
|
os.path.join(cwd, PHOTOS_DB_15_6),
|
||||||
|
".",
|
||||||
|
"--export-by-date",
|
||||||
|
"--update",
|
||||||
|
"--touch-file",
|
||||||
|
"--dry-run",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert (
|
||||||
|
"Exported: 0 photos, updated: 0 photos, skipped: 16 photos, updated EXIF data: 0 photos, touched date: 14 photos"
|
||||||
|
in result.output
|
||||||
|
)
|
||||||
|
|
||||||
|
for fname, mtime in zip(CLI_EXPORT_BY_DATE, CLI_EXPORT_BY_DATE_TOUCH_TIMES):
|
||||||
|
st = os.stat(fname)
|
||||||
|
assert int(st.st_mtime) != mtime
|
||||||
|
|
||||||
|
# --update --touch-file
|
||||||
|
result = runner.invoke(
|
||||||
|
export,
|
||||||
|
[
|
||||||
|
os.path.join(cwd, PHOTOS_DB_15_6),
|
||||||
|
".",
|
||||||
|
"--export-by-date",
|
||||||
|
"--update",
|
||||||
|
"--touch-file",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert (
|
||||||
|
"Exported: 0 photos, updated: 0 photos, skipped: 16 photos, updated EXIF data: 0 photos, touched date: 14 photos"
|
||||||
|
in result.output
|
||||||
|
)
|
||||||
|
|
||||||
|
for fname, mtime in zip(CLI_EXPORT_BY_DATE, CLI_EXPORT_BY_DATE_TOUCH_TIMES):
|
||||||
|
st = os.stat(fname)
|
||||||
|
assert int(st.st_mtime) == mtime
|
||||||
|
|
||||||
|
# touch one file and run update again
|
||||||
|
ts = time.time()
|
||||||
|
os.utime(CLI_EXPORT_BY_DATE[0], (ts, ts))
|
||||||
|
|
||||||
|
result = runner.invoke(
|
||||||
|
export,
|
||||||
|
[
|
||||||
|
os.path.join(cwd, PHOTOS_DB_15_6),
|
||||||
|
".",
|
||||||
|
"--export-by-date",
|
||||||
|
"--update",
|
||||||
|
"--touch-file",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert (
|
||||||
|
"Exported: 0 photos, updated: 1 photo, skipped: 15 photos, updated EXIF data: 0 photos, touched date: 1 photo"
|
||||||
|
in result.output
|
||||||
|
)
|
||||||
|
|
||||||
|
for fname, mtime in zip(CLI_EXPORT_BY_DATE, CLI_EXPORT_BY_DATE_TOUCH_TIMES):
|
||||||
|
st = os.stat(fname)
|
||||||
|
assert int(st.st_mtime) == mtime
|
||||||
|
|
||||||
|
# run update without --touch-file
|
||||||
|
result = runner.invoke(
|
||||||
|
export,
|
||||||
|
[os.path.join(cwd, PHOTOS_DB_15_6), ".", "--export-by-date", "--update"],
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"Exported: 0 photos, updated: 0 photos, skipped: 16 photos, updated EXIF data: 0 photos"
|
||||||
|
in result.output
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_labels():
|
def test_labels():
|
||||||
"""Test osxphotos labels """
|
"""Test osxphotos labels """
|
||||||
import json
|
import json
|
||||||
|
|||||||
Reference in New Issue
Block a user