@@ -114,7 +114,7 @@ std::pair<std::string, std::string> common_download_split_repo_tag(const std::st
114114 return {hf_repo, tag};
115115}
116116
117- class ProgressBar {
117+ class ProgressBar : public common_download_callback {
118118 static inline std::mutex mutex;
119119 static inline std::map<const ProgressBar *, int > lines;
120120 static inline int max_line = 0 ;
@@ -138,7 +138,11 @@ class ProgressBar {
138138 }
139139
140140public:
141- ProgressBar (const std::string & url = " " ) : filename(url) {
141+ ProgressBar () = default ;
142+
143+ void on_start (const common_download_progress & p) override {
144+ filename = p.url ;
145+
142146 if (auto pos = filename.rfind (' /' ); pos != std::string::npos) {
143147 filename = filename.substr (pos + 1 );
144148 }
@@ -156,13 +160,13 @@ class ProgressBar {
156160 }
157161 }
158162
159- ~ProgressBar () {
163+ void on_done ( const common_download_progress &, bool ) override {
160164 std::lock_guard<std::mutex> lock (mutex);
161165 cleanup (this );
162166 }
163167
164- void update ( size_t current, size_t total) {
165- if (!total || !is_output_a_tty ()) {
168+ void on_update ( const common_download_progress & p) override {
169+ if (!p. total || !is_output_a_tty ()) {
166170 return ;
167171 }
168172
@@ -175,8 +179,8 @@ class ProgressBar {
175179 int lines_up = max_line - lines[this ];
176180
177181 size_t bar = (55 - len) * 2 ;
178- size_t pct = (100 * current ) / total;
179- size_t pos = (bar * current ) / total;
182+ size_t pct = (100 * p. downloaded ) / p. total ;
183+ size_t pos = (bar * p. downloaded ) / p. total ;
180184
181185 if (lines_up > 0 ) {
182186 std::cout << " \033 [" << lines_up << " A" ;
@@ -193,7 +197,7 @@ class ProgressBar {
193197 }
194198 std::cout << ' \r ' << std::flush;
195199
196- if (current == total) {
200+ if (p. downloaded == p. total ) {
197201 cleanup (this );
198202 }
199203 }
@@ -206,38 +210,36 @@ static bool common_pull_file(httplib::Client & cli,
206210 const std::string & resolve_path,
207211 const std::string & path_tmp,
208212 bool supports_ranges,
209- size_t existing_size ,
210- size_t & total_size ) {
213+ common_download_progress & p ,
214+ common_download_callback * callback ) {
211215 std::ofstream ofs (path_tmp, std::ios::binary | std::ios::app);
212216 if (!ofs.is_open ()) {
213217 LOG_ERR (" %s: error opening local file for writing: %s\n " , __func__, path_tmp.c_str ());
214218 return false ;
215219 }
216220
217221 httplib::Headers headers;
218- if (supports_ranges && existing_size > 0 ) {
219- headers.emplace (" Range" , " bytes=" + std::to_string (existing_size ) + " -" );
222+ if (supports_ranges && p. downloaded > 0 ) {
223+ headers.emplace (" Range" , " bytes=" + std::to_string (p. downloaded ) + " -" );
220224 }
221225
222226 const char * func = __func__; // avoid __func__ inside a lambda
223- size_t downloaded = existing_size;
224227 size_t progress_step = 0 ;
225- ProgressBar bar (resolve_path);
226228
227229 auto res = cli.Get (resolve_path, headers,
228230 [&](const httplib::Response &response) {
229- if (existing_size > 0 && response.status != 206 ) {
231+ if (p. downloaded > 0 && response.status != 206 ) {
230232 LOG_WRN (" %s: server did not respond with 206 Partial Content for a resume request. Status: %d\n " , func, response.status );
231233 return false ;
232234 }
233- if (existing_size == 0 && response.status != 200 ) {
235+ if (p. downloaded == 0 && response.status != 200 ) {
234236 LOG_WRN (" %s: download received non-successful status code: %d\n " , func, response.status );
235237 return false ;
236238 }
237- if (total_size == 0 && response.has_header (" Content-Length" )) {
239+ if (p. total == 0 && response.has_header (" Content-Length" )) {
238240 try {
239241 size_t content_length = std::stoull (response.get_header_value (" Content-Length" ));
240- total_size = existing_size + content_length;
242+ p. total = p. downloaded + content_length;
241243 } catch (const std::exception &e) {
242244 LOG_WRN (" %s: invalid Content-Length header: %s\n " , func, e.what ());
243245 }
@@ -250,11 +252,13 @@ static bool common_pull_file(httplib::Client & cli,
250252 LOG_ERR (" %s: error writing to file: %s\n " , func, path_tmp.c_str ());
251253 return false ;
252254 }
253- downloaded += len;
255+ p. downloaded += len;
254256 progress_step += len;
255257
256- if (progress_step >= total_size / 1000 || downloaded == total_size) {
257- bar.update (downloaded, total_size);
258+ if (progress_step >= p.total / 1000 || p.downloaded == p.total ) {
259+ if (callback) {
260+ callback->on_update (p);
261+ }
258262 progress_step = 0 ;
259263 }
260264 return true ;
@@ -275,11 +279,10 @@ static bool common_pull_file(httplib::Client & cli,
275279
276280// download one single file from remote URL to local path
277281// returns status code or -1 on error
278- static int common_download_file_single_online (const std::string & url,
279- const std::string & path,
280- const std::string & bearer_token,
281- const common_header_list & custom_headers,
282- bool skip_etag = false ) {
282+ static int common_download_file_single_online (const std::string & url,
283+ const std::string & path,
284+ const common_download_opts & opts,
285+ bool skip_etag) {
283286 static const int max_attempts = 3 ;
284287 static const int retry_delay_seconds = 2 ;
285288
@@ -293,14 +296,14 @@ static int common_download_file_single_online(const std::string & url,
293296 auto [cli, parts] = common_http_client (url);
294297
295298 httplib::Headers headers;
296- for (const auto & h : custom_headers ) {
299+ for (const auto & h : opts. headers ) {
297300 headers.emplace (h.first , h.second );
298301 }
299302 if (headers.find (" User-Agent" ) == headers.end ()) {
300303 headers.emplace (" User-Agent" , " llama-cpp/" + build_info);
301304 }
302- if (!bearer_token.empty ()) {
303- headers.emplace (" Authorization" , " Bearer " + bearer_token);
305+ if (!opts. bearer_token .empty ()) {
306+ headers.emplace (" Authorization" , " Bearer " + opts. bearer_token );
304307 }
305308 cli.set_default_headers (headers);
306309
@@ -326,10 +329,11 @@ static int common_download_file_single_online(const std::string & url,
326329 etag = head->get_header_value (" ETag" );
327330 }
328331
329- size_t total_size = 0 ;
332+ common_download_progress p;
333+ p.url = url;
330334 if (head->has_header (" Content-Length" )) {
331335 try {
332- total_size = std::stoull (head->get_header_value (" Content-Length" ));
336+ p. total = std::stoull (head->get_header_value (" Content-Length" ));
333337 } catch (const std::exception& e) {
334338 LOG_WRN (" %s: invalid Content-Length in HEAD response: %s\n " , __func__, e.what ());
335339 }
@@ -357,13 +361,17 @@ static int common_download_file_single_online(const std::string & url,
357361
358362 { // silent
359363 std::error_code ec;
360- std::filesystem::path p (path);
361- std::filesystem::create_directories (p.parent_path (), ec);
364+ std::filesystem::create_directories (std::filesystem::path (path).parent_path (), ec);
362365 }
363366
367+ bool success = false ;
364368 const std::string path_temporary = path + " .downloadInProgress" ;
365369 int delay = retry_delay_seconds;
366370
371+ if (opts.callback ) {
372+ opts.callback ->on_start (p);
373+ }
374+
367375 for (int i = 0 ; i < max_attempts; ++i) {
368376 if (i) {
369377 LOG_WRN (" %s: retrying after %d seconds...\n " , __func__, delay);
@@ -378,28 +386,38 @@ static int common_download_file_single_online(const std::string & url,
378386 existing_size = std::filesystem::file_size (path_temporary);
379387 } else if (remove (path_temporary.c_str ()) != 0 ) {
380388 LOG_ERR (" %s: unable to delete file: %s\n " , __func__, path_temporary.c_str ());
381- return - 1 ;
389+ break ;
382390 }
383391 }
384392
393+ p.downloaded = existing_size;
394+
385395 LOG_DBG (" %s: downloading from %s to %s (etag:%s)...\n " ,
386396 __func__, common_http_show_masked_url (parts).c_str (),
387397 path_temporary.c_str (), etag.c_str ());
388398
389- if (common_pull_file (cli, parts.path , path_temporary, supports_ranges, existing_size, total_size )) {
399+ if (common_pull_file (cli, parts.path , path_temporary, supports_ranges, p, opts. callback )) {
390400 if (std::rename (path_temporary.c_str (), path.c_str ()) != 0 ) {
391401 LOG_ERR (" %s: unable to rename file: %s to %s\n " , __func__, path_temporary.c_str (), path.c_str ());
392- return - 1 ;
402+ break ;
393403 }
394404 if (!etag.empty () && !skip_etag) {
395405 write_etag (path, etag);
396406 }
397- return head->status ;
407+ success = true ;
408+ break ;
398409 }
399410 }
400411
401- LOG_ERR (" %s: download failed after %d attempts\n " , __func__, max_attempts);
402- return -1 ; // max attempts reached
412+ if (opts.callback ) {
413+ opts.callback ->on_done (p, success);
414+ }
415+ if (!success) {
416+ LOG_ERR (" %s: download failed after %d attempts\n " , __func__, max_attempts);
417+ return -1 ; // max attempts reached
418+ }
419+
420+ return head->status ;
403421}
404422
405423std::pair<long , std::vector<char >> common_remote_get_content (const std::string & url,
@@ -438,12 +456,15 @@ std::pair<long, std::vector<char>> common_remote_get_content(const std::string
438456
439457int common_download_file_single (const std::string & url,
440458 const std::string & path,
441- const std::string & bearer_token,
442- bool offline,
443- const common_header_list & headers,
459+ const common_download_opts & opts,
444460 bool skip_etag) {
445- if (!offline) {
446- return common_download_file_single_online (url, path, bearer_token, headers, skip_etag);
461+ if (!opts.offline ) {
462+ ProgressBar tty_cb;
463+ common_download_opts online_opts = opts;
464+ if (!online_opts.callback ) {
465+ online_opts.callback = &tty_cb;
466+ }
467+ return common_download_file_single_online (url, path, online_opts, skip_etag);
447468 }
448469
449470 if (!std::filesystem::exists (path)) {
@@ -452,6 +473,16 @@ int common_download_file_single(const std::string & url,
452473 }
453474
454475 LOG_DBG (" %s: using cached file (offline mode): %s\n " , __func__, path.c_str ());
476+
477+ // notify the callback that the file was cached
478+ if (opts.callback ) {
479+ common_download_progress p;
480+ p.url = url;
481+ p.cached = true ;
482+ opts.callback ->on_start (p);
483+ opts.callback ->on_done (p, true );
484+ }
485+
455486 return 304 ; // Not Modified - fake cached response
456487}
457488
@@ -631,16 +662,16 @@ struct hf_plan {
631662 hf_cache::hf_file mmproj;
632663};
633664
634- static hf_plan get_hf_plan (const common_params_model & model,
635- const std::string & token ,
636- const common_download_model_opts & opts ) {
665+ static hf_plan get_hf_plan (const common_params_model & model,
666+ const common_download_opts & opts ,
667+ bool download_mmproj ) {
637668 hf_plan plan;
638669 hf_cache::hf_files all;
639670
640671 auto [repo, tag] = common_download_split_repo_tag (model.hf_repo );
641672
642673 if (!opts.offline ) {
643- all = hf_cache::get_repo_files (repo, token );
674+ all = hf_cache::get_repo_files (repo, opts. bearer_token );
644675 }
645676 if (all.empty ()) {
646677 all = hf_cache::get_cached_files (repo);
@@ -675,7 +706,7 @@ static hf_plan get_hf_plan(const common_params_model & model,
675706 plan.primary = primary;
676707 plan.model_files = get_split_files (all, primary);
677708
678- if (opts. download_mmproj ) {
709+ if (download_mmproj) {
679710 plan.mmproj = find_best_mmproj (all, primary.path );
680711 }
681712
@@ -710,18 +741,17 @@ static std::vector<download_task> get_url_tasks(const common_params_model & mode
710741 return tasks;
711742}
712743
713- common_download_model_result common_download_model (const common_params_model & model,
714- const std::string & bearer_token,
715- const common_download_model_opts & opts,
716- const common_header_list & headers) {
744+ common_download_model_result common_download_model (const common_params_model & model,
745+ const common_download_opts & opts,
746+ bool download_mmproj) {
717747 common_download_model_result result;
718748 std::vector<download_task> tasks;
719749 hf_plan hf;
720750
721751 bool is_hf = !model.hf_repo .empty ();
722752
723753 if (is_hf) {
724- hf = get_hf_plan (model, bearer_token, opts );
754+ hf = get_hf_plan (model, opts, download_mmproj );
725755 for (const auto & f : hf.model_files ) {
726756 tasks.push_back ({f.url , f.local_path });
727757 }
@@ -742,8 +772,8 @@ common_download_model_result common_download_model(const common_params_model
742772 std::vector<std::future<bool >> futures;
743773 for (const auto & task : tasks) {
744774 futures.push_back (std::async (std::launch::async,
745- [&task, &bearer_token, offline = opts. offline , &headers , is_hf]() {
746- int status = common_download_file_single (task.url , task.path , bearer_token, offline, headers , is_hf);
775+ [&task, &opts, is_hf]() {
776+ int status = common_download_file_single (task.url , task.path , opts , is_hf);
747777 return is_http_status_ok (status);
748778 }
749779 ));
@@ -879,7 +909,9 @@ std::string common_docker_resolve_model(const std::string & docker) {
879909 std::string local_path = fs_get_cache_file (model_filename);
880910
881911 const std::string blob_url = url_prefix + " /blobs/" + gguf_digest;
882- const int http_status = common_download_file_single (blob_url, local_path, token, false , {});
912+ common_download_opts opts;
913+ opts.bearer_token = token;
914+ const int http_status = common_download_file_single (blob_url, local_path, opts);
883915 if (!is_http_status_ok (http_status)) {
884916 throw std::runtime_error (" Failed to download Docker Model" );
885917 }
0 commit comments