Files
vdm/core/include/vdm
samiandClaude Sonnet 5 91636f8a4d core: add the download engine — task machine, DownloadHandle, Engine
Stage 8 of the CORE build order: the bodies behind the DownloadSpec /
callback API reviewed in core/docs/engine-api-m1.md. Wires probe -> segment
workers -> WriteBuffer -> SparseFile -> .veloxpart.meta -> retry/backoff ->
SegmentBudget -> RateLimiter -> callbacks into one event-driven machine.

- Engine (src/engine.cpp): owns HttpClient, Prober, SegmentBudget,
  RateLimiter and one timer jthread (min-heap of scheduled fns). start()
  builds a task and returns a DownloadHandle; ~Impl quiesces every task
  before joining the timer so no callback fires during teardown.

- DownloadTaskState (src/task/download_task.cpp): one `mu` task lock; a
  shared_mutex over the worker map for the curl write path; callbacks
  collected under `mu` and fired after release via a separate deferred
  queue; weak_from_this() in every async hop. State machine over the
  CORE-owned EngineState subset, auto-pause on 401/407 and on a 200 where
  206 was expected, validated resume via If-Range.

- digest (src/task/digest.cpp): OpenSSL EVP hash_file() for the optional
  post-download checksum; links OpenSSL::Crypto PRIVATE.

- Segmenter::release_segment(): hand a paused segment back to the pool
  unassigned so resume's assign_slot() picks it up instead of splitting a
  still-"assigned" range and orphaning its front half.

- DownloadHandle now names the real control block (vdm::task::
  DownloadTaskState, defined only in the engine TU) via a namespace-scope
  fwd decl and a public-but-effectively-engine-only ctor, replacing the
  nested State/friend pair. Every public signature is unchanged; DAEMON
  (vdm-79) confirmed sched/ names only the public API.

Fixes found while building the end-to-end suite (tests/task/engine_test.cpp,
9 cases against tools/testserver, green under ASan/UBSan and TSan):
- a dropped connection lost its unflushed WriteBuffer tail while advance()
  had already counted those bytes as done -> a retry resumed past an
  unwritten hole. Flush on the failure path.
- when the byte counters hit total while other workers were still live,
  teardown dropped their buffered tails. Now: cancel them and let each
  worker's own seg_finished drain it (the `assembling` state), last one
  starts verification -- no cross-thread buffer access.
- seg_head() let a 401 with credentials present abort before libcurl's
  resend; now it proceeds once and acts on the final status.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS
2026-09-10 20:19:31 +04:00
..

libveloxcore — public API

Status: M1 in progress. util/, net/ (http_client, probe, url, content_disposition), io/ (sparse_file, write_buffer), meta/veloxpart, and segment/ (segmenter, budget) are landed. The download entry pointvdm::Engine, vdm::task::DownloadSpec / DownloadHandle / DownloadCallbacks — is sketched in vdm/engine.hpp and vdm/task/download.hpp and out for DAEMON review: see core/docs/engine-api-m1.md. Bodies land in CORE stage 8; build against the value types now.

Layering (CLAUDE.md §3): this library knows nothing about JSON, SQL, Qt, or RPC. Input is a spec value; output is bytes on disk plus typed callbacks. DAEMON projects engine state onto the wire contract's TaskSummary / TaskDetail / events — see core/docs/proto-requests-m1.md for the shapes that projection needs frozen.

Every header under core/include/vdm/ compiles standalone (-Wall -Wextra -Wpedantic -Werror, C++23). Clean under ASan/UBSan and TSan.


util/ — foundations

vdm/util/error.hpp

enum class Error — the engine-wide failure taxonomy (network / HTTP / content / local I/O / metadata / probe / retry / internal). This is CORE's own vocabulary; it is not a wire type. error_name(Error) gives a stable snake_case string; is_retryable(Error) is the advisory retry hint the task policy consults.

struct ErrorInfo { Error code; std::string context; int http_status; bool retryable; Error cause; } — the payload carried by every failed Result. .to_string() renders "<name>: <context> (HTTP <n>)".

vdm/util/result.hpp

Result<T> — return-based error channel, a thin wrapper over std::expected<T, ErrorInfo>. Errors are returned, never thrown, on anything that runs during a transfer.

  • Result<int> r = 42; / Result<int> r = Err{Error::timeout, "..."}; / Result<T> r = Error::not_found;
  • r.has_value(), explicit operator bool, r.value() / *r / r->, r.error(), r.code(), r.value_or(x)
  • monadic and_then / transform / transform_error (forward to std::expected)
  • Result<void> specialization; vdm::ok() success sentinel
  • VDM_TRY(expr) — return the error if expr failed
  • VDM_TRY_ASSIGN(auto x, expr) — bind the value or return the error

vdm/util/bytes.hpp

Byte / ByteSpan / ConstByteSpan aliases; as_bytes(string_view) / as_chars(span). Little-endian fixed-width codec load_le<T> / store_le<T> and a bounds-checked sequential ByteReader (.u8/.u16/.u32/.u64, .raw(n), .lp_string(), .overran()). Built for the .veloxpart.meta reader and the 4-byte NM framing; every read is bounds-checked and latches on overrun (reader-first, fuzz-ready).

vdm/util/event_bus.hpp

EventBus — typed, thread-safe in-process pub/sub. subscribe<E>(fn) -> Token, publish<E>(ev) (synchronous, calling thread, registration order), unsubscribe(Token), and RAII subscribe_scoped<E> returning a Subscription. Handlers may (un)subscribe or publish during dispatch. Handlers must not throw. Not a hot-path structure — progress is coalesced to ≤4 Hz upstream.

vdm/util/thread_pool.hpp

ThreadPool — fixed-size std::jthread pool for bounded off-loop work (hashing, fsync batches, DNS pre-resolve). submit(fn, args...) -> std::future<R>; propagates exceptions through the future; drains already-queued tasks on destruction. Not the transfer loop — net/ will own one curl_multi per dedicated worker.

vdm/util/log.hpp

Sink interface — core does no I/O itself. LogSink abstract base; DAEMON installs one via set_log_sink(), default discards. CallbackSink adapter (with a min-level filter). VDM_LOG_{TRACE,DEBUG,INFO,WARN,ERROR}(category, fmt, args...)std::format syntax, only formatted when a sink is installed and wants the level.