Feature add keep 730 (#740)

* Implemented #730, --keep

* Implemented #739, fixed --keep to accept relative paths

* Updated to macos-latest

Ref: https://github.com/actions/virtual-environments/issues/5583

* Release files for #730
This commit is contained in:
Rhet Turnbull
2022-07-27 08:14:00 -07:00
committed by GitHub
parent fb4329e0ed
commit 76625b9e84
24 changed files with 336 additions and 49 deletions

View File

@@ -1,3 +1,3 @@
""" version info """
__version__ = "0.50.9"
__version__ = "0.50.10"

View File

@@ -11,7 +11,7 @@ import shlex
import subprocess
import sys
import time
from typing import Dict
from typing import Iterable, List, Tuple
import click
import osxmetadata
@@ -546,6 +546,26 @@ from .verbose import get_verbose_console, time_stamp, verbose_print
"for example, your own scripts or other files. Be sure this is what you intend before using "
"--cleanup. Use --dry-run with --cleanup first if you're not certain.",
)
@click.option(
"--keep",
metavar="KEEP_PATH",
nargs=1,
multiple=True,
help="When used with --cleanup, prevents file or directory KEEP_PATH from being deleted "
"when cleanup is run. Use this if there are files in the export directory that you don't "
"want to be deleted when --cleanup is run. "
"KEEP_PATH may be a file path, e.g. '/Volumes/Photos/keep.jpg', "
"or a file path and wild card, e.g. '/Volumes/Photos/*.txt', "
"or a directory, e.g. '/Volumes/Photos/KeepMe'. "
"KEEP_PATH may be an absolute path or a relative path. "
"If it is relative, it must be relative to the export destination. "
"For example if export destination is `/Volumes/Photos` and you want to keep all `.txt` files, "
'you can specify `--keep "/Volumes/Photos/*.txt"` or `--keep "*.txt"`. '
"If wild card is used, KEEP_PATH must be enclosed in quotes to prevent the shell from expanding the wildcard, "
'e.g. `--keep "/Volumes/Photos/*.txt"`. '
"If KEEP_PATH is a directory, all files and directories contained in KEEP_PATH will be kept. "
"--keep may be repeated to keep additional files/directories.",
)
@click.option(
"--add-exported-to-album",
metavar="ALBUM",
@@ -757,6 +777,7 @@ def export(
is_reference,
jpeg_ext,
jpeg_quality,
keep,
keyword_template,
keyword,
label,
@@ -976,6 +997,7 @@ def export(
in_album = cfg.in_album
jpeg_ext = cfg.jpeg_ext
jpeg_quality = cfg.jpeg_quality
keep = (cfg.keep,)
keyword = cfg.keyword
keyword_template = cfg.keyword_template
label = cfg.label
@@ -1115,6 +1137,7 @@ def export(
("exiftool_option", ("exiftool")),
("ignore_signature", ("update", "force_update")),
("jpeg_quality", ("convert_to_jpeg")),
("keep", ("cleanup")),
("missing", ("download_missing", "use_photos_export")),
("only_new", ("update", "force_update")),
("append", ("report")),
@@ -1689,9 +1712,18 @@ def export(
+ [r[0] for r in results.error]
+ db_files
)
# if --report, add report file to keep list to prevent it from being deleted
if report:
all_files.append(report)
dirs_to_keep = []
if keep:
files_to_keep, dirs_to_keep = collect_files_to_keep(keep, dest)
all_files += files_to_keep
rich_echo(f"Cleaning up [filepath]{dest}")
cleaned_files, cleaned_dirs = cleanup_files(
dest, all_files, fileutil, verbose_=verbose_
dest, all_files, dirs_to_keep, fileutil, verbose_=verbose_
)
file_str = "files" if len(cleaned_files) != 1 else "file"
dir_str = "directories" if len(cleaned_dirs) != 1 else "directory"
@@ -2453,7 +2485,6 @@ def find_files_in_branch(pathname, filename):
# walk down the tree
for root, _, filenames in os.walk(pathname):
# for directory in directories:
# print(os.path.join(root, directory))
for fname in filenames:
if fname == filename and pathlib.Path(root) != pathname:
files.append(os.path.join(root, fname))
@@ -2470,14 +2501,43 @@ def find_files_in_branch(pathname, filename):
return files
def cleanup_files(dest_path, files_to_keep, fileutil, verbose_):
def collect_files_to_keep(
keep: Iterable[str], export_dir: str
) -> Tuple[List[str], List[str]]:
"""Collect all files to keep for --keep/--cleanup.
Args:
keep: Iterable of filepaths to keep; each path may be a filepath, a filepath/wildcard, or a directory path.
export_dir: the export directory which will be used to resolve paths when paths in keep are relative instead of absolute
Returns:
tuple of [files_to_keep], [dirs_to_keep]
"""
export_dir = pathlib.Path(export_dir)
keepers = []
for k in keep:
keeper = pathlib.Path(k).expanduser()
if not keeper.is_absolute():
# relative path: relative to export_dir
keeper = export_dir / keeper
if keeper.is_dir():
keepers.extend(keeper.glob("**/*"))
keepers.extend(keeper.parent.glob(keeper.name))
files_to_keep = [str(k) for k in keepers if k.is_file()]
dirs_to_keep = [str(k) for k in keepers if k.is_dir()]
return files_to_keep, dirs_to_keep
def cleanup_files(dest_path, files_to_keep, dirs_to_keep, fileutil, verbose_):
"""cleanup dest_path by deleting and files and empty directories
not in files_to_keep
Args:
dest_path: path to directory to clean
files_to_keep: list of full file paths to keep (not delete)
fileutile: FileUtil object
dirs_to_keep: list of full dir paths to keep (not delete if they are empty)
fileutil: FileUtil object
verbose_: verbose callable for printing verbose output
Returns:
tuple of (list of files deleted, list of directories deleted)
@@ -2497,6 +2557,8 @@ def cleanup_files(dest_path, files_to_keep, fileutil, verbose_):
deleted_dirs = []
# walk directory tree bottom up and verify contents are empty
for dirpath, _, _ in os.walk(dest_path, topdown=False):
if dirpath in dirs_to_keep:
continue
if not list(pathlib.Path(dirpath).glob("*")):
# directory and directory is empty
verbose_(f"Deleting empty directory {dirpath}")

Binary file not shown.