11"""Handles file downloads with retries and error handling"""
22
3+ import base64
34import datetime
45import logging
56import os
67import time
8+ from functools import partial
79
810from requests import Response
911from tzlocal import get_localzone
@@ -68,11 +70,14 @@ def mkdirs_for_path_dry_run(logger: logging.Logger, download_path: str) -> bool:
6870
6971
7072def download_response_to_path (
71- _logger : logging .Logger , response : Response , download_path : str , created_date : datetime .datetime
73+ response : Response ,
74+ temp_download_path : str ,
75+ append_mode : bool ,
76+ download_path : str ,
77+ created_date : datetime .datetime ,
7278) -> bool :
7379 """Saves response content into file with desired created date"""
74- temp_download_path = download_path + ".part"
75- with open (temp_download_path , "wb" ) as file_obj :
80+ with open (temp_download_path , ("ab" if append_mode else "wb" )) as file_obj :
7681 for chunk in response .iter_content (chunk_size = 1024 ):
7782 if chunk :
7883 file_obj .write (chunk )
@@ -84,6 +89,8 @@ def download_response_to_path(
8489def download_response_to_path_dry_run (
8590 logger : logging .Logger ,
8691 _response : Response ,
92+ _temp_download_path : str ,
93+ _append_mode : bool ,
8794 download_path : str ,
8895 _created_date : datetime .datetime ,
8996) -> bool :
@@ -107,22 +114,36 @@ def download_media(
107114 """Download the photo to path, with retries and error handling"""
108115
109116 mkdirs_local = mkdirs_for_path_dry_run if dry_run else mkdirs_for_path
110- download_local = download_response_to_path_dry_run if dry_run else download_response_to_path
111-
112117 if not mkdirs_local (logger , download_path ):
113118 return False
114119
120+ checksum = base64 .b64decode (version .checksum )
121+ checksum32 = base64 .b32encode (checksum ).decode ()
122+ download_dir = os .path .dirname (download_path )
123+ temp_download_path = os .path .join (download_dir , checksum32 ) + ".part"
124+
125+ download_local = (
126+ partial (download_response_to_path_dry_run , logger ) if dry_run else download_response_to_path
127+ )
128+
115129 retries = 0
116130 while True :
117131 try :
118- photo_response = photo .download (version .url )
119- if photo_response :
120- return download_local (logger , photo_response , download_path , photo .created )
121-
122- logger .error (
123- "Could not find URL to download %s for size %s" , version .filename , size .value
124- )
125- break
132+ append_mode = os .path .exists (temp_download_path )
133+ current_size = os .path .getsize (temp_download_path ) if append_mode else 0
134+ if append_mode :
135+ logger .debug (f"Resuming downloading of { download_path } from { current_size } " )
136+
137+ photo_response = photo .download (version .url , current_size )
138+ if photo_response .ok :
139+ return download_local (
140+ photo_response , temp_download_path , append_mode , download_path , photo .created
141+ )
142+ else :
143+ logger .error (
144+ "Could not find URL to download %s for size %s" , version .filename , size .value
145+ )
146+ break
126147
127148 except PyiCloudAPIResponseException as ex :
128149 if "Invalid global session" in str (ex ):
0 commit comments