8.5 KiB
qBittorrent and Syncthing integration
Adapters are version-aware boundaries. Core orchestration consumes normalized resource, route, and progress models and never branches on raw service response shapes.
qBittorrent Web API
The implementation targets qBittorrent 4.5–4.6 and 5.x releases and detects the application and Web API versions at startup. The authoritative references are the official 5.0 WebUI API and 4.1-compatible WebUI API.
Adapter capabilities
| Need | Web API family | Archive Control rule |
|---|---|---|
| Authenticate | auth/login, cookie session |
Log no credentials/cookies; reauthenticate once on expiry |
| Detect compatibility | app/version, app/webapiVersion, build info |
Require qBittorrent 4.5.0+ because exact metainfo export is mandatory |
| List/lookup | torrents/info, properties |
On-demand and hash-scoped where possible |
| Read file state | torrents/files |
Normalize indices, paths, selected/skipped, size, progress |
| Export metainfo | torrents/export |
Required before staging; fail if exact metainfo unavailable |
| Add target | torrents/add |
Explicit save path, stopped/paused, manual management |
| Set selection | torrents/filePrio / selection endpoint |
Encode only selected vs skipped, never preserve priority levels |
| Recheck | torrents/recheck plus state polling |
Keep stopped; any download activity fails the job |
| Remove entry | torrents/delete |
Eviction uses deleteFiles=false only |
The adapter accounts for terminology/behavior changes such as paused versus stopped states. It does not mutate qBittorrent preferences, categories, tags, limits, queueing defaults, or global save-path behavior.
Syncthing route validation normalizes both absolute and ~/ folder-path
spellings under the configured sync root before comparing an existing folder;
an equivalent pre-existing pair is adopted without rewriting it.
Inventory normalization
For each torrent the client derives canonical v1/v2 identity from reliable API and exported-metainfo data, keeps the qB-local hash separately, and calculates:
- total and selected-complete logical bytes;
- normalized selected and selected-complete file-index sets;
- torrent runtime state;
- canonical path flag and content revision;
- a validated qBittorrent
save_path, retained only in the client's local normalized observation.
qBittorrent 5 may expose a pure-v2 or hybrid torrent under the first 20 bytes
of its v2 hash while separately advertising full infohash_v1 and
infohash_v2 values. Lookup matches all advertised identities, rejects
ambiguous matches, validates the canonical hashes from exported metainfo, and
uses only qBittorrent's returned local hash for later mutation calls.
Complete content trees are built only for a chosen resource or job preflight. All entries retain qBittorrent's stable torrent file index. Renamed/noncanonical content paths are rejected in v1 because they cannot be transported and merged without ambiguity.
Per-torrent local content roots
save_path is not inventory, placement, or protocol data. It is qBittorrent
metadata used only by the daemon that queried qBittorrent. Before staging a
source, merging into an existing target, applying post-recheck permissions, or
evicting files, that daemon maps the torrent's API-visible save_path through
qbittorrent.api_root/local_root. The most-specific
qbittorrent.local_path_overrides mapping wins when a nested path is exposed
through a distinct client container mount.
An ordinary nested qBittorrent path such as /media/Data/Downloading needs no
per-resource configuration: it resolves beneath the configured root. A path
outside that root, an unmapped distinct mount, or a mapped local path that is
not a visible real directory fails the affected job before filesystem mutation.
This check is intentionally per resource; an unrelated malformed qBittorrent
entry cannot prevent normal resources from being staged or transferred.
Verification guard
The client captures transfer counters and state before recheck, issues recheck, and watches until success, timeout/stall, or failure. Success requires every selected file complete and no missing/error pieces. Any downloading state or increase in received payload bytes causes immediate stop and a failed job with the affected path/piece detail available from the adapter when possible.
Syncthing REST and events APIs
Clients authenticate using the configured API key header and discover exact service version/capabilities. Primary references are Syncthing's official REST conventions, configuration model, and DownloadProgress event.
Discovery and route management
The adapter reads system status/version and configuration, then selects folders that:
- are
sendreceive; - map beneath the configured API-visible and local sync roots;
- contain exactly the local device and one peer device;
- pair one registered cache with one registered archive client;
- have a safe relative filesystem path.
The folder's native ID becomes route_id. Multi-device, send-only,
receive-only, out-of-root, or ambiguous folders are reported as unsupported and
left unchanged.
Syncthing may serialize a folder path relative to its home as ~/.... The
client normalizes that notation beneath the configured API-visible sync root
before applying the API-to-local root mapping. When that folder is nested
below qB's content root, an exact local_path_overrides entry must map it
through the qB bind mount. Do not mirror it as a second nested client bind
mount: it denotes the same host bytes but a distinct mount namespace boundary,
which prevents hardlink staging.
Provisioning uses idempotent device and folder configuration updates. Each
client receives its peer device ID and optional advertised addresses
(dynamic by default). It changes only the specific device/folder objects
owned by the provisioning command. Read-back verification must agree before
nonce exchange begins.
Transfer progress and completion
DownloadProgress events are filtered to the job's exact staging prefix and
used for live block-level byte estimates. Their percentage is advisory. The
adapter falls back to coarser polling if events are unavailable.
Final completion combines:
- job-prefix files present and matching manifest logical sizes;
- Syncthing folder/local-global completion from the official
db/completionresponse; - per-file state from
db/filewhere supported; - no relevant outstanding entries from
db/need; - valid manifest digest and ready marker.
The client can request a scoped rescan after publishing/cleanup. It does not
declare transfer success from a 100% progress event alone.
Error normalization
Each adapter maps transport errors, authentication failure, incompatible API, missing torrent/folder, stale external state, permission failures, and service health into stable protocol error codes. Responses and exception text are redacted before logging because service products may echo URLs, headers, or filesystem paths. Authentication failures are permanent until configuration changes; transient service absence moves an active job to waiting.
Bot-free control adapter
The loopback-only test adapter calls the same ArchiveControlService used by
Telegram:
GET /test/v1/resources?operation=archive|unarchive|evict_cacheperforms a complete, paged, on-demand summary scan and reconciles placements.GET /test/v1/resources/{info_hash}/tree?client_id={client}fetches one complete revisioned content tree.POST /test/v1/jobs/previewfetches fresh source and target summaries and full trees, expands optionalselected_pathsorselected_file_indices, and returns an immutable definition plus itspreview_revision.POST /test/v1/jobsconsumes{preview_revision, definition}and rejects a deterministic protobuf SHA-256 mismatch.- Job list/detail/cancel/hide/clear endpoints expose the shared ownerless job
state, while
GET /test/v1/placementsexposes reconciled generations.
Lower-level inventory-query, route, scheduler, and backup endpoints remain for contract and failure-injection tests. This adapter must never bind outside loopback.