Skip to content

Feat/dynamic frontend#8

Open
Ironboxplus wants to merge 97 commits into
mainfrom
feat/dynamic-frontend
Open

Feat/dynamic frontend#8
Ironboxplus wants to merge 97 commits into
mainfrom
feat/dynamic-frontend

Conversation

@Ironboxplus

Copy link
Copy Markdown
Owner

Description / 描述

Motivation and Context / 背景

Closes #XXXX

Relates to #XXXX

How Has This Been Tested? / 测试

Checklist / 检查清单

  • I have read the CONTRIBUTING document.
    我已阅读 CONTRIBUTING 文档。
  • I have formatted my code with go fmt or prettier.
    我已使用 go fmtprettier 格式化提交的代码。
  • I have added appropriate labels to this PR (or mentioned needed labels in the description if lacking permissions).
    我已为此 PR 添加了适当的标签(如无权限或需要的标签不存在,请在描述中说明,管理员将后续处理)。
  • I have requested review from relevant code authors using the "Request review" feature when applicable.
    我已在适当情况下使用"Request review"功能请求相关代码作者进行审查。
  • I have updated the repository accordingly (If it’s needed).
    我已相应更新了相关仓库(若适用)。

@github-actions

github-actions Bot commented Apr 5, 2026

Copy link
Copy Markdown

⚠️ PR 标题需以 feat(): , docs(): , fix(): , style(): , refactor(): , chore(): 其中之一开头,例如:feat(component): 新增功能
⚠️ The PR title must start with feat(): , docs(): , fix(): , style(): , or refactor(): , chore(): . For example: feat(component): add new feature.

如果跨多个组件,请使用主要组件作为前缀,并在标题中枚举、描述中说明。
If it spans multiple components, use the main component as the prefix and enumerate in the title, describe in the body.

如果是破坏性变更,请在类型后添加 !,例如 feat(component)!: 破坏性变更
For breaking changes, add ! after the type, e.g., feat(component)!: breaking change.

@Ironboxplus Ironboxplus force-pushed the feat/dynamic-frontend branch 6 times, most recently from 3b720cc to f0fc91a Compare April 12, 2026 06:35
@Ironboxplus Ironboxplus force-pushed the feat/dynamic-frontend branch 2 times, most recently from 66b9ff7 to bc14381 Compare April 28, 2026 07:06
@Ironboxplus Ironboxplus force-pushed the feat/dynamic-frontend branch from bc14381 to 0bf12c5 Compare May 8, 2026 13:14
PIKACHUIM and others added 2 commits May 12, 2026 21:45
…m#2471)

* fix(driver): fix 189 & 189pc fastcopy form local storage

* fix(driver): fix 189 & 189pc fastcopy form local storage

* fix(driver): fix 189 & 189pc fastcopy form local storage
Optimize the 189pc driver to implement AccessToken login
@Ironboxplus Ironboxplus force-pushed the feat/dynamic-frontend branch 2 times, most recently from 02e4430 to acad358 Compare May 13, 2026 16:15
j2rong4cn and others added 3 commits May 14, 2026 22:14
* add LinearMemory

* replace mmap with LinearMemory

* remove unused code

* add GuardedMemory; add `min_free_memoryMB` conf

* add HybridCache and StreamBuffer

* log

* rename SizedReadWriterAt to Section

* 重构 FileStream,改用 HybridCache

* 重构 HybridCache,更新方法名并添加回滚功能;优化请求和流处理逻辑

* 重构 StreamSectionReader 接口,使用HybridCache

* 在 NewGuardedMemory 函数中添加了对 LinearMemory 的最终化处理,以确保内存释放

* .

* 优化检查逻辑

* 重命名

* 改进、重命名

* 修复

* 添加测试

* 移除HybridCacheReader并引入DynamicReadAtSeeker

* 重构缓存读取逻辑,简化代码并引入ReadFromN方法

* 优化缓存配置注释并修复下载器部分大小限制逻辑

* 优化中断逻辑

* 优化下载器代码

* 修复bug

* HybridCache添加多文件缓存模式

* 优化下载器并发,添加测试

* 修复bug

* 重命名+注释

* .

* fix(net): always cleanup downloader on interrupt

* fix(net): guard chunk enqueue with context cancel

* fix(net): update interrupt logic and add download interrupt test

* fix(test): update concurrency limit in high concurrency test

* refactor(buffer): simplify ReadAt logic

* refactor(config): update memory configuration logic

* .

---------

Co-authored-by: Suyunmeng <Susus0175@proton.me>
@Ironboxplus Ironboxplus force-pushed the feat/dynamic-frontend branch from acad358 to 7995dbd Compare May 17, 2026 02:58
Copilot AI and others added 3 commits May 17, 2026 22:31
…#2478)

fix(139): remove RFC8441-incompatible Connection header

Agent-Logs-Url: https://github.com/OpenListTeam/OpenList/sessions/1c6b226d-02c4-4b43-80ad-5ab2861d30c3

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jyxjjj <16695261+jyxjjj@users.noreply.github.com>
* refactor(HybridCache)!: extract hybrid_cache package and rename cache_threshold to auto_memory_limit

* split HybridCache and stores into internal/hybrid_cache package
* introduce BackingStore abstraction with BufferStore and FileStore
* rename CacheThreshold to AutoMemoryLimit in config/env/bootstrap
* update stream/request integration and related tests

* rename singleFileCache and MultiFileCache to singleFileStore and MultiFileStore

* refactor(HybridCache): improve memory management strategies in NewHybridCache

* rename

* alias hybrid_cache to hcache

* omments
…returning HTTP 204 No Content on successful authentication (OpenListTeam#2476)
@Ironboxplus Ironboxplus force-pushed the feat/dynamic-frontend branch 2 times, most recently from 3c908da to d569888 Compare May 19, 2026 19:08
elysia-best and others added 6 commits May 21, 2026 15:52
* Squashed commit of the following:

commit f029df8
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 14:00:36 2025 +0800

    Add Terms of Use and Privacy Policy links to READMEs

    Added links to the Terms of Use and Privacy Policy in the English, Chinese, Japanese, and Dutch README files to provide users with easy access to legal information.

commit 00f825d
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 13:53:29 2025 +0800

    Update documentation links in README files

    Replaced plain documentation URLs with labeled links and icons in English, Chinese, Japanese, and Dutch README files for improved clarity and user experience.

commit 732dcfa
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 13:40:52 2025 +0800

    fix format

commit e2ad8ea
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 13:38:28 2025 +0800

    Revert "test large name"

    This reverts commit affedc8.

commit affedc8
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 13:37:43 2025 +0800

    test large name

commit 382cd64
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 13:34:42 2025 +0800

    Add Dutch README and update language links

    Added a new Dutch translation (README_nl.md) and updated language navigation links in the English, Chinese, and Japanese README files to include Dutch.

commit e880acb
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 13:29:51 2025 +0800

    Add AGPL-3.0 license links to README files

    Updated the English, Chinese, and Japanese README files to include direct links to the AGPL-3.0 license text and the LICENSE file for clarity and easier access.

commit a0d1ead
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 13:25:52 2025 +0800

    Move language and links sections below logo in READMEs

    Repositioned the language selection and related links sections to appear after the logo and separator in README.md, README_cn.md, and README_ja.md for improved layout consistency.

commit 70a0a32
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 13:23:36 2025 +0800

    Revise and unify README files across languages

    Updated README.md, README_cn.md, and README_ja.md to improve structure, add navigation links, clarify project purpose, and unify feature lists. Enhanced formatting, added acknowledgments to original authors, and improved legal/disclaimer sections for consistency across English, Chinese, and Japanese documentation.

commit 2f32120
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 12:56:26 2025 +0800

    Update Go Report Card badge URL in README

    Changed the Go Report Card badge to reference v3 instead of v4. This ensures the badge displays the correct status for the intended version.

commit 0fdfa2b
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 12:53:43 2025 +0800

    Update README.md

commit 8271361
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 12:53:22 2025 +0800

    Update Go Report Card badge URL in README

    Changed the Go Report Card badge link to remove the '/v3' suffix, ensuring it points to the correct repository path.

commit 41acb3e
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 12:52:46 2025 +0800

    Update project description in README

    Revised the introductory paragraph to emphasize OpenList's resilience and community-driven nature as a fork of AList, highlighting its commitment to defending open source against trust-based attacks.

commit 77aca66
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 12:50:45 2025 +0800

    Update README.md

commit 63a597f
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 12:49:57 2025 +0800

    Improve README badge formatting and alignment

    Reformatted the badge section in the README for better readability and visual alignment. Updated the div to use 'align="center"' and placed each badge on its own line with proper indentation.

commit fcf7530
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 12:46:38 2025 +0800

    Update logo size and remove migration note in README

    Set explicit width and height for the logo image and removed the note about migration progress, reflecting project updates.

commit 5f0645d
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 12:45:04 2025 +0800

    Revert README header to HTML

    Replaces markdown-based center alignment and badge/image syntax with HTML tags for better visual formatting and consistency in the README header.

commit 0f7ba95
Author: jyxjjj <773933146@qq.com>
Date:   Tue Jul 1 12:42:59 2025 +0800

    Revise README formatting and update project info

    Refactored the README to use markdown badge/link syntax, improved formatting, and clarified the disclaimer section. Updated Docker Deploy status, added a Contact Us section, and reordered the Contributors section for better project transparency and communication.

* chore(readme): fix sites

* chore(readme): fix sites
…stTeam#2507)

优化 PR 模板结构和检查项
增加 PR 中 AI 辅助披露说明和工具范围
在 Issue 模板中增加 AI 辅助内容字段
补充贡献指南中的社区政策和 AI 披露要求
将 Go 版本要求改为以 go.mod 声明为准
修正文档链接和行为准则格式细节
…tTeam#2452)

Add end-to-end offline download support for torrent files, magnet links, and ed2k links, including torrent parse and generate APIs, CAS-based rapid upload for Cloud189, and protocol-aware tool routing with transfer flow improvements.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: j2rong4cn <36783515+j2rong4cn@users.noreply.github.com>
Co-authored-by: j2rong4cn <j2rong@qq.com>
Co-authored-by: Suyunjing <suyunmeng@oplist.org.cn>
- Fix OpenListTeam#2343: update 115driver to v1.3.3 to handle float size values
- Fix OpenListTeam#2349, OpenListTeam#2356, OpenListTeam#2502: use base.UserAgent as fallback when User-Agent header is empty

The 115 CDN returns 403 "no cookie value" when the User-Agent header
is empty or doesn't match the cookie. This fix ensures a consistent
browser User-Agent is used for download link generation.
@Ironboxplus Ironboxplus force-pushed the feat/dynamic-frontend branch from d569888 to 4525637 Compare May 25, 2026 18:21
cyk added 12 commits June 21, 2026 01:23
The libmpv "Failed to recognize file format" symptom came from one
underlying mistake: 115's GetFolderInfoByPath endpoint (yuque
rl8zrhe2nag21dfw, "Get folder details") is folder-oriented. Its struct
carries Count / FolderCount / PlayLong and the Size field is
byte-accurate only for directories; for FILE paths Size is empty or
"0". driver.Get nevertheless called it for every path. The resulting
Obj had FS = 0, op.Get returned it to ServeHTTP, and the empty-file +
Range branch in internal/net/serve.go answered with 200 OK +
Content-Length: 0. mpv couldn't recognize the empty stream.

Webpage playback masked the bug because op.List warmed dirCache with
GetFilesResp_File entries (FS arrives as int64 there and is always
correct). mpv direct access skipped op.List, hit driver.Get cold, and
broke.

Fix:
- Introduce fromFolderInfo gateway used by driver.Get:
    * folder → return Obj from folderInfoToObj (fast path, single API call)
    * file   → return errs.NotImplement so op.Get falls through to its
               list-based path, which pulls FS out of
               GetFilesResp_File.FS (int64, reliable)
- folderInfoToObj extracts only the fields the gateway actually uses
  (FileID, FileName, FileCategory, Sha1, PickCode). Size is not parsed —
  files are rejected before any caller would read FS.
- log.Debugf in driver.Get prints the raw resp.Size / FileCategory so
  future diagnoses don't need to theorize.

Trade-off: cold-cache file access costs 3 WaitLimit-gated SDK calls
(wasted GetFolderInfoByPath on the file + GetFolderInfoByPath on the
parent for its FileID + GetFiles to list the parent) plus the eventual
DownURL = 4 calls. With default limit_rate=1 req/s that's ~3s of pure
rate-limit wait. Steady-state access within dirCache TTL (5 min)
collapses back to a single DownURL. Bumping limit_rate to 5–10 in the
storage config keeps the worst case under 1s.

Verified in production with curl + libmpv UA + Range: bytes=0-1023 →
206 Partial Content / Content-Length: 1024 / Content-Range bytes
0-1023/36767958354 / Last-Modified populated (proving the Obj came
from the List path, not the bogus folderInfoToObj of an earlier
attempt).

5 unit tests pin the contract: folder fast path, file/folder gateway
behavior, and an existing TestGetReturnsObjForExistingFolder updated
to set file_category="0" so it actually exercises the folder branch.
… names

Two changes that go together because the second was useless without the
first. Push to feat/dynamic-frontend (the active branch) was not in the
trigger list, so commits like Fix 3 b80688c never produced a fresh
:front-rolling image -- the user kept pulling stale binaries and the
mpv "Failed to recognize file format" symptom appeared unfixed when in
fact CI had simply not run.

- Replace push:main + pull_request:copy triggers with push:feat/dynamic-frontend
  so every commit on the active branch rebuilds the four docker variants.
- Drop the shared IMAGE_NAME env and give each matrix row its own
  image_name: openlist, openlist-ffmpeg, openlist-aria2, openlist-aio.
  Tags collapse from openlist:front-rolling-{variant} to
  {image_name}:front-rolling, so the pull path matches the image
  intent (ghcr.io/.../openlist-aio:front-rolling, etc.).
- :latest now applies to each image when its frontend_channel is
  "latest", rather than only openlist:latest.
- Build cache key reuses the per-variant image_name so caches don't
  collide across variants.
Required by movi-player's FFmpeg WASM which uses SharedArrayBuffer
for multi-threaded decoding. Without these headers, browsers block
SharedArrayBuffer and the player fails to initialize.
The dynamic frontend cache in the data volume was overriding newer
embedded dist from Docker images. On networks where GitHub API is
unreachable, the stale cache persisted indefinitely.

Now initStatic() always starts with the embedded dist. The watcher
still runs in the background and hot-swaps to a newer version from
GitHub when available. Also default FrontendRepo to Ironboxplus fork.
… key

- All workflows now default to Ironboxplus/OpenList-Frontend instead of
  upstream, so builds embed the correct frontend without needing vars.
- build.sh default also updated.
- test_docker.yml: rolling cache key now includes the release asset name
  so cache is busted when a new rolling release is published.
Move JOURNAL.md, OVERVIEW.md, COMPATIBILITY_REPORT.md to parent
E:\Go\Openlist\. Update CLAUDE.md to workspace-wide aggregated version
covering backend, frontend, and movi-player.
Pulls in two auth fixes:
- token refresh detached from caller context (WithoutCancel + 30s
  timeout) so a canceled streaming request can no longer kill an
  in-flight refresh and lose the rotated one-time refresh_token
- 40140117 (refresh frequently) and 40140120 (refresh token error)
  no longer misclassified as stale-access-token errors that trigger
  another refresh attempt
The rebase merged upstream OpenListTeam#2596 (sdk.ErrObjectNotFound mapping) with our
ErrDataEmpty handling into a union inside driver.Get. Extract it into a
unit-testable helper so both not-found sentinels are covered directly:
ErrObjectNotFound is shadowed by ErrDataEmpty for GetFolderInfoByPath
(request.go collapses an empty/[] payload before folder-info unmarshal),
so it is unreachable via an HTTP mock and an inline test could not exercise it.

- isObjectNotFound(err): ErrObjectNotFound || ErrDataEmpty, both errors.Is-safe
- TestIsObjectNotFound: both sentinels, wrapped forms, unrelated error, nil
@Ironboxplus Ironboxplus force-pushed the feat/dynamic-frontend branch from aba56c7 to 98707ee Compare June 20, 2026 17:31
cyk added 17 commits June 21, 2026 23:24
…ding progress

Backend plugin system: swap wazero/WASM runtime for an in-process yaegi
(Go-source) interpreter so admins can author plugins in plain Go.
- internal/plugin: interp.go (Runtime: Load/Unload/hot-reload, curated host
  symbols), registry.go (hook event bus), hooks.go (Hook* constants +
  SettingGetter/Setter injection), manager.go (file watcher), manage.go
  (user-plugin CRUD + enable/disable)
- bootstrap/plugin.go bridges op StorageHook/ObjsUpdateHook into the registry
  and wires setting capabilities (no op import cycle)
- fire HookFsListAfter / HookFsLinkAfter / HookUserLoginAfter from handles
- admin API: /admin/plugin list/get/save/delete/enable

Header: FsListResp.mount_details (cache + singleflight backed, 429-safe,
guest/HideStorageDetails gated) powers a dynamic disk-usage widget.

Also: favorites (db/model/handles), storage loading progress (op/loading),
115_open video play, doc restructure (INDEX/OVERVIEW + docs/*).

Tests: internal/plugin, server/handles, internal/op, internal/model green.
Brings the VideoPlayResp fix: 115's /open/video/play returns
file_size/duration/width/height as quoted strings; FlexInt64 now tolerates
both string and number, fixing "cannot unmarshal string into Go struct
field VideoPlayResp.file_size of type int64".
Cryptographically-authenticated, real-time storage-config sync between nodes
using a distributed CRDT. Disabled by default; fully inert without config.

- crypto: PSK->HKDF->AES-256-GCM AEAD envelope (membership auth + confidentiality
  for the secrets in storage configs), ed25519 per-node identity, node id ==
  hash(pubkey) so records are self-authenticating and origin can't be forged.
- state: per-mount LWW-CRDT keyed by content hash (idempotent: identical config
  => no churn, 'peers seeing the same token don't change'), Lamport versioning,
  signed records, tombstones.
- config: file-backed (enable/key/peers/share filters/intervals/apply_remote/
  share_deletes), admin API + status.
- transport: sealed envelope, timestamp+nonce replay protection.
- engine: push on healthy token refresh (health-gated, never propagates broken
  tokens), pull on token-invalid, periodic anti-entropy announce backstop;
  applies peer configs via op create/update/delete (loop-safe via content hash).
- op: fire storage hook on saveDriverStorage (token-refresh path) + add
  NotifyStorageTokenInvalid signal.
- wiring: bootstrap Init/Start + /api/cluster/sync peer endpoint and
  /admin/cluster config/status routes.
Replaces the stateless HTTP request/reply sync link with a stateful, persistent
WebSocket so nodes behind NAT can participate: a NAT'd node dials OUT to its
configured reachable peers and keeps the link open (it cannot be dialed itself),
while a reachable node accepts inbound links at /api/cluster/ws and RELAYS
newly-applied records between everyone it is connected to — so two NAT'd nodes
converge through a common reachable peer. Same sealed AEAD envelope per frame
(PSK auth + confidentiality), so crypto/CRDT layers are unchanged. Adds a dial
supervisor (auto-reconnect/backoff), ping keepalive to hold NAT mappings, and
relay-on-change so propagation terminates. Drops the old POST /cluster/sync in
favour of GET /cluster/ws.
…registry

Covers AEAD seal/open round-trip + wrong-key/tampered-aad rejection, ed25519
identity binding (node id == hash(pubkey)) and record self-authentication,
LWW merge + content-hash idempotency + tombstones, envelope replay/stale
rejection, config active/share-filter gating, and wsURL/conn registry.
…auto-discovery

Redesign the cluster plugin around three user requirements:

- Sync ONLY credentials. Instead of replicating whole storage configs, extract
  just the credential fields (token/cookie/secret/refresh/access/...) from a
  driver's Addition by name heuristic and overlay them onto peers, preserving
  every node-local field (mount path, root folder, cache, order, proxy). New
  creds.go isolates extraction/apply/hash; only changed fields are written, so
  identical credentials cause no churn or re-init.

- Manual multipartite sync groups. A group links storages across nodes that
  share one account; the group set is cluster-shared (LWW doc, signed). Creds
  flow per-group, idempotent by cred-hash, health-gated (never push a broken
  token; pull from peers on token-invalid/non-WORK).

- Auto-discovery. Any node that can open our AEAD envelope is admitted; public
  nodes advertise an addr and peers learn+dial the rest via inventory exchange
  (PEX). Manual peer lists are gone; an optional seed only bootstraps the first
  join. Node inventory (per-node storages) is gossiped to power the UI.

state.go now holds groups + credential records + inventory CRDTs; status.go
exposes a redaction-safe overview (nodes/liveness, per-group sync state, live
connections, activity log, rollup stats) for the admin UI. Config drops the
driver/mount filters and peers list in favour of key/label/addr/seeds. New
POST /admin/cluster/groups endpoint. Unit tests cover credential
extraction/overlay/idempotency, group + cred LWW, forgery rejection and
inventory merge.
115 online-play returns HLS on 115's CDN; the browser fetching it directly
fails CORS. Add /video_proxy: a signed (sign.Sign over the upstream URL,
no open-relay) endpoint that streams an upstream media URL through OpenList,
rewriting inner segment/key/sub-playlist URIs in HLS manifests so they also
route back through the proxy. FsVideoPlay now wraps every transcoded source
URL through it. Unit tests cover URL build/verify, m3u8 rewrite, isM3U8.
ClusterGetConfig redacts the key to '********' (it is polled live), so add a
dedicated admin-only GET /admin/cluster/key returning the real key for the UI's
show-key toggle.
- model.ObjExtra interface + GetExtra() helper (walks ObjUnwrap chain)
- fsread ObjResp.extra: forward-compatible metadata passthrough
- 115_open Obj.Extra(): starred/duration/resolution/tags from API json
- auth UpdateCurrent: block username/password change without manage_user_info
115 refresh tokens rotate single-use, so independently-refreshing cluster
nodes mutually invalidate each other; and a node booting with a stale/dead
token had no recovery path — NotifyStorageTokenInvalid fired a "token-invalid"
hook that NO driver ever called, so the pull path was dead code.

Design (per spec: token defaults invalid; only share a proven-valid token;
pull when invalid; on success mark valid + push):

- 115-sdk-go v0.2.11: WithOnTokenValid/WithOnTokenInvalid callbacks fired from
  the authRequest choke point — valid on a successful authed request, invalid on
  a direct 40140120 or when a refresh dies (40140117 throttle does NOT fire).
- 115 driver: edge-trigger via tokenInvalid atomic.Bool so op hooks fire only on
  real invalid<->valid transitions (no per-request churn): valid->invalid calls
  NotifyStorageTokenInvalid, invalid->valid calls NotifyStorageTokenValid.
- op/hook: add NotifyStorageTokenValid ("token-valid" storage hook) alongside
  the existing NotifyStorageTokenInvalid.
- cluster engine: onStorageHook gains "token-valid" (re-share if WORK, else pull)
  and "token-invalid" (dropOwnCred + pullGroup). canOfferCred/ownTokenHealthy
  gate anti-entropy so a node only advertises its OWN cred while its token is
  proven healthy (Status==WORK) — peer-authored records always relayable. This
  stops a stale/boot token from poisoning peers. seedLocalCreds drops own cred
  for unhealthy member mounts at boot, then pulls.
- state: dropOwnCred removes only our own-origin record so a dead token's Lamport
  version can't out-rank a peer's valid one and block recovery.

Tests: cluster TestDropOwnCred + TestCanOfferCred green; SDK callback tests green.
Wire format unchanged (still credDigest/syncMessage) — new nodes are
backward-compatible with old nodes, so canary (one node at a time) is safe.
The movi 115 transcoded preview serves adaptive HLS that drops the source
mkv's embedded subtitle tracks. The frontend can't call 115 directly (no auth),
so expose 115's VideoSubtitle API through the backend; the player merges these
provider-sourced subtitles with sidecar subs so subtitles are available on every
quality source, including transcoded streams.

- internal/driver: VideoSubtitleProvider interface + VideoSubtitleInfo struct.
- 115 driver: VideoSubtitle() maps the 115 /open/video/subtitle response, skipping
  caption-map entries (IsCaptionMap==1) and URL-less items; Title falls back to
  Language.
- handles/fsread: FsVideoSubtitle (mirrors FsVideoPlay) wraps subtitle URLs in
  BuildVideoProxyURL (signed same-origin proxy).
- router: POST /api/fs/video_subtitle.

Note: 115's subtitle list may not include every embedded track of the source
stream — it's a best-effort superset of what 115 exposes.
Adds a proxy_user_agent storage option (default empty = passthrough the client's
UA). When set, the configured UA is applied to the client request header BEFORE
the link is resolved, so drivers that sign the download URL with the request UA
(e.g. 115's DownURL, keyed by LinkCacheUA) use it, and transparent-proxy drivers
pick it up via ProcessHeader. Deliberately not mutating the cached *model.Link
header, which would race across concurrent downloads and leak the UA into the
link cache.
FsVideoSubtitle becomes the unified subtitle source: it lists the video's folder
once and returns same-name sidecar subs (basename.*{srt,vtt,ass,ssa,sup}, any
driver, signed /p URLs) alongside provider tracks (e.g. 115). Matching requires a
'.' right after the basename so "Movie2.srt" / "passthrough.ass" don't false-match
"Movie.mkv". No longer 400s for non-provider drivers.
Stops the frequent-refresh spiral: cooldown after 40140117, dead-latch
after 40140120 until a new refresh_token is injected.
115 Open can prove credentials healthy after an earlier init or auth failure, but the stale storage status kept the existing cluster Status == WORK gate from sharing those credentials. Restore the status at the token-valid hook before dispatching the existing cluster hook.

The persistence path updates only the status column so a successful request cannot overwrite unrelated storage fields from a stale in-memory copy.

Constraint: Cluster credential sharing intentionally relies on storage.Status == work

Rejected: Bypass cluster status gate for token-valid | would hide stale health state instead of repairing it

Confidence: high

Scope-risk: moderate

Directive: Do not relax cluster credential sharing gates unless status recovery is proven insufficient

Tested: go test ./internal/db ./internal/op ./drivers/115_open ./internal/cluster -count=1

Not-tested: Full go test ./... is blocked by missing public/dist, internal/net HTTPS proxy env expectation, and aria2 localhost:6800 dependency
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.