Exportdb refactor (#638)

* Working on export_db refactor

* Added exportdb command, removed logic for missing export_db, #630

* Updated tests

* updated docs

* Added --config-only, #606

* Added validation for --exportdb

* Added --info to exportdb command

* Fixed exportdb --touch-file to migrate database if needed

* Added exportdb --migrate
This commit is contained in:
Rhet Turnbull
2022-02-21 10:15:01 -07:00
committed by GitHub
parent d8204e65eb
commit ecbd370a47
20 changed files with 1673 additions and 1060 deletions

View File

@@ -3,6 +3,7 @@
import os
import sqlite3
import tempfile
from tempfile import TemporaryDirectory
import pytest
from click.testing import CliRunner
@@ -675,6 +676,8 @@ CLI_EXIFTOOL_IGNORE_DATE_MODIFIED = {
CLI_EXIFTOOL_ERROR = ["E2078879-A29C-4D6F-BACB-E3BBE6C3EB91"]
CLI_NOT_REALLY_A_JPEG = "E2078879-A29C-4D6F-BACB-E3BBE6C3EB91"
CLI_EXIFTOOL_DUPLICATE_KEYWORDS = {
"E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51": "wedding.jpg"
}
@@ -4936,12 +4939,126 @@ def test_export_force_update():
export, [os.path.join(cwd, photos_db_path), ".", "--force-update"]
)
assert result.exit_code == 0
print(result.output)
assert (
f"Processed: {PHOTOS_NOT_IN_TRASH_LEN_15_7} photos, exported: 0, updated: 0, skipped: {PHOTOS_NOT_IN_TRASH_LEN_15_7+PHOTOS_EDITED_15_7}, updated EXIF data: 0, missing: 3, error: 0"
in result.output
)
@pytest.mark.skipif(exiftool is None, reason="exiftool not installed")
def test_export_update_complex():
"""test complex --update scenario, #630"""
import glob
import os
import os.path
import osxphotos
from osxphotos.cli import OSXPHOTOS_EXPORT_DB, export
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
# basic export
result = runner.invoke(export, [os.path.join(cwd, CLI_PHOTOS_DB), ".", "-V"])
assert result.exit_code == 0
files = glob.glob("*")
assert sorted(files) == sorted(CLI_EXPORT_FILENAMES)
assert os.path.isfile(OSXPHOTOS_EXPORT_DB)
src = os.path.join(cwd, CLI_PHOTOS_DB)
dest = os.path.join(os.getcwd(), "export_complex_update.photoslibrary")
photos_db_path = copy_photos_library_to_path(src, dest)
tempdir = TemporaryDirectory()
options = [
"--verbose",
"--update",
"--cleanup",
"--directory",
"{created.year}/{created.month}",
"--description-template",
"Album:{album,}{newline}Description:{descr,}",
"--exiftool",
"--exiftool-merge-keywords",
"--exiftool-merge-persons",
"--keyword-template",
"{keyword}",
"--not-hidden",
"--retry",
"2",
"--skip-original-if-edited",
"--timestamp",
"--strip",
"--skip-uuid",
CLI_NOT_REALLY_A_JPEG,
]
# update
result = runner.invoke(
export, [os.path.join(cwd, photos_db_path), tempdir.name, *options]
)
assert result.exit_code == 0
assert (
f"exported: {PHOTOS_NOT_IN_TRASH_LEN_15_7-1}, updated: 0, skipped: 0, updated EXIF data: {PHOTOS_NOT_IN_TRASH_LEN_15_7-1}"
in result.output
)
result = runner.invoke(
export, [os.path.join(cwd, photos_db_path), tempdir.name, *options]
)
assert result.exit_code == 0
assert "exported: 0" in result.output
# update a file
dbpath = os.path.join(photos_db_path, "database/Photos.sqlite")
try:
conn = sqlite3.connect(dbpath)
c = conn.cursor()
except sqlite3.Error as e:
pytest.exit(f"An error occurred opening sqlite file")
# photo is IMG_4547.jpg
c.execute(
"UPDATE ZADDITIONALASSETATTRIBUTES SET Z_OPT=9, ZTITLE='My Updated Title' WHERE Z_PK=8;"
)
conn.commit()
# run --update to see if updated metadata forced update
result = runner.invoke(
export, [os.path.join(cwd, photos_db_path), tempdir.name, *options]
)
assert result.exit_code == 0
assert (
f"exported: 0, updated: 1, skipped: {PHOTOS_NOT_IN_TRASH_LEN_15_7-2}, updated EXIF data: 1"
in result.output
)
# update, nothing should export
result = runner.invoke(
export, [os.path.join(cwd, photos_db_path), tempdir.name, *options]
)
assert result.exit_code == 0
assert (
f"exported: 0, updated: 0, skipped: {PHOTOS_NOT_IN_TRASH_LEN_15_7-1}, updated EXIF data: 0"
in result.output
)
# change the template and run again
options.extend(["--keyword-template", "FOO"])
# run update and all photos should be updated
result = runner.invoke(
export, [os.path.join(cwd, photos_db_path), tempdir.name, *options]
)
assert result.exit_code == 0
assert (
f"exported: 0, updated: {PHOTOS_NOT_IN_TRASH_LEN_15_7-1}, skipped: 0, updated EXIF data: {PHOTOS_NOT_IN_TRASH_LEN_15_7-1}"
in result.output
)
@pytest.mark.skipif(
"OSXPHOTOS_TEST_EXPORT" not in os.environ,
reason="Skip if not running on author's personal library.",
@@ -5266,17 +5383,14 @@ def test_export_update_no_db():
assert os.path.isfile(OSXPHOTOS_EXPORT_DB)
os.unlink(OSXPHOTOS_EXPORT_DB)
# update
# update, will re-export all files with different names
result = runner.invoke(
export, [os.path.join(cwd, CLI_PHOTOS_DB), ".", "--update"]
)
assert result.exit_code == 0
# unedited files will be skipped because their signatures will compare but
# edited files will be re-exported because there won't be an edited signature
# in the database
assert (
f"Processed: {PHOTOS_NOT_IN_TRASH_LEN_15_7} photos, exported: 0, updated: {PHOTOS_EDITED_15_7}, skipped: {PHOTOS_NOT_IN_TRASH_LEN_15_7}, updated EXIF data: 0, missing: 3, error: 0"
f"Processed: {PHOTOS_NOT_IN_TRASH_LEN_15_7} photos, exported: {PHOTOS_NOT_IN_TRASH_LEN_15_7+PHOTOS_EDITED_15_7}, updated: 0"
in result.output
)
assert os.path.isfile(OSXPHOTOS_EXPORT_DB)
@@ -5590,7 +5704,7 @@ def test_export_touch_files_update():
# touch one file and run update again
ts = time.time()
os.utime(CLI_EXPORT_BY_DATE[0], (ts, ts))
os.utime(CLI_EXPORT_BY_DATE_NEED_TOUCH[1], (ts, ts))
result = runner.invoke(
export,
@@ -5922,7 +6036,12 @@ def test_export_ignore_signature_sidecar():
# should result in a new sidecar being exported but not the image itself
exportdb = osxphotos.export_db.ExportDB("./.osxphotos_export.db", ".")
for filename in CLI_EXPORT_IGNORE_SIGNATURE_FILENAMES:
exportdb.set_sidecar_for_file(f"{filename}.xmp", "FOO", (0, 1, 2))
record = exportdb.get_file_record(filename)
sidecar_record = exportdb.create_or_get_file_record(
f"{filename}.xmp", record.uuid
)
sidecar_record.dest_sig = (0, 1, 2)
sidecar_record.digest = "FOO"
result = runner.invoke(
export,
@@ -6426,7 +6545,7 @@ def test_save_load_config():
],
)
assert result.exit_code == 0
assert "Saving options to file" in result.output
assert "Saving options to config file" in result.output
files = glob.glob("*")
assert "config.toml" in files
@@ -6462,7 +6581,7 @@ def test_save_load_config():
],
)
assert result.exit_code == 0
assert "Saving options to file" in result.output
assert "Saving options to config file" in result.output
files = glob.glob("*")
assert "config.toml" in files
@@ -6499,6 +6618,41 @@ def test_save_load_config():
assert "Writing XMP sidecar" not in result.output
def test_config_only():
"""test --save-config, --config-only"""
import glob
import os
import os.path
from osxphotos.cli import export
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
# test save config file
result = runner.invoke(
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"-V",
"--sidecar",
"XMP",
"--touch-file",
"--update",
"--save-config",
"config.toml",
"--config-only",
],
)
assert result.exit_code == 0
assert "Saved config file" in result.output
assert "Processed:" not in result.output
files = glob.glob("*")
assert "config.toml" in files
def test_export_exportdb():
"""test --exportdb"""
import glob