feat(remote): reopen a lost remote session and resume at the current title

This commit is contained in:
imSp4rky
2026-10-27 13:24:31 -06:00
parent 9017ab95b7
commit 756c2ce2b5
12 changed files with 420 additions and 56 deletions
+4
View File
@@ -45,6 +45,7 @@ Health check. **This is the only route exempt from authentication**: you can cal
"status": "ok",
"version": "5.3.0",
"code_hash": "1d22a1e",
"draining": false,
"update_check": {
"update_available": false,
"current_version": "5.3.0",
@@ -55,6 +56,8 @@ Health check. **This is the only route exempt from authentication**: you can cal
`code_hash` fingerprints the framework source the server operates, so it names the code itself rather than the release. It is the same value for a git checkout and for an installed package built from that checkout. It changes when you edit anything under `unshackle/core`, `unshackle/commands`, `unshackle/utils`, or `unshackle/vaults`. Service modules are not included. unshackle sets the value to `null` only when it cannot read the source files.
`draining` is `true` while the server refuses new remote sessions and jobs before a restart.
If the update check fails (for example, no network), `update_available` and `latest_version` are `null` while `current_version` still reports the installed version.
| Status | Meaning |
@@ -1243,6 +1246,7 @@ Return remote session metadata and validity.
"service": "EXAMPLE",
"valid": true,
"expires_in": 300,
"draining": false,
"track_count": 4,
"title_count": 10
}
+1 -1
View File
@@ -126,7 +126,7 @@ while the server restarts.
| `SESSION_NOT_FOUND` | 404 | The remote-dl session does not exist or has expired. |
| `TRACK_NOT_FOUND` | 404 | A referenced track ID is not present in the remote session. |
| `CONFLICT` | 409 | The target resource is in a state that disallows the action (retrying a running job, prioritising a non-queued job, clearing cache during an active download). |
| `SERVER_DRAINING` | 409 | The server is about to restart and starts no new remote session or download job. Open work continues. Retryable after the restart. |
| `SERVER_DRAINING` | 409 | The server is about to restart and starts no new remote session or download job. Open work continues. Retryable after the restart. A `dl --remote` client waits for the restart and opens a new remote session. |
| `SERVICE_ERROR` | 422 | The streaming service's API returned an error, or the service does not support the requested operation. |
| `DRM_ERROR` | 422 | DRM or license acquisition failed. |
| `RATE_LIMITED` | 429 | The streaming service is rate-limiting requests. Retryable. |
+24
View File
@@ -625,11 +625,15 @@ existing file as a directory, and logs a warning.
"service": "EXAMPLE",
"valid": true,
"expires_in": 300,
"draining": false,
"track_count": 12,
"title_count": 40
}
```
`draining` is `true` after the operator starts a drain before a restart. The client reads
it before each title, so it knows of the restart before it occurs.
!!! warning "`expires_in` is the configured TTL, not time remaining"
The `expires_in` value reports the store's configured `session_ttl`, not the
seconds left before this specific remote session expires.
@@ -660,6 +664,26 @@ remote session API and a good template for any client.
(merging returned `session_headers`/`session_cookie_jar`, re-parsing `manifests`) →
get the licence in proxy mode or `server_cdm` mode → `close()` (`DELETE`, saving any returned
`cache`).
- **Server restarts.** The server keeps remote sessions in memory only, so a restart
loses them all. The client recovers in two ways:
- A drain announced through `draining`: the client finishes the current title, then
closes its remote session, so the drain can end and the refreshed cache comes back.
It waits until `/api/health` reports the restarted server without `draining`,
opens a new remote session, and continues with the next title.
- A lost remote session (`SESSION_NOT_FOUND`, a connection error, a timeout, a closed
tunnel or a gateway error page): the client waits for `/api/health`, then checks the
old remote session. A live remote session takes the call again only after a connection
error, because a timed-out call can already have run. A lost one is replaced: the
client opens a new remote session, sends `titles` and the current title's `tracks`
again, and then sends the failed call again. A title or track that the new remote
session gives another ID keeps its old ID on the client, and the client sends the
new ID. The client matches a title by season and episode number, and a track by its
attributes.
The client waits up to 30 minutes for the server, which covers a deploy's drain. It
stops with an error if the new remote session licenses differently from the old one,
for example after a change to the API key's `server_cdm` grant. An interactive login
asks for its input again.
- **Server resolution.** `resolve_server()` reads `config.remote_services.<name>`
into `{url, api_key, services, server_cdm}`, injecting the `server_cdm` flag into
the services map as `_server_cdm`.