@@ -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,25 @@ 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 std::string partial_size = std::to_string (std::filesystem::file_size (path_temporary));
242+ LOG_INF (" %s: server supports range requests, resuming download from byte %s\n " , __func__,
243+ partial_size.c_str ());
244+ const std::string range_str = partial_size + " -" ;
245+ curl_easy_setopt (curl, CURLOPT_RANGE, range_str.c_str ());
246+ }
247+
229248 CURLcode res = curl_easy_perform (curl);
230249 if (res == CURLE_OK) {
231250 return true ;
@@ -246,15 +265,14 @@ static bool curl_perform_with_retry(const std::string & url, CURL * curl, int ma
246265
247266// download one single file from remote URL to local path
248267static 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-
252268 // If the file exists, check its JSON metadata companion file.
253269 std::string metadata_path = path + " .json" ;
254270 nlohmann::json metadata; // TODO @ngxson : get rid of this json, use regex instead
255271 std::string etag;
256272 std::string last_modified;
257273
274+ // Check if the file already exists locally
275+ const auto file_exists = std::filesystem::exists (path);
258276 if (file_exists) {
259277 if (offline) {
260278 LOG_INF (" %s: using cached file (offline mode): %s\n " , __func__, path.c_str ());
@@ -289,6 +307,7 @@ static bool common_download_file_single(const std::string & url, const std::stri
289307 struct common_load_model_from_url_headers {
290308 std::string etag;
291309 std::string last_modified;
310+ std::string accept_ranges;
292311 };
293312
294313 common_load_model_from_url_headers headers;
@@ -328,7 +347,7 @@ static bool common_download_file_single(const std::string & url, const std::stri
328347 static std::regex header_regex (" ([^:]+): (.*)\r\n " );
329348 static std::regex etag_regex (" ETag" , std::regex_constants::icase);
330349 static std::regex last_modified_regex (" Last-Modified" , std::regex_constants::icase);
331-
350+ static std::regex accept_ranges_regex ( " Accept-Ranges " , std::regex_constants::icase);
332351 std::string header (buffer, n_items);
333352 std::smatch match;
334353 if (std::regex_match (header, match, header_regex)) {
@@ -338,6 +357,8 @@ static bool common_download_file_single(const std::string & url, const std::stri
338357 headers->etag = value;
339358 } else if (std::regex_match (key, match, last_modified_regex)) {
340359 headers->last_modified = value;
360+ } else if (std::regex_match (key, match, accept_ranges_regex)) {
361+ headers->accept_ranges = value;
341362 }
342363 }
343364 return n_items;
@@ -366,28 +387,48 @@ static bool common_download_file_single(const std::string & url, const std::stri
366387
367388 // if head_request_ok is false, we don't have the etag or last-modified headers
368389 // we leave should_download as-is, which is true if the file does not exist
390+ bool should_download_from_scratch = false ;
369391 if (head_request_ok) {
370392 // check if ETag or Last-Modified headers are different
371393 // if it is, we need to download the file again
372394 if (!etag.empty () && etag != headers.etag ) {
373395 LOG_WRN (" %s: ETag header is different (%s != %s): triggering a new download\n " , __func__, etag.c_str (), headers.etag .c_str ());
374396 should_download = true ;
397+ should_download_from_scratch = true ;
375398 } else if (!last_modified.empty () && last_modified != headers.last_modified ) {
376399 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 ());
377400 should_download = true ;
401+ should_download_from_scratch = true ;
378402 }
379403 }
380404
405+ const bool accept_ranges_supported = !headers.accept_ranges .empty () && headers.accept_ranges != " none" ;
381406 if (should_download) {
382- std::string path_temporary = path + " .downloadInProgress" ;
383- if (file_exists) {
407+ if (file_exists && !accept_ranges_supported) { // Resumable downloads not supported, delete and start again.
384408 LOG_WRN (" %s: deleting previous downloaded file: %s\n " , __func__, path.c_str ());
385409 if (remove (path.c_str ()) != 0 ) {
386410 LOG_ERR (" %s: unable to delete file: %s\n " , __func__, path.c_str ());
387411 return false ;
388412 }
389413 }
390414
415+ const std::string path_temporary = path + " .downloadInProgress" ;
416+ if (should_download_from_scratch) {
417+ if (std::filesystem::exists (path_temporary)) {
418+ if (remove (path_temporary.c_str ()) != 0 ) {
419+ LOG_ERR (" %s: unable to delete file: %s\n " , __func__, path_temporary.c_str ());
420+ return false ;
421+ }
422+ }
423+
424+ if (std::filesystem::exists (path)) {
425+ if (remove (path.c_str ()) != 0 ) {
426+ LOG_ERR (" %s: unable to delete file: %s\n " , __func__, path.c_str ());
427+ return false ;
428+ }
429+ }
430+ }
431+
391432 // Set the output file
392433
393434 struct FILE_deleter {
@@ -396,7 +437,8 @@ static bool common_download_file_single(const std::string & url, const std::stri
396437 }
397438 };
398439
399- std::unique_ptr<FILE, FILE_deleter> outfile (fopen (path_temporary.c_str (), " wb" ));
440+ // Always open file in append mode could be resuming
441+ std::unique_ptr<FILE, FILE_deleter> outfile (fopen (path_temporary.c_str (), " ab" ));
400442 if (!outfile) {
401443 LOG_ERR (" %s: error opening local file for writing: %s\n " , __func__, path.c_str ());
402444 return false ;
@@ -431,7 +473,19 @@ static bool common_download_file_single(const std::string & url, const std::stri
431473 // start the download
432474 LOG_INF (" %s: trying to download model from %s to %s (server_etag:%s, server_last_modified:%s)...\n " , __func__,
433475 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" );
476+
477+ // Write the updated JSON metadata file.
478+ metadata.update ({
479+ {" url" , url},
480+ {" etag" , headers.etag },
481+ {" lastModified" , headers.last_modified }
482+ });
483+ write_file (metadata_path, metadata.dump (4 ));
484+ LOG_DBG (" %s: file metadata saved: %s\n " , __func__, metadata_path.c_str ());
485+
486+ const bool was_perform_successful =
487+ curl_perform_with_retry (url, curl.get (), CURL_MAX_RETRY, CURL_RETRY_DELAY_SECONDS, " GET" ,
488+ headers.accept_ranges .empty () ? " " : path_temporary);
435489 if (!was_perform_successful) {
436490 return false ;
437491 }
@@ -446,15 +500,6 @@ static bool common_download_file_single(const std::string & url, const std::stri
446500 // Causes file to be closed explicitly here before we rename it.
447501 outfile.reset ();
448502
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-
458503 if (rename (path_temporary.c_str (), path.c_str ()) != 0 ) {
459504 LOG_ERR (" %s: unable to rename file: %s to %s\n " , __func__, path_temporary.c_str (), path.c_str ());
460505 return false ;
@@ -770,7 +815,7 @@ static std::string common_docker_get_token(const std::string & repo) {
770815}
771816
772817static std::string common_docker_resolve_model (const std::string & docker) {
773- // Parse ai/smollm2:135M-Q4_K_M
818+ // Parse ai/smollm2:135M-Q4_0
774819 size_t colon_pos = docker.find (' :' );
775820 std::string repo, tag;
776821 if (colon_pos != std::string::npos) {
0 commit comments