149 lines
7.2 KiB
Markdown
149 lines
7.2 KiB
Markdown
# 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](https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-%28qBittorrent-5.0%29)
|
||
and [4.1-compatible WebUI API](https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-%28qBittorrent-4.1%29).
|
||
|
||
### 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.
|
||
|
||
### 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 save-path fingerprint based on validated path mapping, never a leaked
|
||
absolute host path.
|
||
|
||
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.
|
||
|
||
### 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](https://docs.syncthing.net/dev/rest.html),
|
||
[configuration model](https://docs.syncthing.net/users/config.html), and
|
||
[DownloadProgress event](https://docs.syncthing.net/events/downloadprogress.html).
|
||
|
||
### 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. Deployments must mirror
|
||
Syncthing's nested bind mounts into the client so the normalized API path and
|
||
the client filesystem path refer to the same bytes.
|
||
|
||
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/completion`](https://docs.syncthing.net/rest/db-completion-get.html)
|
||
response;
|
||
- per-file state from [`db/file`](https://docs.syncthing.net/v2.0.0/rest/db-file-get.html)
|
||
where supported;
|
||
- no relevant outstanding entries from
|
||
[`db/need`](https://docs.syncthing.net/v1.19.2/rest/db-need-get.html);
|
||
- 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_cache` performs 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/preview` fetches fresh source and target summaries and
|
||
full trees, expands optional `selected_paths` or
|
||
`selected_file_indices`, and returns an immutable definition plus its
|
||
`preview_revision`.
|
||
- `POST /test/v1/jobs` consumes `{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/placements` exposes 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.
|