Skip to content

Commit

Permalink
resolved remarks from AndreyNikiforov. Thanks for reviewing!
Browse files Browse the repository at this point in the history
  • Loading branch information
fver committed Sep 25, 2023
1 parent 177705a commit 44229e4
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 144 deletions.
285 changes: 143 additions & 142 deletions src/icloudpd/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ def main(

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

if auto_delete and delete_after_download:
Expand Down Expand Up @@ -759,157 +759,158 @@ def core(
return 1

download_photo = downloader(icloud)

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

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

# Access to the selected library. Defaults to the primary photos object.
library_object = icloud.photos
else:
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:
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
logger.error("error?? %s", err)
return 1

if list_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"
logger.debug(
"Looking up all photos%s from album %s...",
videos_phrase,
album
)

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:
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
logger.error("error?? %s", err)
return 1

if list_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"
logger.debug(
"Looking up all photos%s from album %s...",
videos_phrase,
album
)
session_exception_handler = session_error_handle_builder(
logger, icloud)
internal_error_handler = internal_error_handle_builder(logger)

error_handler = compose_handlers([session_exception_handler, internal_error_handler
])

photos.exception_handler = error_handler

photos_count = len(photos)

# Optional: Only download the x most recent photos.
if recent is not None:
photos_count = recent
photos = itertools.islice(photos, recent)

tqdm_kwargs = {"total": photos_count}

if until_found is not None:
del tqdm_kwargs["total"]
photos_count = "???"
# ensure photos iterator doesn't have a known length
photos = (p for p in photos)

# Use only ASCII characters in progress bar
tqdm_kwargs["ascii"] = True

tqdm_kwargs["leave"] = False
tqdm_kwargs["dynamic_ncols"] = True

# Skip the one-line progress bar if we're only printing the filenames,
# or if the progress bar is explicitly disabled,
# or if this is not a terminal (e.g. cron or piping output to file)
skip_bar = not os.environ.get("FORCE_TQDM") and (
only_print_filenames or no_progress_bar or not sys.stdout.isatty())
if skip_bar:
photos_enumerator = photos
# logger.set_tqdm(None)
else:
photos_enumerator = tqdm(photos, **tqdm_kwargs)
# logger.set_tqdm(photos_enumerator)

plural_suffix = "" if photos_count == 1 else "s"
video_suffix = ""
photos_count_str = "the first" if photos_count == 1 else photos_count
if not skip_videos:
video_suffix = " or video" if photos_count == 1 else " and videos"
logger.info(
("Downloading %s %s" +
" photo%s%s to %s ..."),
photos_count_str,
size,
plural_suffix,
video_suffix,
directory
)

session_exception_handler = session_error_handle_builder(
logger, icloud)
internal_error_handler = internal_error_handle_builder(logger)

error_handler = compose_handlers([session_exception_handler, internal_error_handler
])

photos.exception_handler = error_handler

photos_count = len(photos)

# Optional: Only download the x most recent photos.
if recent is not None:
photos_count = recent
photos = itertools.islice(photos, recent)

tqdm_kwargs = {"total": photos_count}

if until_found is not None:
del tqdm_kwargs["total"]
photos_count = "???"
# ensure photos iterator doesn't have a known length
photos = (p for p in photos)

# Use only ASCII characters in progress bar
tqdm_kwargs["ascii"] = True

tqdm_kwargs["leave"] = False
tqdm_kwargs["dynamic_ncols"] = True

# Skip the one-line progress bar if we're only printing the filenames,
# or if the progress bar is explicitly disabled,
# or if this is not a terminal (e.g. cron or piping output to file)
skip_bar = not os.environ.get("FORCE_TQDM") and (
only_print_filenames or no_progress_bar or not sys.stdout.isatty())
if skip_bar:
photos_enumerator = photos
# logger.set_tqdm(None)
else:
photos_enumerator = tqdm(photos, **tqdm_kwargs)
# logger.set_tqdm(photos_enumerator)

plural_suffix = "" if photos_count == 1 else "s"
video_suffix = ""
photos_count_str = "the first" if photos_count == 1 else photos_count
if not skip_videos:
video_suffix = " or video" if photos_count == 1 else " and videos"
logger.info(
("Downloading %s %s" +
" photo%s%s to %s ..."),
photos_count_str,
size,
plural_suffix,
video_suffix,
directory
)
consecutive_files_found = Counter(0)

consecutive_files_found = Counter(0)
def should_break(counter):
"""Exit if until_found condition is reached"""
return until_found is not None and counter.value() >= until_found

def should_break(counter):
"""Exit if until_found condition is reached"""
return until_found is not None and counter.value() >= until_found
photos_iterator = iter(photos_enumerator)
while True:
try:
if should_break(consecutive_files_found):
logger.info(
"Found %s consecutive previously downloaded photos. Exiting",
until_found
)
break
item = next(photos_iterator)
if download_photo(
consecutive_files_found,
item) and delete_after_download:

photos_iterator = iter(photos_enumerator)
while True:
try:
if should_break(consecutive_files_found):
logger.info(
"Found %s consecutive previously downloaded photos. Exiting",
until_found
)
def delete_cmd():
delete_local = delete_photo_dry_run if dry_run else delete_photo
delete_local(logger, icloud, item)

retrier(delete_cmd, error_handler)

except StopIteration:
break
item = next(photos_iterator)
if download_photo(
consecutive_files_found,
item) and delete_after_download:

def delete_cmd():
delete_local = delete_photo_dry_run if dry_run else delete_photo
delete_local(logger, icloud, item)

retrier(delete_cmd, error_handler)

except StopIteration:
break

if only_print_filenames:
return 0

logger.info("All photos have been downloaded")

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

if watch_interval: # pragma: no cover
logger.info(f"Waiting for {watch_interval} sec...")
interval = range(1, watch_interval)
for _ in interval if skip_bar else tqdm(
interval,
desc="Waiting...",
ascii=True,
leave=False,
dynamic_ncols=True
):
time.sleep(1)
else:
break # pragma: no cover

if only_print_filenames:
return 0

logger.info("All photos have been downloaded")

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

if watch_interval: # pragma: no cover
logger.info(f"Waiting for {watch_interval} sec...")
interval = range(1, watch_interval)
for _ in interval if skip_bar else tqdm(
interval,
desc="Waiting...",
ascii=True,
leave=False,
dynamic_ncols=True
):
time.sleep(1)
else:
break # pragma: no cover

return 0
4 changes: 2 additions & 2 deletions tests/vcr_cassettes/2sa_flow_valid_device.yml
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,7 @@ interactions:
status: {code: 200, message: OK}
- request:
body: '{"batch": [{"resultsLimit": 1, "query": {"filterBy": {"fieldName": "indexCountID",
"fieldValue": {"type": "STRING_LIST", "value": ["CPLAssetByAssetDateWithout"]}, "comparator":
"fieldValue": {"type": "STRING_LIST", "value": ["CPLAssetByAssetDateWithoutHiddenOrDeleted"]}, "comparator":
"IN"}, "recordType": "HyperionIndexCountLookup"}, "zoneWide": true, "zoneID":
{"zoneName": "PrimarySync", "ownerRecordName": "_bfc6dbbcc77b03e6cebefd28a28f7e2f",
"zoneType": "REGULAR_CUSTOM_ZONE"}}]}'
Expand All @@ -901,7 +901,7 @@ interactions:
uri: https://p10-ckdatabasews.icloud.com/database/1/com.apple.photos.cloud/production/private/internal/records/query/batch?clientBuildNumber=17DHotfix5&clientMasteringNumber=17DHotfix5&ckjsBuildVersion=17DProjectDev77&ckjsVersion=2.0.5&clientId=DE309E26-942E-11E8-92F5-14109FE0B321&dsid=185776146&remapEnums=True&getCurrentSyncToken=True
response:
body: {string: "{\n \"batch\" : [ {\n \"records\" : [ {\n \"recordName\"
: \"CPLAssetByAssetDateWithout\",\n \"recordType\" : \"IndexCountResult\",\n
: \"CPLAssetByAssetDateWithoutHiddenOrDeleted\",\n \"recordType\" : \"IndexCountResult\",\n
\ \"fields\" : {\n \"itemCount\" : {\n \"value\" : 33161,\n
\ \"type\" : \"INT64\"\n }\n },\n \"pluginFields\"
: { },\n \"recordChangeTag\" : \"0\",\n \"created\" : {\n \"timestamp\"
Expand Down

0 comments on commit 44229e4

Please sign in to comment.