@@ -57,12 +57,18 @@ static std::string read_file(const std::string & fname) {
5757}
5858
5959static  void  write_file (const  std::string & fname, const  std::string & content) {
60-     std::ofstream file (fname);
60+     const  std::string fname_tmp = fname + " .tmp"  ;
61+     std::ofstream     file (fname_tmp);
6162    if  (!file) {
6263        throw  std::runtime_error (string_format (" error: failed to open file '%s'\n "  , fname.c_str ()));
6364    }
6465    file << content;
6566    file.close ();
67+ 
68+     //  Makes write atomic
69+     if  (rename (fname_tmp.c_str (), fname.c_str ()) != 0 ) {
70+         LOG_ERR (" %s: unable to rename file: %s to %s\n "  , __func__, fname_tmp.c_str (), fname.c_str ());
71+     }
6672}
6773
6874common_arg & common_arg::set_examples (std::initializer_list<enum  llama_example> examples) {
@@ -220,12 +226,24 @@ struct curl_slist_ptr {
220226#define  CURL_MAX_RETRY  3 
221227#define  CURL_RETRY_DELAY_SECONDS  2 
222228
223- static  bool  curl_perform_with_retry (const  std::string & url, CURL * curl, int  max_attempts, int  retry_delay_seconds, const  char  * method_name) {
229+ static  bool  curl_perform_with_retry (const  std::string & url,
230+                                     CURL *              curl,
231+                                     int                  max_attempts,
232+                                     int                  retry_delay_seconds,
233+                                     const  char  *        method_name,
234+                                     const  std::string & path_temporary = " "  ) {
224235    int  remaining_attempts = max_attempts;
225236
226237    while  (remaining_attempts > 0 ) {
227238        LOG_INF (" %s: %s %s (attempt %d of %d)...\n "  , __func__ , method_name, url.c_str (), max_attempts - remaining_attempts + 1 , max_attempts);
228239
240+         if  (std::filesystem::exists (path_temporary)) {
241+             const  long  partial_size = static_cast <long >(std::filesystem::file_size (path_temporary));
242+             LOG_INF (" %s: server supports range requests, resuming download from byte %ld\n "  , __func__, partial_size);
243+             const  std::string range_str = std::to_string (partial_size) + " -"  ;
244+             curl_easy_setopt (curl, CURLOPT_RANGE, range_str.c_str ());
245+         }
246+ 
229247        CURLcode res = curl_easy_perform (curl);
230248        if  (res == CURLE_OK) {
231249            return  true ;
@@ -246,15 +264,14 @@ static bool curl_perform_with_retry(const std::string & url, CURL * curl, int ma
246264
247265//  download one single file from remote URL to local path
248266static  bool  common_download_file_single (const  std::string & url, const  std::string & path, const  std::string & bearer_token, bool  offline) {
249-     //  Check if the file already exists locally
250-     auto  file_exists = std::filesystem::exists (path);
251- 
252267    //  If the file exists, check its JSON metadata companion file.
253268    std::string metadata_path = path + " .json"  ;
254269    nlohmann::json metadata; //  TODO @ngxson : get rid of this json, use regex instead
255270    std::string etag;
256271    std::string last_modified;
257272
273+     //  Check if the file already exists locally
274+     auto  file_exists = std::filesystem::exists (path);
258275    if  (file_exists) {
259276        if  (offline) {
260277            LOG_INF (" %s: using cached file (offline mode): %s\n "  , __func__, path.c_str ());
@@ -289,6 +306,7 @@ static bool common_download_file_single(const std::string & url, const std::stri
289306    struct  common_load_model_from_url_headers  {
290307        std::string etag;
291308        std::string last_modified;
309+         std::string accept_ranges;
292310    };
293311
294312    common_load_model_from_url_headers headers;
@@ -328,7 +346,7 @@ static bool common_download_file_single(const std::string & url, const std::stri
328346        static  std::regex header_regex (" ([^:]+): (.*)\r\n "  );
329347        static  std::regex etag_regex (" ETag"  , std::regex_constants::icase);
330348        static  std::regex last_modified_regex (" Last-Modified"  , std::regex_constants::icase);
331- 
349+          static  std::regex  accept_ranges_regex ( " Accept-Ranges " , std::regex_constants::icase); 
332350        std::string header (buffer, n_items);
333351        std::smatch match;
334352        if  (std::regex_match (header, match, header_regex)) {
@@ -338,6 +356,8 @@ static bool common_download_file_single(const std::string & url, const std::stri
338356                headers->etag  = value;
339357            } else  if  (std::regex_match (key, match, last_modified_regex)) {
340358                headers->last_modified  = value;
359+             } else  if  (std::regex_match (key, match, accept_ranges_regex)) {
360+                 headers->accept_ranges  = value;
341361            }
342362        }
343363        return  n_items;
@@ -366,28 +386,48 @@ static bool common_download_file_single(const std::string & url, const std::stri
366386
367387    //  if head_request_ok is false, we don't have the etag or last-modified headers
368388    //  we leave should_download as-is, which is true if the file does not exist
389+     bool  should_download_from_scratch = false ;
369390    if  (head_request_ok) {
370391        //  check if ETag or Last-Modified headers are different
371392        //  if it is, we need to download the file again
372393        if  (!etag.empty () && etag != headers.etag ) {
373394            LOG_WRN (" %s: ETag header is different (%s != %s): triggering a new download\n "  , __func__, etag.c_str (), headers.etag .c_str ());
374395            should_download = true ;
396+             should_download_from_scratch = true ;
375397        } else  if  (!last_modified.empty () && last_modified != headers.last_modified ) {
376398            LOG_WRN (" %s: Last-Modified header is different (%s != %s): triggering a new download\n "  , __func__, last_modified.c_str (), headers.last_modified .c_str ());
377399            should_download = true ;
400+             should_download_from_scratch = true ;
378401        }
379402    }
380403
404+     const  bool  accept_ranges_supported = !headers.accept_ranges .empty () && headers.accept_ranges  != " none"  ;
381405    if  (should_download) {
382-         std::string path_temporary = path + " .downloadInProgress"  ;
383-         if  (file_exists) {
406+         if  (file_exists && !accept_ranges_supported) {  //  Resumable downloads not supported, delete and start again.
384407            LOG_WRN (" %s: deleting previous downloaded file: %s\n "  , __func__, path.c_str ());
385408            if  (remove (path.c_str ()) != 0 ) {
386409                LOG_ERR (" %s: unable to delete file: %s\n "  , __func__, path.c_str ());
387410                return  false ;
388411            }
389412        }
390413
414+         std::string path_temporary = path + " .downloadInProgress"  ;
415+         if  (should_download_from_scratch) {
416+             if  (std::filesystem::exists (path_temporary)) {
417+                 if  (remove (path_temporary.c_str ()) != 0 ) {
418+                     LOG_ERR (" %s: unable to delete file: %s\n "  , __func__, path_temporary.c_str ());
419+                     return  false ;
420+                 }
421+             }
422+ 
423+             if  (std::filesystem::exists (path)) {
424+                 if  (remove (path.c_str ()) != 0 ) {
425+                     LOG_ERR (" %s: unable to delete file: %s\n "  , __func__, path.c_str ());
426+                     return  false ;
427+                 }
428+             }
429+         }
430+ 
391431        //  Set the output file
392432
393433        struct  FILE_deleter  {
@@ -396,7 +436,8 @@ static bool common_download_file_single(const std::string & url, const std::stri
396436            }
397437        };
398438
399-         std::unique_ptr<FILE, FILE_deleter> outfile (fopen (path_temporary.c_str (), " wb"  ));
439+         //  Always open file in append mode could be resuming
440+         std::unique_ptr<FILE, FILE_deleter> outfile (fopen (path_temporary.c_str (), " ab"  ));
400441        if  (!outfile) {
401442            LOG_ERR (" %s: error opening local file for writing: %s\n "  , __func__, path.c_str ());
402443            return  false ;
@@ -431,7 +472,19 @@ static bool common_download_file_single(const std::string & url, const std::stri
431472        //  start the download
432473        LOG_INF (" %s: trying to download model from %s to %s (server_etag:%s, server_last_modified:%s)...\n "  , __func__,
433474            llama_download_hide_password_in_url (url).c_str (), path.c_str (), headers.etag .c_str (), headers.last_modified .c_str ());
434-         bool  was_perform_successful = curl_perform_with_retry (url, curl.get (), CURL_MAX_RETRY, CURL_RETRY_DELAY_SECONDS, " GET"  );
475+ 
476+         //  Write the updated JSON metadata file.
477+         metadata.update ({
478+             {" url"  , url},
479+             {" etag"  , headers.etag },
480+             {" lastModified"  , headers.last_modified }
481+         });
482+         write_file (metadata_path, metadata.dump (4 ));
483+         LOG_DBG (" %s: file metadata saved: %s\n "  , __func__, metadata_path.c_str ());
484+ 
485+         const  bool  was_perform_successful =
486+             curl_perform_with_retry (url, curl.get (), CURL_MAX_RETRY, CURL_RETRY_DELAY_SECONDS, " GET"  ,
487+                                     headers.accept_ranges .empty () ? " "   : path_temporary);
435488        if  (!was_perform_successful) {
436489            return  false ;
437490        }
@@ -446,15 +499,6 @@ static bool common_download_file_single(const std::string & url, const std::stri
446499        //  Causes file to be closed explicitly here before we rename it.
447500        outfile.reset ();
448501
449-         //  Write the updated JSON metadata file.
450-         metadata.update ({
451-             {" url"  , url},
452-             {" etag"  , headers.etag },
453-             {" lastModified"  , headers.last_modified }
454-         });
455-         write_file (metadata_path, metadata.dump (4 ));
456-         LOG_DBG (" %s: file metadata saved: %s\n "  , __func__, metadata_path.c_str ());
457- 
458502        if  (rename (path_temporary.c_str (), path.c_str ()) != 0 ) {
459503            LOG_ERR (" %s: unable to rename file: %s to %s\n "  , __func__, path_temporary.c_str (), path.c_str ());
460504            return  false ;
0 commit comments