Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

- fix: fix ignored photos with --delete-after-download or --keep-icloud-recent-days [#616](https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/616)
- fix: timeout set to 30 seconds for HTTP requests [#793](https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/793)

## 1.27.4 (2025-04-15)
Expand Down
2 changes: 2 additions & 0 deletions src/icloudpd/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,8 @@ def should_break(counter: Counter) -> bool:
)

retrier(delete_local, error_handler)
if photos.direction != "DESCENDING":
photos.increment_offset(-1)

photos_counter += 1
status_exchange.get_progress().photos_counter = photos_counter
Expand Down
27 changes: 14 additions & 13 deletions src/pyicloud_ipd/services/photos.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,10 @@ def __init__(self, service:PhotosService, service_endpoint: str, name: str, list
self.list_type = list_type
self.obj_type = obj_type
self.direction = direction
if self.direction == "DESCENDING":
self.offset = len(self) - 1
else:
self.offset = 0
self.query_filter = query_filter
self.page_size = page_size
self.exception_handler: Optional[Callable[[Exception, int], None]] = None
Expand Down Expand Up @@ -386,30 +390,25 @@ def __len__(self) -> int:

# Perform the request in a separate method so that we
# can mock it to test session errors.
def photos_request(self, offset: int) -> Response:
def photos_request(self) -> Response:
url = ('%s/records/query?' % self.service_endpoint) + \
urlencode(self.service.params)
return self.service.session.post(
url,
data=json.dumps(self._list_query_gen(
offset, self.list_type, self.direction,
self.offset, self.list_type, self.direction,
self.query_filter)),
headers={'Content-type': 'text/plain'}
)


@property
def photos(self) -> Generator["PhotoAsset", Any, None]:
if self.direction == "DESCENDING":
offset = len(self) - 1
else:
offset = 0

exception_retries = 0

while(True):
try:
request = self.photos_request(offset)
request = self.photos_request()
except PyiCloudAPIResponseException as ex:
if self.exception_handler:
exception_retries += 1
Expand Down Expand Up @@ -446,18 +445,20 @@ def photos(self) -> Generator["PhotoAsset", Any, None]:

master_records_len = len(master_records)
if master_records_len:
if self.direction == "DESCENDING":
offset = offset - master_records_len
else:
offset = offset + master_records_len

for master_record in master_records:
record_name = master_record['recordName']
yield PhotoAsset(self.service, master_record,
asset_records[record_name])
self.increment_offset(1)
else:
break

def increment_offset(self, value: int) -> None:
if self.direction == "DESCENDING":
self.offset -= value
else:
self.offset += value

def _count_query_gen(self, obj_type: str) -> Dict[str, Any]:
query = {
u'batch': [{
Expand Down
4 changes: 2 additions & 2 deletions tests/test_download_photos.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ def mocked_authenticate(self: PyiCloudService) -> None:
def test_handle_session_error_during_photo_iteration(self) -> None:
base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3])

def mock_raise_response_error(_offset: int) -> NoReturn:
def mock_raise_response_error() -> NoReturn:
raise PyiCloudAPIResponseException("Invalid global session", "100")

with mock.patch("time.sleep") as sleep_mock: # noqa: SIM117
Expand Down Expand Up @@ -1760,7 +1760,7 @@ def mock_raise_response_error(_arg: Any) -> NoReturn:
def test_handle_internal_error_during_photo_iteration(self) -> None:
base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3])

def mock_raise_response_error(_offset: int) -> NoReturn:
def mock_raise_response_error() -> NoReturn:
raise PyiCloudAPIResponseException("INTERNAL_ERROR", "INTERNAL_ERROR")

with mock.patch("time.sleep") as sleep_mock: # noqa: SIM117
Expand Down
4 changes: 2 additions & 2 deletions tests/test_download_photos_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ def mocked_authenticate(self: PyiCloudService) -> None:
def test_handle_session_error_during_photo_iteration_name_id7(self) -> None:
base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3])

def mock_raise_response_error(_offset: int) -> NoReturn:
def mock_raise_response_error() -> NoReturn:
raise PyiCloudAPIResponseException("Invalid global session", "100")

with mock.patch("time.sleep") as sleep_mock: # noqa: SIM117
Expand Down Expand Up @@ -1744,7 +1744,7 @@ def mock_raise_response_error(_arg: Any) -> NoReturn:
def test_handle_internal_error_during_photo_iteration_name_id7(self) -> None:
base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3])

def mock_raise_response_error(_offset: int) -> NoReturn:
def mock_raise_response_error() -> NoReturn:
raise PyiCloudAPIResponseException("INTERNAL_ERROR", "INTERNAL_ERROR")

with mock.patch("time.sleep") as sleep_mock: # noqa: SIM117
Expand Down
Loading