Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merged the (old) shared library support into the new structure #678

Merged
merged 6 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
11 changes: 9 additions & 2 deletions src/icloudpd/autodelete.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,27 @@ def delete_file(logger, path) -> bool:
logger.info("Deleted %s", path)
return True


def delete_file_dry_run(logger, path) -> bool:
""" Dry run deletion of files """
logger.info("[DRY RUN] Would delete %s", path)
return True

def autodelete_photos(logger, dry_run, icloud, folder_structure, directory):

def autodelete_photos(
logger,
dry_run,
library_object,
folder_structure,
directory):
"""
Scans the "Recently Deleted" folder and deletes any matching files
from the download directory.
(I.e. If you delete a photo on your phone, it's also deleted on your computer.)
"""
logger.info("Deleting any files found in 'Recently Deleted'...")

recently_deleted = icloud.photos.albums["Recently Deleted"]
recently_deleted = library_object.albums["Recently Deleted"]

for media in recently_deleted:
try:
Expand Down
59 changes: 45 additions & 14 deletions src/icloudpd/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@
help="Lists the available albums",
is_flag=True,
)
@click.option(
"--library",
help="Library to download (default: Personal Library)",
metavar="<library>",
default="PrimarySync",
)
@click.option(
"--list-libraries",
help="Lists the available libraries",
is_flag=True,
)
@click.option(
"--skip-videos",
help="Don't download any videos (default: Download all photos and videos)",
Expand Down Expand Up @@ -122,12 +133,13 @@
+ "(Does not download or delete any files.)",
is_flag=True,
)
@click.option("--folder-structure",
help="Folder structure (default: {:%Y/%m/%d}). "
"If set to 'none' all photos will just be placed into the download directory",
metavar="<folder_structure>",
default="{:%Y/%m/%d}",
)
@click.option(
"--folder-structure",
help="Folder structure (default: {:%Y/%m/%d}). "
"If set to 'none' all photos will just be placed into the download directory",
metavar="<folder_structure>",
default="{:%Y/%m/%d}",
)
@click.option(
"--set-exif-datetime",
help="Write the DateTimeOriginal exif tag from file creation date, " +
Expand Down Expand Up @@ -235,6 +247,8 @@ def main(
until_found,
album,
list_albums,
library,
list_libraries,
skip_videos,
skip_live_photos,
force_size,
Expand Down Expand Up @@ -281,7 +295,7 @@ def main(
with logging_redirect_tqdm():

# check required directory param only if not list albums
if not list_albums and not directory:
if not list_albums and not list_libraries and not directory:
print('--directory or --list-albums are required')
sys.exit(2)

Expand Down Expand Up @@ -318,6 +332,8 @@ def main(
until_found,
album,
list_albums,
library,
list_libraries,
skip_videos,
auto_delete,
only_print_filenames,
Expand Down Expand Up @@ -691,6 +707,8 @@ def core(
until_found,
album,
list_albums,
library,
list_libraries,
skip_videos,
auto_delete,
only_print_filenames,
Expand Down Expand Up @@ -741,28 +759,41 @@ def core(
return 1

download_photo = downloader(icloud)
if list_libraries:
libraries_dict = icloud.photos.libraries
library_names = libraries_dict.keys()
print(*library_names, sep="\n")
sys.exit(0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pls change to return, so program exists are only at top level

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adapted, in a separate commit - extra return triggered a lint warning, so did some refactor


while True:
# Access to the selected library. Defaults to the primary photos object.
library_object = icloud.photos

while True:
# Default album is "All Photos", so this is the same as
# calling `icloud.photos.all`.
# After 6 or 7 runs within 1h Apple blocks the API for some time. In that
# case exit.
try:
photos = icloud.photos.albums[album]
if library:
try:
library_object = icloud.photos.libraries[library]
except KeyError:
logger.error("Unknown library: %s", library)
return 1
photos = library_object.albums[album]
except PyiCloudAPIResponseError as err:
# For later: come up with a nicer message to the user. For now take the
# exception text
print(err)
logger.error("error?? %s", err)
return 1

if list_albums:
albums_dict = icloud.photos.albums
print("Albums:")
albums_dict = library_object.albums
albums = albums_dict.values() # pragma: no cover
album_titles = [str(a) for a in albums]
print(*album_titles, sep="\n")
return 0

directory = os.path.normpath(directory)

videos_phrase = "" if skip_videos else " and videos"
Expand Down Expand Up @@ -864,7 +895,7 @@ def delete_cmd():
logger.info("All photos have been downloaded")

if auto_delete:
autodelete_photos(logger, dry_run, icloud,
autodelete_photos(logger, dry_run, library_object,
folder_structure, directory)

if watch_interval: # pragma: no cover
Expand All @@ -879,6 +910,6 @@ def delete_cmd():
):
time.sleep(1)
else:
break
break # pragma: no cover

return 0
24 changes: 19 additions & 5 deletions src/icloudpd/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# Import the constants object so that we can mock WAIT_SECONDS in tests
from icloudpd import constants


def update_mtime(created: datetime.datetime, download_path):
"""Set the modification time of the downloaded file to the photo creation date"""
if created:
Expand All @@ -31,13 +32,14 @@ def set_utime(download_path, created_date):
ctime = time.mktime(created_date.timetuple())
os.utime(download_path, (ctime, ctime))


def mkdirs_for_path(logger, download_path: str) -> bool:
""" Creates hierarchy of folders for file path if it needed """
try:
# get back the directory for the file to be downloaded and create it if
# not there already
download_dir = os.path.dirname(download_path)
os.makedirs(name = download_dir, exist_ok=True)
os.makedirs(name=download_dir, exist_ok=True)
return True
except OSError:
logger.error(
Expand All @@ -46,6 +48,7 @@ def mkdirs_for_path(logger, download_path: str) -> bool:
)
return False


def mkdirs_for_path_dry_run(logger, download_path: str) -> bool:
""" DRY Run for Creating hierarchy of folders for file path """
download_dir = os.path.dirname(download_path)
Expand All @@ -56,6 +59,7 @@ def mkdirs_for_path_dry_run(logger, download_path: str) -> bool:
)
return True


def download_response_to_path(
_logger,
response,
Expand All @@ -71,6 +75,7 @@ def download_response_to_path(
update_mtime(created_date, download_path)
return True


def download_response_to_path_dry_run(
logger,
_response,
Expand All @@ -84,7 +89,15 @@ def download_response_to_path_dry_run(
return True

# pylint: disable-msg=too-many-arguments
def download_media(logger, dry_run, icloud, photo, download_path, size) -> bool:


def download_media(
logger,
dry_run,
icloud,
photo,
download_path,
size) -> bool:
"""Download the photo to path, with retries and error handling"""

mkdirs_local = mkdirs_for_path_dry_run if dry_run else mkdirs_for_path
Expand All @@ -97,7 +110,8 @@ def download_media(logger, dry_run, icloud, photo, download_path, size) -> bool:
try:
photo_response = photo.download(size)
if photo_response:
return download_local(logger, photo_response, download_path, photo.created)
return download_local(
logger, photo_response, download_path, photo.created)

logger.error(
"Could not find URL to download %s for size %s",
Expand Down Expand Up @@ -132,13 +146,13 @@ def download_media(logger, dry_run, icloud, photo, download_path, size) -> bool:
"IOError while writing file to %s. " +
"You might have run out of disk space, or the file " +
"might be too large for your OS. " +
"Skipping this file...",
"Skipping this file...",
download_path
)
break
else:
logger.error(
"Could not download %s. Please try again later.",
"Could not download %s. Please try again later.",
photo.filename,
)

Expand Down
Loading