Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gpmc/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def main():
parser.add_argument("--recursive", action="store_true", help="Scan the directory recursively.")
parser.add_argument("--threads", type=int, default=1, help="Number of threads to run uploads with. Defaults to 1.")
parser.add_argument("--force-upload", action="store_true", help="Upload files regardless of their presence in Google Photos (determined by hash).")
parser.add_argument("--delete-from-host", action="store_true", help="Delete uploaded files from source path.")
parser.add_argument("--delete-from-host", action="store_true", help="Delete each file immediately after its individual upload completes.")
parser.add_argument("--use-quota", action="store_true", help="Uploaded files will count against your Google Photos storage quota.")
parser.add_argument("--saver", action="store_true", help="Upload files in storage saver quality.")
parser.add_argument("--timeout", type=int, default=30, help=f"Requests timeout, seconds. Defaults to {DEFAULT_TIMEOUT}.")
Expand Down
21 changes: 13 additions & 8 deletions gpmc/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def _handle_auth_data(self, auth_data: str | None) -> str:

raise ValueError("`GP_AUTH_DATA` environment variable not set. Create it or provide `auth_data` as an argument.")

def _upload_file(self, file_path: str | Path, hash_value: bytes | str, progress: Progress, force_upload: bool, use_quota: bool, saver: bool) -> dict[str, str]:
def _upload_file(self, file_path: str | Path, hash_value: bytes | str, progress: Progress, force_upload: bool, use_quota: bool, saver: bool, delete_from_host: bool = False) -> dict[str, str]:
"""
Upload a single file to Google Photos.

Expand All @@ -101,6 +101,7 @@ def _upload_file(self, file_path: str | Path, hash_value: bytes | str, progress:
force_upload: Whether to upload the file even if it's already present in Google Photos.
use_quota: Uploaded files will count against your Google Photos storage quota.
saver: Upload files in storage saver quality.
delete_from_host: Whether to delete the file from host immediately after successful upload.

Returns:
dict[str, str]: A dictionary mapping the absolute file path to its Google Photos media key.
Expand Down Expand Up @@ -147,6 +148,12 @@ def _upload_file(self, file_path: str | Path, hash_value: bytes | str, progress:
model=model,
quality=quality,
)

# Delete file immediately after successful upload if requested
if delete_from_host:
self.logger.info(f"{file_path} deleting from host")
os.remove(file_path)

return {file_path.absolute().as_posix(): media_key}
finally:
progress.update(file_progress_id, visible=False)
Expand Down Expand Up @@ -270,7 +277,7 @@ def upload(
threads: Number of concurrent upload threads for multiple files. Defaults to 1.
force_upload: Whether to upload files even if they're already present in
Google Photos (based on hash). Defaults to False.
delete_from_host: Whether to delete the file from the host after successful upload.
delete_from_host: Whether to delete each file immediately after its individual upload completes.
Defaults to False.
filter_exp: The filter expression to match against filenames or paths.
filter_exclude: If True, exclude files matching the filter.
Expand Down Expand Up @@ -306,15 +313,12 @@ def upload(
force_upload=force_upload,
use_quota=use_quota,
saver=saver,
delete_from_host=delete_from_host,
)

if album_name:
self._handle_album_creation(results, album_name, show_progress)

if delete_from_host:
for file_path, _ in results.items():
self.logger.info(f"{file_path} deleting from host")
os.remove(file_path)
return results

def _handle_target_input(
Expand Down Expand Up @@ -426,7 +430,7 @@ def _calculate_hash(self, file_path: Path, progress: Progress) -> tuple[Path, by
finally:
progress.update(hash_calc_progress_id, visible=False)

def _upload_concurrently(self, path_hash_pairs: Mapping[Path, bytes | str], threads: int, show_progress: bool, force_upload: bool, use_quota: bool, saver: bool) -> dict[str, str]:
def _upload_concurrently(self, path_hash_pairs: Mapping[Path, bytes | str], threads: int, show_progress: bool, force_upload: bool, use_quota: bool, saver: bool, delete_from_host: bool) -> dict[str, str]:
"""
Upload files concurrently to Google Photos.

Expand All @@ -437,6 +441,7 @@ def _upload_concurrently(self, path_hash_pairs: Mapping[Path, bytes | str], thre
force_upload: Upload even if file exists in Google Photos.
use_quota: Count uploads against storage quota.
saver: Upload in storage saver quality.
delete_from_host: Delete each file immediately after successful upload.

Returns:
dict[str, str]: Dictionary mapping file paths to media keys.
Expand Down Expand Up @@ -470,7 +475,7 @@ def _upload_concurrently(self, path_hash_pairs: Mapping[Path, bytes | str], thre
overall_task_id = overall_progress.add_task("Errors: 0", total=len(path_hash_pairs.keys()), visible=show_progress)
with context:
with ThreadPoolExecutor(max_workers=threads) as executor:
futures = {executor.submit(self._upload_file, path, hash_value, progress=file_progress, force_upload=force_upload, use_quota=use_quota, saver=saver): (path, hash_value) for path, hash_value in path_hash_pairs.items()}
futures = {executor.submit(self._upload_file, path, hash_value, progress=file_progress, force_upload=force_upload, use_quota=use_quota, saver=saver, delete_from_host=delete_from_host): (path, hash_value) for path, hash_value in path_hash_pairs.items()}
for future in as_completed(futures):
file = futures[future]
try:
Expand Down