Performance improvements, partial for #591

This commit is contained in:
Rhet Turnbull 2022-01-25 20:37:58 -08:00
parent bd31120569
commit 3bc53fd92b
3 changed files with 31 additions and 67 deletions

View File

@ -79,7 +79,6 @@ from .sqlgrep import sqlgrep
from .uti import get_preferred_uti_extension
from .utils import (
expand_and_validate_filepath,
list_directory,
load_function,
normalize_fs_path,
)
@ -3071,8 +3070,7 @@ def export_photo_to_directory(
results.missing.append(str(pathlib.Path(dest_path) / filename))
return results
render_options = RenderOptions(
export_dir=export_dir, dest_path=dest_path)
render_options = RenderOptions(export_dir=export_dir, dest_path=dest_path)
tries = 0
while tries <= retry:
@ -3211,7 +3209,7 @@ def get_filenames_from_template(
filename=True,
edited_version=edited,
export_dir=export_dir,
dest_path=dest_path
dest_path=dest_path,
)
filenames, unmatched = photo.render_template(filename_template, options)
except ValueError as e:
@ -3277,8 +3275,7 @@ def get_dirnames_from_template(
elif directory:
# got a directory template, render it and check results are valid
try:
options = RenderOptions(
dirname=True, edited_version=edited)
options = RenderOptions(dirname=True, edited_version=edited)
dirnames, unmatched = photo.render_template(directory, options)
except ValueError as e:
raise click.BadOptionUsage(
@ -3607,7 +3604,7 @@ def write_finder_tags(
options = RenderOptions(
none_str=_OSXPHOTOS_NONE_SENTINEL,
path_sep="/",
export_dir=export_dir
export_dir=export_dir,
)
rendered, unmatched = photo.render_template(template_str, options)
except ValueError as e:
@ -3671,9 +3668,7 @@ def write_extended_attributes(
for xattr, template_str in xattr_template:
try:
options = RenderOptions(
none_str=_OSXPHOTOS_NONE_SENTINEL,
path_sep="/",
export_dir=export_dir
none_str=_OSXPHOTOS_NONE_SENTINEL, path_sep="/", export_dir=export_dir
)
rendered, unmatched = photo.render_template(template_str, options)
except ValueError as e:
@ -3740,8 +3735,7 @@ def run_post_command(
# some categories, like error, return a tuple of (file, error str)
if isinstance(f, tuple):
f = f[0]
render_options = RenderOptions(
export_dir=export_dir, filepath=f)
render_options = RenderOptions(export_dir=export_dir, filepath=f)
template = PhotoTemplate(photo, exiftool_path=exiftool_path)
command, _ = template.render(command_template, options=render_options)
command = command[0] if command else None

View File

@ -601,6 +601,7 @@ class PhotoExporter:
if dest_uuid != self.photo.uuid:
# not the right file, find the right one
glob_str = str(dest.parent / f"{dest.stem} (*{dest.suffix}")
# TODO: use the normalized code in utils
dest_files = glob.glob(glob_str)
for file_ in dest_files:
dest_uuid = export_db.get_uuid_for_file(file_)

View File

@ -20,7 +20,7 @@ from typing import Callable, List, Union
import CoreFoundation
import objc
from Foundation import NSFileManager, NSString
from Foundation import NSFileManager, NSPredicate, NSString
from ._constants import UNICODE_FORMAT
@ -265,7 +265,7 @@ def list_photo_libraries():
# On older MacOS versions, mdfind appears to ignore some libraries
# glob to find libraries in ~/Pictures then mdfind to find all the others
# TODO: make this more robust
lib_list = glob.glob(f"{str(pathlib.Path.home())}/Pictures/*.photoslibrary")
lib_list = glob.glob(f"{pathlib.Path.home()}/Pictures/*.photoslibrary")
# On older OS, may not get all libraries so make sure we get the last one
last_lib = get_last_library_path()
@ -284,35 +284,32 @@ def list_photo_libraries():
def normalize_fs_path(path: str) -> str:
"""Normalize filesystem paths with unicode in them"""
with objc.autorelease_pool():
normalized_path = NSString.fileSystemRepresentation(path)
return normalized_path.decode("utf8")
# macOS HFS+ uses NFD, APFS doesn't normalize but stick with NFD
# ref: https://eclecticlight.co/2021/05/08/explainer-unicode-normalization-and-apfs/
return unicodedata.normalize("NFD", path)
def findfiles(pattern, path_):
"""Returns list of filenames from path_ matched by pattern
def findfiles(pattern, path):
"""Returns list of filenames from path matched by pattern
shell pattern. Matching is case-insensitive.
If 'path_' is invalid/doesn't exist, returns []."""
if not os.path.isdir(path_):
if not os.path.isdir(path):
return []
# See: https://gist.github.com/techtonik/5694830
# paths need to be normalized for unicode as filesystem returns unicode in NFD form
pattern = normalize_fs_path(pattern)
rule = re.compile(fnmatch.translate(pattern), re.IGNORECASE)
files = list_directory(path_)
files = os.listdir(path)
return [name for name in files if rule.match(name)]
def list_directory(directory_path: str) -> List[str]:
"""List directory contents using NSFileManager"""
"""[[NSFileManager defaultManager] contentsOfDirectoryAtPath:@"directoryName" error:nil]"""
with objc.autorelease_pool():
manager = NSFileManager.defaultManager()
contents, error = manager.contentsOfDirectoryAtPath_error_(directory_path, None)
if error:
raise OSError(f"Error listing directory {directory_path}: {error}")
return [str(path) for path in contents]
def list_directory_startswith(directory_path: str, startswith: str) -> List[str]:
"""List directory contents and return list of files starting with startswith; returns [] if directory doesn't exist"""
if not os.path.isdir(directory_path):
return []
startswith = normalize_fs_path(startswith)
files = [normalize_fs_path(f) for f in os.listdir(directory_path)]
return [f for f in files if f.startswith(startswith)]
def _open_sql_file(dbname):
@ -353,44 +350,16 @@ def _db_is_locked(dbname):
return locked
# OSXPHOTOS_XATTR_UUID = "com.osxphotos.uuid"
# def get_uuid_for_file(filepath):
# """ returns UUID associated with an exported file
# filepath: path to exported photo
# """
# attr = xattr.xattr(filepath)
# try:
# uuid_bytes = attr[OSXPHOTOS_XATTR_UUID]
# uuid_str = uuid_bytes.decode('utf-8')
# except KeyError:
# uuid_str = None
# return uuid_str
# def set_uuid_for_file(filepath, uuid):
# """ sets the UUID associated with an exported file
# filepath: path to exported photo
# uuid: uuid string for photo
# """
# if not os.path.exists(filepath):
# raise FileNotFoundError(f"Missing file: {filepath}")
# attr = xattr.xattr(filepath)
# uuid_bytes = bytes(uuid, 'utf-8')
# attr.set(OSXPHOTOS_XATTR_UUID, uuid_bytes)
def normalize_unicode(value):
"""normalize unicode data"""
if value is not None:
if isinstance(value, (tuple, list)):
return tuple(unicodedata.normalize(UNICODE_FORMAT, v) for v in value)
elif isinstance(value, str):
return unicodedata.normalize(UNICODE_FORMAT, value)
else:
return value
else:
if value is None:
return None
if isinstance(value, (tuple, list)):
return tuple(unicodedata.normalize(UNICODE_FORMAT, v) for v in value)
elif isinstance(value, str):
return unicodedata.normalize(UNICODE_FORMAT, value)
else:
return value
def increment_filename_with_count(
@ -411,7 +380,7 @@ def increment_filename_with_count(
Note: This obviously is subject to race condition so using with caution.
"""
dest = filepath if isinstance(filepath, pathlib.Path) else pathlib.Path(filepath)
dest_files = findfiles(f"{dest.stem}*", str(dest.parent))
dest_files = list_directory_startswith(str(dest.parent), dest.stem)
dest_files = [pathlib.Path(f).stem.lower() for f in dest_files]
dest_new = f"{dest.stem} ({count})" if count else dest.stem
dest_new = normalize_fs_path(dest_new)