Added --debug and crash reporter to export, #628
This commit is contained in:
@@ -61,6 +61,7 @@ from .configoptions import (
|
|||||||
ConfigOptionsInvalidError,
|
ConfigOptionsInvalidError,
|
||||||
ConfigOptionsLoadError,
|
ConfigOptionsLoadError,
|
||||||
)
|
)
|
||||||
|
from .crash_reporter import crash_reporter
|
||||||
from .datetime_formatter import DateTimeFormatter
|
from .datetime_formatter import DateTimeFormatter
|
||||||
from .exiftool import get_exiftool_path
|
from .exiftool import get_exiftool_path
|
||||||
from .export_db import ExportDB, ExportDBInMemory
|
from .export_db import ExportDB, ExportDBInMemory
|
||||||
@@ -135,12 +136,19 @@ __all__ = [
|
|||||||
VERBOSE = False
|
VERBOSE = False
|
||||||
VERBOSE_TIMESTAMP = False
|
VERBOSE_TIMESTAMP = False
|
||||||
|
|
||||||
|
# global variable to control debug output
|
||||||
|
# set via --debug
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
# used to show/hide hidden commands
|
# used to show/hide hidden commands
|
||||||
OSXPHOTOS_HIDDEN = not bool(os.getenv("OSXPHOTOS_SHOW_HIDDEN", default=False))
|
OSXPHOTOS_HIDDEN = not bool(os.getenv("OSXPHOTOS_SHOW_HIDDEN", default=False))
|
||||||
|
|
||||||
# used by snap and diff commands
|
# used by snap and diff commands
|
||||||
OSXPHOTOS_SNAPSHOT_DIR = "/private/tmp/osxphotos_snapshots"
|
OSXPHOTOS_SNAPSHOT_DIR = "/private/tmp/osxphotos_snapshots"
|
||||||
|
|
||||||
|
# where to write the crash report if osxphotos crashes
|
||||||
|
OSXPHOTOS_CRASH_LOG = os.getcwd() + "/osxphotos_crash.log"
|
||||||
|
|
||||||
rich.traceback.install()
|
rich.traceback.install()
|
||||||
|
|
||||||
|
|
||||||
@@ -674,7 +682,7 @@ def QUERY_OPTIONS(f):
|
|||||||
@click.version_option(__version__, "--version", "-v")
|
@click.version_option(__version__, "--version", "-v")
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def cli(ctx, db, json_, debug):
|
def cli(ctx, db, json_, debug):
|
||||||
ctx.obj = CLI_Obj(db=db, json=json_, debug=debug)
|
ctx.obj = CLI_Obj(db=db, json=json_)
|
||||||
|
|
||||||
|
|
||||||
@cli.command(cls=ExportCommand)
|
@cli.command(cls=ExportCommand)
|
||||||
@@ -1205,10 +1213,25 @@ def cli(ctx, db, json_, debug):
|
|||||||
f"Can be specified multiple times. Valid options are: {PROFILE_SORT_KEYS}. "
|
f"Can be specified multiple times. Valid options are: {PROFILE_SORT_KEYS}. "
|
||||||
"Default = 'cumulative'.",
|
"Default = 'cumulative'.",
|
||||||
)
|
)
|
||||||
|
@click.option(
|
||||||
|
"--debug",
|
||||||
|
required=False,
|
||||||
|
is_flag=True,
|
||||||
|
default=False,
|
||||||
|
hidden=OSXPHOTOS_HIDDEN,
|
||||||
|
help="Enable debug output.",
|
||||||
|
)
|
||||||
@DB_ARGUMENT
|
@DB_ARGUMENT
|
||||||
@click.argument("dest", nargs=1, type=click.Path(exists=True))
|
@click.argument("dest", nargs=1, type=click.Path(exists=True))
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
|
@crash_reporter(
|
||||||
|
OSXPHOTOS_CRASH_LOG,
|
||||||
|
"[red]Something went wrong and osxphotos encountered an error:[/red]",
|
||||||
|
"osxphotos crash log",
|
||||||
|
"Please file a bug report at https://github.com/RhetTbull/osxphotos/issues with the crash log attached.",
|
||||||
|
f"osxphotos version: {__version__}",
|
||||||
|
)
|
||||||
def export(
|
def export(
|
||||||
ctx,
|
ctx,
|
||||||
cli_obj,
|
cli_obj,
|
||||||
@@ -1347,6 +1370,7 @@ def export(
|
|||||||
preview_if_missing,
|
preview_if_missing,
|
||||||
profile,
|
profile,
|
||||||
profile_sort,
|
profile_sort,
|
||||||
|
debug,
|
||||||
):
|
):
|
||||||
"""Export photos from the Photos database.
|
"""Export photos from the Photos database.
|
||||||
Export path DEST is required.
|
Export path DEST is required.
|
||||||
@@ -1360,6 +1384,11 @@ def export(
|
|||||||
to modify this behavior.
|
to modify this behavior.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
global DEBUG
|
||||||
|
if debug:
|
||||||
|
DEBUG = True
|
||||||
|
osxphotos._set_debug(True)
|
||||||
|
|
||||||
if profile:
|
if profile:
|
||||||
click.echo("Profiling...")
|
click.echo("Profiling...")
|
||||||
profile_sort = profile_sort or ["cumulative"]
|
profile_sort = profile_sort or ["cumulative"]
|
||||||
@@ -1403,7 +1432,7 @@ def export(
|
|||||||
),
|
),
|
||||||
err=True,
|
err=True,
|
||||||
)
|
)
|
||||||
raise click.Abort()
|
sys.exit(1)
|
||||||
|
|
||||||
# re-set the local vars to the corresponding config value
|
# re-set the local vars to the corresponding config value
|
||||||
# this isn't elegant but avoids having to rewrite this function to use cfg.varname for every parameter
|
# this isn't elegant but avoids having to rewrite this function to use cfg.varname for every parameter
|
||||||
@@ -1589,7 +1618,7 @@ def export(
|
|||||||
),
|
),
|
||||||
err=True,
|
err=True,
|
||||||
)
|
)
|
||||||
raise click.Abort()
|
sys.exit(1)
|
||||||
|
|
||||||
if all(x in [s.lower() for s in sidecar] for x in ["json", "exiftool"]):
|
if all(x in [s.lower() for s in sidecar] for x in ["json", "exiftool"]):
|
||||||
click.echo(
|
click.echo(
|
||||||
@@ -1599,7 +1628,7 @@ def export(
|
|||||||
),
|
),
|
||||||
err=True,
|
err=True,
|
||||||
)
|
)
|
||||||
raise click.Abort()
|
sys.exit(1)
|
||||||
|
|
||||||
if xattr_template:
|
if xattr_template:
|
||||||
for attr, _ in xattr_template:
|
for attr, _ in xattr_template:
|
||||||
@@ -1612,7 +1641,7 @@ def export(
|
|||||||
),
|
),
|
||||||
err=True,
|
err=True,
|
||||||
)
|
)
|
||||||
raise click.Abort()
|
sys.exit(1)
|
||||||
|
|
||||||
if save_config:
|
if save_config:
|
||||||
verbose_(f"Saving options to file {save_config}")
|
verbose_(f"Saving options to file {save_config}")
|
||||||
@@ -1633,7 +1662,7 @@ def export(
|
|||||||
click.echo(
|
click.echo(
|
||||||
click.style(f"DEST {dest} must be valid path", fg=CLI_COLOR_ERROR), err=True
|
click.style(f"DEST {dest} must be valid path", fg=CLI_COLOR_ERROR), err=True
|
||||||
)
|
)
|
||||||
raise click.Abort()
|
sys.exit(1)
|
||||||
|
|
||||||
dest = str(pathlib.Path(dest).resolve())
|
dest = str(pathlib.Path(dest).resolve())
|
||||||
|
|
||||||
@@ -1644,7 +1673,7 @@ def export(
|
|||||||
),
|
),
|
||||||
err=True,
|
err=True,
|
||||||
)
|
)
|
||||||
raise click.Abort()
|
sys.exit(1)
|
||||||
|
|
||||||
# if use_photokit and not check_photokit_authorization():
|
# if use_photokit and not check_photokit_authorization():
|
||||||
# click.echo(
|
# click.echo(
|
||||||
@@ -3148,6 +3177,9 @@ def export_photo_to_directory(
|
|||||||
f"Retrying export for photo ({photo.uuid}: {photo.original_filename})"
|
f"Retrying export for photo ({photo.uuid}: {photo.original_filename})"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
if DEBUG:
|
||||||
|
# if debug mode, don't swallow the exceptions
|
||||||
|
raise e
|
||||||
click.echo(
|
click.echo(
|
||||||
click.style(
|
click.style(
|
||||||
f"Error exporting photo ({photo.uuid}: {photo.original_filename}) as {filename}: {e}",
|
f"Error exporting photo ({photo.uuid}: {photo.original_filename}) as {filename}: {e}",
|
||||||
@@ -3516,7 +3548,7 @@ def write_export_report(report_file, results):
|
|||||||
click.style("Could not open output file for writing", fg=CLI_COLOR_ERROR),
|
click.style("Could not open output file for writing", fg=CLI_COLOR_ERROR),
|
||||||
err=True,
|
err=True,
|
||||||
)
|
)
|
||||||
raise click.Abort()
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def cleanup_files(dest_path, files_to_keep, fileutil):
|
def cleanup_files(dest_path, files_to_keep, fileutil):
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .._constants import _DB_TABLE_NAMES, _PHOTOS_4_VERSION
|
from .._constants import _DB_TABLE_NAMES, _PHOTOS_4_VERSION
|
||||||
from ..utils import _db_is_locked, _debug, _open_sql_file
|
from ..utils import _db_is_locked, _open_sql_file
|
||||||
from .photosdb_utils import get_db_version
|
from .photosdb_utils import get_db_version
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import uuid as uuidlib
|
|||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
|
|
||||||
from .._constants import _PHOTOS_4_VERSION, SEARCH_CATEGORY_LABEL
|
from .._constants import _PHOTOS_4_VERSION, SEARCH_CATEGORY_LABEL
|
||||||
from ..utils import _db_is_locked, _debug, _open_sql_file, normalize_unicode
|
from ..utils import _db_is_locked, _open_sql_file, normalize_unicode
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This module should be imported in the class defintion of PhotosDB in photosdb.py
|
This module should be imported in the class defintion of PhotosDB in photosdb.py
|
||||||
@@ -139,17 +139,6 @@ def _process_searchinfo(self):
|
|||||||
_db_searchinfo_labels[label] = [uuid]
|
_db_searchinfo_labels[label] = [uuid]
|
||||||
_db_searchinfo_labels_normalized[label_norm] = [uuid]
|
_db_searchinfo_labels_normalized[label_norm] = [uuid]
|
||||||
|
|
||||||
if _debug():
|
|
||||||
logging.debug(
|
|
||||||
"_db_searchinfo_categories: \n" + pformat(self._db_searchinfo_categories)
|
|
||||||
)
|
|
||||||
logging.debug("_db_searchinfo_uuid: \n" + pformat(self._db_searchinfo_uuid))
|
|
||||||
logging.debug("_db_searchinfo_labels: \n" + pformat(self._db_searchinfo_labels))
|
|
||||||
logging.debug(
|
|
||||||
"_db_searchinfo_labels_normalized: \n"
|
|
||||||
+ pformat(self._db_searchinfo_labels_normalized)
|
|
||||||
)
|
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2456,7 +2456,6 @@ class PhotosDB:
|
|||||||
verbose("Processing moments.")
|
verbose("Processing moments.")
|
||||||
self._process_moments()
|
self._process_moments()
|
||||||
|
|
||||||
# done processing, dump debug data if requested
|
|
||||||
verbose("Done processing details from Photos library.")
|
verbose("Done processing details from Photos library.")
|
||||||
|
|
||||||
def _process_moments(self):
|
def _process_moments(self):
|
||||||
|
|||||||
@@ -3883,8 +3883,8 @@ def test_export_directory_template_3():
|
|||||||
"{created.year}/{foo}",
|
"{created.year}/{foo}",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
assert result.exit_code == 2
|
assert result.exit_code != 0
|
||||||
assert "Error: Invalid template" in result.output
|
assert "Invalid template" in result.output
|
||||||
|
|
||||||
|
|
||||||
def test_export_directory_template_album_1():
|
def test_export_directory_template_album_1():
|
||||||
@@ -4203,8 +4203,8 @@ def test_export_filename_template_3():
|
|||||||
"{foo}-{original_filename}",
|
"{foo}-{original_filename}",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
assert result.exit_code == 2
|
assert result.exit_code != 0
|
||||||
assert "Error: Invalid template" in result.output
|
assert "Invalid template" in result.output
|
||||||
|
|
||||||
|
|
||||||
def test_export_album():
|
def test_export_album():
|
||||||
@@ -4896,6 +4896,7 @@ def test_export_force_update():
|
|||||||
in result.output
|
in result.output
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
"OSXPHOTOS_TEST_EXPORT" not in os.environ,
|
"OSXPHOTOS_TEST_EXPORT" not in os.environ,
|
||||||
reason="Skip if not running on author's personal library.",
|
reason="Skip if not running on author's personal library.",
|
||||||
@@ -6073,7 +6074,7 @@ def test_export_report_not_a_file():
|
|||||||
export, [os.path.join(cwd, CLI_PHOTOS_DB), ".", "-V", "--report", "."]
|
export, [os.path.join(cwd, CLI_PHOTOS_DB), ".", "-V", "--report", "."]
|
||||||
)
|
)
|
||||||
assert result.exit_code != 0
|
assert result.exit_code != 0
|
||||||
assert "Aborted!" in result.output
|
assert "report is a directory, must be file name" in result.output
|
||||||
|
|
||||||
|
|
||||||
def test_export_as_hardlink_download_missing():
|
def test_export_as_hardlink_download_missing():
|
||||||
@@ -6101,7 +6102,7 @@ def test_export_as_hardlink_download_missing():
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
assert result.exit_code != 0
|
assert result.exit_code != 0
|
||||||
assert "Aborted!" in result.output
|
assert "Incompatible export options" in result.output
|
||||||
|
|
||||||
|
|
||||||
def test_export_missing():
|
def test_export_missing():
|
||||||
@@ -6149,7 +6150,7 @@ def test_export_missing_not_download_missing():
|
|||||||
export, [os.path.join(cwd, CLI_PHOTOS_DB), ".", "-V", "--missing", "."]
|
export, [os.path.join(cwd, CLI_PHOTOS_DB), ".", "-V", "--missing", "."]
|
||||||
)
|
)
|
||||||
assert result.exit_code != 0
|
assert result.exit_code != 0
|
||||||
assert "Aborted!" in result.output
|
assert "Incompatible export options" in result.output
|
||||||
|
|
||||||
|
|
||||||
def test_export_cleanup():
|
def test_export_cleanup():
|
||||||
@@ -7466,7 +7467,7 @@ def test_bad_query_eval():
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
assert result.exit_code != 0
|
assert result.exit_code != 0
|
||||||
assert "Error: Invalid query-eval CRITERIA" in result.output
|
assert "Invalid query-eval CRITERIA" in result.output
|
||||||
|
|
||||||
|
|
||||||
def test_query_min_size_1():
|
def test_query_min_size_1():
|
||||||
|
|||||||
Reference in New Issue
Block a user