From 2016dceaa9cfc65ee80ee7e433331390f4263744 Mon Sep 17 00:00:00 2001 From: bigfoot547 Date: Mon, 12 Jan 2026 16:30:37 -0600 Subject: download jobs --- lib/include/log.h | 7 ++ lib/include/net.h | 29 +++++ lib/include/util.h | 8 ++ lib/include/vector.h | 27 +++++ lib/log.c | 6 +- lib/meson.build | 2 +- lib/net.c | 327 +++++++++++++++++++++++++++++++++++++++++++++++++++ lib/util.c | 30 +++++ lib/vector.c | 122 +++++++++++++++++++ main.c | 98 ++++++++++++++- 10 files changed, 649 insertions(+), 7 deletions(-) create mode 100644 lib/include/util.h create mode 100644 lib/include/vector.h create mode 100644 lib/util.c create mode 100644 lib/vector.c diff --git a/lib/include/log.h b/lib/include/log.h index a8549e0..1483559 100644 --- a/lib/include/log.h +++ b/lib/include/log.h @@ -1,3 +1,6 @@ +#ifndef VL_LOG_H_INCLUDED +#define VL_LOG_H_INCLUDED + #include #include @@ -7,6 +10,8 @@ #define LOG_WARN 3u #define LOG_ERROR 4u +/* Log functions MUST leave errno untouched */ + void vl_log_setlevel(unsigned level); void vl_logv(unsigned level, const char *fmt, va_list args); @@ -50,3 +55,5 @@ void vl_log(unsigned level, const char *fmt, ...) __attribute__((format(printf, #define LOG_ERROR_ENABLED #define vl_error(...) vl_log(LOG_ERROR, __VA_ARGS__) #define vl_errorv(...) vl_logv(LOG_ERROR, __VA_ARGS__) + +#endif diff --git a/lib/include/net.h b/lib/include/net.h index 5ee7fc6..610ed77 100644 --- a/lib/include/net.h +++ b/lib/include/net.h @@ -2,6 +2,7 @@ #define VL_NET_H_INCLUDED #include "arena.h" +#include "sha1.h" enum { /* The operation completed successfully */ @@ -61,4 +62,32 @@ int vl_net_ensure_verified(const char *url, const char *target_path, unsigned fl * Returns NET_EIO if the downloaded file is not readable or could not be verified due to an I/O issue. */ int vl_net_verify(const char *target_path, unsigned flags, ...); +enum { + /* The job hasn't been started yet. */ + STATUS_WAITING = 0u, + + /* The job is in flight. */ + STATUS_RUNNING = 1u, + + /* The job is finished without errors. */ + STATUS_COMPLETE = 2u, + + /* The job is finished with errors. */ + STATUS_ERROR = 3u, + + /* The job finished successfully, but the downloaded file failed integrity checks. */ + STATUS_INTEGRITY = 4u +}; + +struct vl_download_job { + const char *url; + const char *opath; + size_t expect_len; + vl_sha1 expect_hash; + unsigned verify_flags; + unsigned status; +}; + +int vl_net_download_all(struct vl_download_job *jobs, size_t njobs, size_t simult); + #endif diff --git a/lib/include/util.h b/lib/include/util.h new file mode 100644 index 0000000..5ef3c8b --- /dev/null +++ b/lib/include/util.h @@ -0,0 +1,8 @@ +#ifndef VL_UTIL_H_INCLUDED +#define VL_UTIL_H_INCLUDED + +#include + +int vl_mkdir_parents(int fd, char *path, mode_t mode); + +#endif diff --git a/lib/include/vector.h b/lib/include/vector.h new file mode 100644 index 0000000..02ed84e --- /dev/null +++ b/lib/include/vector.h @@ -0,0 +1,27 @@ +#ifndef VL_VECTOR_H_INCLUDED +#define VL_VECTOR_H_INCLUDED + +#include + +/* implements a vector type (array of same-sized values) */ + +typedef struct vl__vector_tag vl_vector; + +vl_vector *vl_vector_new(size_t sz); +vl_vector *vl_vector_new_ex(size_t sz, size_t init_cap); +void vl_vector_free(vl_vector *vec); + +/* will abort if realloc fails to shrink the allocation (should never happen) */ +void vl_vector_shrink_to_size(vl_vector *vec); + +#define vl_newvec(_t) vl_vector_new(sizeof(_t)) +#define vl_newvec_ex(_t, _ic) vl_vector_new_ex(sizeof(_t), _ic) + +int vl_vector_push(vl_vector *vec, void *data); +int vl_vector_push_vec(vl_vector *vec, void *data, size_t n); +int vl_vector_push_2d(vl_vector *vec, void **data, size_t n); + +size_t vl_vector_size(const vl_vector *vec); +void *vl_vector_values(const vl_vector *vec, size_t *psz); + +#endif diff --git a/lib/log.c b/lib/log.c index 4924782..6f0f40a 100644 --- a/lib/log.c +++ b/lib/log.c @@ -1,5 +1,6 @@ #include #include +#include #include "log.h" /* TODO: replace this with something thread-safe later. */ @@ -41,6 +42,7 @@ void vl_logv(unsigned level, const char *fmt, va_list args) char datebuf[MAX_LOGTIMELEN]; time_t now; struct tm now_tm; + int en = errno; if (log_level > level) return; if (level > LOG_ERROR) return; @@ -55,7 +57,9 @@ void vl_logv(unsigned level, const char *fmt, va_list args) strcpy(datebuf, "???"); } - fprintf(log_stream(level), "[%s] %s: %s\n", datebuf, log_names[level], buf); + fprintf(log_stream(level), "[%s] %5s: %s\n", datebuf, log_names[level], buf); + + errno = en; } void vl_log(unsigned level, const char *fmt, ...) diff --git a/lib/meson.build b/lib/meson.build index d2fca24..04c2cd9 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -1,5 +1,5 @@ vaclaunch_lib_inc = include_directories('include') vaclaunch_libs = both_libraries('vaclaunch', - files('manifest.c', 'log.c', 'net.c', 'sha1.c', 'arena.c'), + files('manifest.c', 'log.c', 'net.c', 'sha1.c', 'arena.c', 'util.c', 'vector.c'), include_directories : vaclaunch_lib_inc, dependencies : [ curl_dep, jansson_dep ]) diff --git a/lib/net.c b/lib/net.c index 99c7deb..f400974 100644 --- a/lib/net.c +++ b/lib/net.c @@ -492,3 +492,330 @@ int vl_net_ensure_verified(const char *url, const char *target_path, unsigned fl return ret; } +struct dl_all_ctx { + struct write_ctx wrctx; + struct vl_download_job *cur_job; + char errbuf[CURL_ERROR_SIZE]; +}; + +static int setup_easy(CURLM *multi, CURL *easy, struct vl_download_job *job) +{ + int ret = NET_EUNSPEC; + CURLcode ecode; + CURLMcode mcode; + + struct dl_all_ctx *dlctx; + + if ((ecode = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &dlctx)) != CURLE_OK || !dlctx) { + vl_error("Bad curl easy handle! curl_easy_getinfo(CURLINFO_PRIVATE): %s", curl_easy_strerror(ecode)); + abort(); /* weird should never happen */ + } + +#define C(_ex) CHECK(_ex, #_ex, ecode, dlctx->errbuf, cleanup, ret) + + C(curl_easy_setopt(easy, CURLOPT_URL, job->url)); + +#undef C + + if (!(dlctx->wrctx.ofile = fopen(job->opath, "wb"))) { + vl_warn("Failed to open output file %s: %s", job->opath, strerror(errno)); + ret = NET_EIO; + goto cleanup; + } + + dlctx->wrctx.opath = NULL; + dlctx->wrctx.total_read = 0; + vl_sha1_init(&dlctx->wrctx.sha1_state); + + dlctx->cur_job = job; + job->status = STATUS_RUNNING; + + if ((mcode = curl_multi_add_handle(multi, easy)) != CURLM_OK) { + vl_warn("Failed to add easy handle to multi (for %s): %s", job->url, curl_multi_strerror(mcode)); + goto cleanup; + } + + ret = NET_OK; + +cleanup: + if (ret != NET_OK && dlctx->wrctx.ofile) { + fclose(dlctx->wrctx.ofile); + dlctx->wrctx.ofile = NULL; + + if (remove(job->opath) < 0) { + vl_debug("...also failed to delete touched file %s: %s", job->opath, strerror(errno)); + } else { + vl_trace("removed touched file %s", job->opath); + } + } + + return ret; +} + +static int xfer_complete(CURLM *multi, CURL *easy, CURLcode ecode) +{ + struct dl_all_ctx *ctx = NULL; + int ret = NET_EUNSPEC; + long statuscode; + CURLcode myecode; + CURLMcode mcode; + + if ((myecode = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &ctx)) != CURLE_OK || !ctx) { + vl_error("Failed to get context information: curl_easy_getinfo(CURLINFO_PRIVATE): %s", curl_easy_strerror(myecode)); + abort(); /* weird! should never happen */ + } + + vl_trace("Handling completed transfer %s", ctx->cur_job->url); + + if ((mcode = curl_multi_remove_handle(multi, easy)) != CURLM_OK) { + vl_warn("Failed to remove easy handle from multi (for %s): %s", ctx->cur_job->url, curl_multi_strerror(mcode)); + goto cleanup; + } + + if (ecode != CURLE_OK) { + ctx->cur_job->status = STATUS_ERROR; + ret = translate_curlcode(ecode); + goto cleanup; + } + + /* transfer completed successfully, make sure everything else is right also. */ + +#define C(_ex) CHECK(_ex, #_ex, myecode, ctx->errbuf, cleanup, ret) + + C(curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &statuscode)); + + if (statuscode / 100 != 2) { + /* should never happen */ + vl_warn("Bad HTTP response code from %s: %ld", ctx->cur_job->url, statuscode); + ret = NET_ESTATUS; + goto cleanup; + } + + vl_trace("Got %ld OK from %s", statuscode, ctx->cur_job->url); + + /* compare integrity info */ + + if (ctx->cur_job->verify_flags & VERIFY_SIZE) { + if (ctx->cur_job->expect_len != ctx->wrctx.total_read) { + vl_warn("Bad integrity on downloaded file %s: size mismatch: expected %zu bytes, got %zu bytes", ctx->cur_job->url, ctx->cur_job->expect_len, ctx->wrctx.total_read); + ret = NET_EINTEGRITY; + ctx->cur_job->status = STATUS_INTEGRITY; + goto cleanup; + } + + vl_trace("size matches %s: %zu", ctx->cur_job->url, ctx->cur_job->expect_len); + } + + if (ctx->cur_job->verify_flags & VERIFY_SHA1) { + vl_sha1 got_hash; + vl_sha1_finalize(&ctx->wrctx.sha1_state, got_hash); + + char exp_hex[VL_SHA1_DIGEST_HEX_STRLEN + 1]; + char got_hex[VL_SHA1_DIGEST_HEX_STRLEN + 1]; + + exp_hex[VL_SHA1_DIGEST_HEX_STRLEN] = '\0'; + got_hex[VL_SHA1_DIGEST_HEX_STRLEN] = '\0'; + + vl_sha1_encode(ctx->cur_job->expect_hash, exp_hex); + vl_sha1_encode(got_hash, got_hex); + + if (memcmp(ctx->cur_job->expect_hash, got_hash, sizeof(vl_sha1))) { + vl_warn("Bad integrity on downloaded file %s: sha1 mismatch: expected %s, got %s", ctx->cur_job->url, exp_hex, got_hex); + ret = NET_EINTEGRITY; + ctx->cur_job->status = STATUS_INTEGRITY; + goto cleanup; + } + + vl_trace("sha1 matches %s: %s", ctx->cur_job->url, exp_hex); + } + +#undef C + + ctx->cur_job->status = STATUS_COMPLETE; + ret = NET_OK; + +cleanup: + if (ctx->wrctx.ofile) { + fclose(ctx->wrctx.ofile); + ctx->wrctx.ofile = NULL; + } + + if (ret != NET_OK) { + if (remove(ctx->cur_job->opath) < 0) { + vl_debug("...and failed to clean up after failed job: remove(%s): %s", ctx->cur_job->opath, strerror(errno)); + } else { + vl_trace("removed touched file (failed job): %s", ctx->cur_job->opath); + } + } + + ctx->cur_job = NULL; + + return ret; +} + +int vl_net_download_all(struct vl_download_job *jobs, size_t njobs, size_t simult) +{ + vl_arena *arena = NULL; + int ret = NET_EUNSPEC; + int tempret; + CURLM *multi = NULL; + CURL **easy_pool = NULL; + struct dl_all_ctx *dlcontexts = NULL; + size_t next_job = 0; + size_t completed_jobs = 0; + CURLcode ecode; + + CURLMcode mcode; + int nrunning = 0; + CURLMsg *mmsg; + int nmsg = 0; + + /* sanity check for simultaneous transfer count */ + if (simult < 8) simult = 8; + if (simult > njobs) simult = njobs; + + arena = vl_arena_new((size_t)1 << 14); + if (!arena) { + vl_warn("Error allocating arena for download job!"); + goto cleanup; + } + + /* array of easy handles */ + easy_pool = vl_arena_push(arena, simult * sizeof(CURL *)); + memset(easy_pool, 0, simult * sizeof(CURL *)); /* erm but NULL isn't necessarily represented as all 0s.. */ + + /* context info for each easy handle */ + dlcontexts = vl_arena_push(arena, simult * sizeof(struct dl_all_ctx)); + memset(dlcontexts, 0, simult * sizeof(struct dl_all_ctx)); + + multi = curl_multi_init(); + if (!multi) { + vl_warn("Error creating multi handle for download job"); + goto cleanup; + } + + /* initialize each easy handle with default info */ + for (size_t i = 0; i < simult; ++i) { + easy_pool[i] = curl_easy_init(); + if (!easy_pool[i]) { + vl_warn("Failed to create easy handle %zu", i); + goto cleanup; + } + + if ((ecode = curl_easy_setopt(easy_pool[i], CURLOPT_ERRORBUFFER, dlcontexts[i].errbuf)) != CURLE_OK) { + vl_warn("Failed to set error buffer on easy handle %zu: %s", i, curl_easy_strerror(ecode)); + goto cleanup; + } + +#define C(_ex) CHECK(_ex, #_ex, ecode, dlcontexts[i].errbuf, cleanup, ret) + + C(curl_easy_setopt(easy_pool[i], CURLOPT_USERAGENT, VL_USER_AGENT)); + C(curl_easy_setopt(easy_pool[i], CURLOPT_HTTPGET, 1L)); + C(curl_easy_setopt(easy_pool[i], CURLOPT_FOLLOWLOCATION, CURLFOLLOW_ALL)); + C(curl_easy_setopt(easy_pool[i], CURLOPT_TIMEOUT, 60L)); + C(curl_easy_setopt(easy_pool[i], CURLOPT_FAILONERROR, 1L)); + C(curl_easy_setopt(easy_pool[i], CURLOPT_WRITEFUNCTION, &handle_write)); + C(curl_easy_setopt(easy_pool[i], CURLOPT_WRITEDATA, &dlcontexts[i].wrctx)); + C(curl_easy_setopt(easy_pool[i], CURLOPT_PRIVATE, dlcontexts + i)); + +#undef C + } + + vl_trace("Setting off %zu initial jobs.", simult); + for (size_t i = 0; i < simult; ++i) { + if ((tempret = setup_easy(multi, easy_pool[i], jobs + i)) != NET_OK) { + vl_trace("Failed to set up initial job %zu", i); + ret = tempret; + goto cleanup; + } + } + + next_job = simult; + + do { + vl_trace("Starting download loop."); + mcode = curl_multi_perform(multi, &nrunning); + if (mcode != CURLM_OK) { + vl_warn("Error in CURL transfer: curl_multi_perform: %s", curl_multi_strerror(mcode)); + goto cleanup; + } + + /* handle complete transfers */ + do { + mmsg = curl_multi_info_read(multi, &nmsg); + if (!mmsg) break; + + if (mmsg->msg == CURLMSG_DONE) { + /* a transfer has completed */ + ecode = mmsg->data.result; + if ((tempret = xfer_complete(multi, mmsg->easy_handle, ecode)) != NET_OK) { + ret = tempret; + goto cleanup; + } + + ++completed_jobs; + + /* set up the next transfer */ + if (next_job < njobs) { + if ((tempret = setup_easy(multi, mmsg->easy_handle, jobs + next_job)) != NET_OK) { + vl_trace("failed to set up job %zu", next_job); + ret = tempret; + goto cleanup; + } + + vl_trace("Set up job %zu", next_job); + ++next_job; + } + } + } while (mmsg); + + vl_trace("Download loop complete: %d running, next job: %zu, njobs: %zu, completed: %zu", nrunning, next_job, njobs, completed_jobs); + + if (completed_jobs >= njobs) { + vl_trace("Breaking out early %zu >= %zu.", completed_jobs, njobs); + break; + } + + mcode = curl_multi_poll(multi, NULL, 0, 1000, NULL); + if (mcode != CURLM_OK) { + vl_warn("Error in CURL transfer: curl_multi_poll: %s", curl_multi_strerror(mcode)); + goto cleanup; + } + } while (completed_jobs < njobs); + + ret = NET_OK; + +cleanup: + if (easy_pool) { + for (size_t i = 0; i < simult; ++i) { + if (easy_pool[i]) { + if (multi) { + /* if this call returns an error then idc I did my best */ + curl_multi_remove_handle(multi, easy_pool[i]); + } + curl_easy_cleanup(easy_pool[i]); + } + } + } + + if (multi) { + curl_multi_cleanup(multi); + } + + for (size_t i = 0; i < simult; ++i) { + if (dlcontexts[i].cur_job && dlcontexts[i].wrctx.ofile) { + fclose(dlcontexts[i].wrctx.ofile); + if (remove(dlcontexts[i].cur_job->opath) < 0) { + vl_debug("...also failed to remove touched file %s: %s", dlcontexts[i].cur_job->opath, strerror(errno)); + } else { + vl_trace("Removed touched file %s", dlcontexts[i].cur_job->opath); + } + } + } + + if (arena) { + vl_arena_free(arena); + } + + return ret; +} diff --git a/lib/util.c b/lib/util.c new file mode 100644 index 0000000..6cfcf1f --- /dev/null +++ b/lib/util.c @@ -0,0 +1,30 @@ +#include "log.h" +#include "util.h" + +#include +#include +#include +#include + +/* will clobber path */ +int vl_mkdir_parents(int fd, char *path, mode_t mode) +{ + int last = 0; + + char *ch = path; + do { + if (*ch == '/' || (*ch == '\0' && (last = 1))) { + /* last is set to 1 iff *ch == '\0' */ + *ch = '\0'; + + if (mkdirat(fd, path, mode) < 0 && errno != EEXIST) { + vl_debug("Failed to create directory %s: %s", path, strerror(errno)); + return -1; + } + + if (!last) *ch = '/'; + } + } while (*(ch++)); + + return 0; +} diff --git a/lib/vector.c b/lib/vector.c new file mode 100644 index 0000000..eb7233c --- /dev/null +++ b/lib/vector.c @@ -0,0 +1,122 @@ +#include "vector.h" +#include +#include +#include + +struct vl__vector_tag { + size_t cap, sz, len; + void *data; /* cap * len */ +}; + +vl_vector *vl_vector_new(size_t sz) +{ + return vl_vector_new_ex(sz, 16); +} + +vl_vector *vl_vector_new_ex(size_t sz, size_t init_cap) +{ + vl_vector *vec = calloc(1, sizeof(vl_vector)); + if (!vec) return NULL; + + if (init_cap < 16) init_cap = 16; + + vec->cap = init_cap; + vec->len = 0; + vec->sz = sz; + + void *data = calloc(init_cap, sz); + if (!data) { + free(vec); + return NULL; + } + + vec->data = data; + + return vec; +} + +void vl_vector_free(vl_vector *vec) +{ + if (!vec) return; + + free(vec->data); + free(vec); +} + +static int vec_grow(vl_vector *vec, size_t newlen) +{ + void *temp = reallocarray(vec->data, newlen, vec->sz); + if (!temp) return -1; + vec->data = temp; + + return 0; +} + +static size_t next_po2(size_t sz) +{ + assert(sz > 0); + return (size_t)(1 << (64 - __builtin_clzll((unsigned long long)sz))); +} + +static int ensure_fits(vl_vector *vec, size_t toadd) +{ + if (vec->len + toadd <= vec->cap) { + return 0; + } + + size_t newcap = next_po2(vec->len + toadd); + return vec_grow(vec, newcap); +} + +/* will abort if realloc fails to shrink the allocation (should never happen) */ +void vl_vector_shrink_to_size(vl_vector *vec) +{ + if (vec_grow(vec, vec->len) < 0) abort(); +} + +#define vl_newvec(_t) vl_vector_new(sizeof(_t)) +#define vl_newvec_ex(_t, _ic) vl_vector_new_ex(sizeof(_t), _ic) + +int vl_vector_push(vl_vector *vec, void *data) +{ + return vl_vector_push_vec(vec, data, 1); +} + +int vl_vector_push_vec(vl_vector *vec, void *data, size_t n) +{ + if (ensure_fits(vec, n) < 0) { + return -1; + } + + unsigned char *d = vec->data; + memcpy(d + (vec->len * vec->sz), data, n * vec->sz); + vec->len += n; + return 0; +} + +int vl_vector_push_2d(vl_vector *vec, void **data, size_t n) +{ + if (ensure_fits(vec, n) < 0) { + return -1; + } + + unsigned char *d = vec->data; + for (size_t i = 0; i < n; ++i) { + memcpy(d + ((vec->len + i) * vec->sz), data[i], vec->sz); + } + + vec->len += n; + + return 0; +} + +size_t vl_vector_size(const vl_vector *vec) +{ + return vec->len; +} + +void *vl_vector_values(const vl_vector *vec, size_t *psz) +{ + if (psz) *psz = vec->len; + return vec->data; +} diff --git a/main.c b/main.c index 93fe7e2..1f1709f 100644 --- a/main.c +++ b/main.c @@ -1,8 +1,15 @@ -#include /* for setlocale */ -#include #include "log.h" #include "arena.h" #include "net.h" +#include "sha1.h" +#include "vector.h" + +#include +#include +#include +#include /* for setlocale */ +#include +#include int main(void) { @@ -10,12 +17,93 @@ int main(void) vl_log_setlevel(LOG_TRACE); vl_arena *arena = vl_arena_new(8192); - int ret = vl_net_ensure_cached(arena, "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json", "manifest.json"); + vl_net_ensure_cached(arena, "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json", "manifest.json"); + vl_arena_reset(arena); + + vl_net_ensure_cached(arena, "https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json", "runtime-manifest.json"); vl_arena_reset(arena); - ret = vl_net_ensure_cached(arena, "https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json", "runtime-manifest.json"); - printf("%d\n", ret); + json_error_t err; + json_t *manifest = json_load_file("manifest.json", 0, &err), *ver; + size_t idx; + if (!manifest) { + vl_error("bad: %s", err.text); + exit(EXIT_FAILURE); + } + + json_t *versions = json_object_get(manifest, "versions"); + + json_array_foreach(versions, idx, ver) { + const char *verid; + const char *url; + const char *sha1_hex; + json_unpack(ver, "{s:s,s:s,s:s}", "id", &verid, "url", &url, "sha1", &sha1_hex); + if (!strcmp(verid, "1.8.9") && url) { + vl_sha1 hash; + vl_sha1_decode(hash, sha1_hex); + vl_net_ensure_verified(url, "1.8.9.json", VERIFY_SHA1, hash); + } + } + + json_decref(manifest); + + json_t *oneeightnine = json_load_file("1.8.9.json", 0, &err); + if (!oneeightnine) { + vl_error("bad ver: %s", err.text); + exit(EXIT_FAILURE); + } + + json_t *libs = json_object_get(oneeightnine, "libraries"), *lib; + vl_vector *vec = vl_newvec(struct vl_download_job); + + json_array_foreach(libs, idx, lib) { + const char *url; + const char *path; + json_int_t sz; + const char *sha1hex; + size_t sha1hexlen; + + struct vl_download_job tjob; + + if (json_unpack(lib, "{s:{s:{s:s,s:s,s:I,s:s%}}}", "downloads", "artifact", "url", &url, "path", &path, "size", &sz, "sha1", &sha1hex, &sha1hexlen) < 0) { + continue; + } + + path = basename(vl_arena_strdup(arena, path)); + + tjob.url = url; + tjob.opath = path; + tjob.expect_len = (size_t)sz; + + if (sha1hexlen != VL_SHA1_DIGEST_HEX_STRLEN || vl_sha1_decode(tjob.expect_hash, sha1hex) < 0) { + vl_error("bad expect hash %s", sha1hex); + continue; + } + + tjob.verify_flags = VERIFY_SIZE | VERIFY_SHA1; + + if (vl_net_verify(tjob.opath, VERIFY_SIZE | VERIFY_SHA1, tjob.expect_len, tjob.expect_hash) == NET_OK) { + vl_trace("fine %s", tjob.opath); + continue; + } + + if (vl_vector_push(vec, &tjob) < 0) { + abort(); + } + } + + size_t njobs; + struct vl_download_job *jobs = vl_vector_values(vec, &njobs); + printf("%d\n", vl_net_download_all(jobs, njobs, 8)); + + for (size_t n = 0; n < njobs; ++n) { + printf("job %s status: %u\n", jobs[n].url, jobs[n].status); + } + + json_decref(oneeightnine); + vl_vector_free(vec); vl_arena_free(arena); + return 0; } -- cgit v1.2.3-70-g09d2