Skip to content

fix(139): throw instead of silently break when folder not found in path - #121

Open
PIKACHUIM wants to merge 3 commits into
mainfrom
fix/139-resolve-catalog-id-silent-fallback
Open

PIKACHUIM wants to merge 3 commits into
mainfrom
fix/139-resolve-catalog-id-silent-fallback

Conversation

@PIKACHUIM

Copy link
Copy Markdown
Member

fix(139): throw instead of silently break when folder not found in path

问题描述

挂载 139 云盘并访问子目录(例如 /DISK1-4T/139)时,文件列表始终返回根目录内容,无法正常列出子目录文件。

根本原因

resolveCatalogId() 负责将虚拟路径逐段解析为实际的 catalogID。当某个路径段在文件夹列表中找不到时,原代码使用 break 静默退出循环:

// 修复前
if (foundFolder) {
  currentCatalogId = foundFolder.catalogID
} else {
  break  // ← 静默退出,currentCatalogId 仍为根目录 ID
}

break 会让方法悄无声息地返回当前已解析到的最后一个有效 catalogId(通常就是根目录),导致 list() 和 get() 表面上返回 200,但内容是根目录而非目标路径,用户完全无从得知出了什么问题。

常见触发场景:存储配置的 type 字段未填写(应填 personal 或 personal_new),导致 API 版本选错,文件夹列表为空,路径第一段即匹配失败。

修复方案

将 break 改为 throw,附带明确的错误信息(包含未找到的路径段名称和完整路径):

// 修复后
if (foundFolder) {
  currentCatalogId = foundFolder.catalogID
} else {
  throw new Error(
    `[139] folder not found: "${part}" in path "${physicalPath}"`,
  )
}

修复效果

  • 路径解析失败时,list() / get() 返回明确的错误,前端提示错误而非显示错误内容
  • 日志中可以直接看到是哪个路径段找不到,方便用户自查账号类型配置是否正确
  • 行为与其他驱动一致(找不到路径应报错,而非降级)

变更范围

文件 变更
src/backend/drivers/139/driver.ts break → throw new Error(...) (+3/-1)

测试

  • 配置正确的 139 账号:路径解析成功,行为与修复前相同 ✅
  • 配置错误(type 字段留空的老账号):之前返回根目录,现在返回明确错误 ✅

关联

复现环境:https://openlist-web-dpw1236efa31.edgeone.cool/139/DISK1-4T/139

@PIKACHUIM PIKACHUIM linked an issue Oct 11, 2026 that may be closed by this pull request
4 tasks done
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
openlist-work a3855e3 Oct 16 2026, 09:13 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
openlist-tsworkers a3855e3 Oct 16 2026, 09:13 AM

@pikachuren
pikachuren force-pushed the fix/139-resolve-catalog-id-silent-fallback branch from 1edbbde to d122278 Compare October 11, 2026 05:49
…l results

1. drivers/139/driver.ts — resolveCatalogId() used `break` when a path
   segment was not found in the folder listing, silently returning the
   last successfully resolved catalogId (typically the root).  list()
   and get() then appeared to succeed but returned root directory
   contents for any path whose intermediate folder could not be matched,
   producing the 'always shows root' symptom.  Changed to `throw` with
   a message that includes the missing segment and full path so
   misconfigured account types are immediately visible in logs.

2. server/fs.ts — when resolvePath() returns isVirtual=true the storage
   object is null and cacheExpiration was set to `undefined`.  The front
   end interprets undefined as a non-zero TTL and caches the Virtual
   result; subsequent requests hit the cache and keep showing the Virtual
   provider even after the real storage comes online.  Fixed by returning
   0 for Virtual paths so the front end never caches them.
@pikachuren
pikachuren force-pushed the fix/139-resolve-catalog-id-silent-fallback branch from d122278 to ee136b6 Compare October 11, 2026 05:53
@PIKACHUIM

Copy link
Copy Markdown
Member Author

修复不了,好像不是这个问题

wumingzhinu added a commit to wumingzhinu/OpenList-Worker that referenced this pull request Oct 13, 2026
维护者反馈 OpenListTeam#121(break→throw)并未解决症状 1:按名查找目录成功时
任何 throw 都不会触发,问题出在查找成功之后——解析结果或请求目标
host 静默退化为根目录:

- init() 在 personal_new 取不到 queryRoutePolicy 下发的 personal host
  时硬失败(对齐 Go 版 PersonalCloudHost is empty → Init error),
  不再静默回退默认域 yun.139.com——默认域的 /file/list 是 legacy
  端点,忽略 parentFileId 恒返回根目录内容,且因按名解析仍成功而不
  触发任何报错(issue OpenListTeam#112 症状 1、OpenListTeam#121 无效的原因)。
- 新增 buildRequestUrls:personal_new 拒绝把业务端点发到默认域;
  候选 URL 按 host 直拼(现网 /hcy 形态)优先,404/route not found
  时回退 orchestration 形态(裸 host 补 /orchestration),仅路由类
  错误重试——对齐 gaozhangmin/boxplayer 的候选序列与重试语义。
- /file/list 目录项 id 并集回退 fileId||catalogId||catalogID;仍拿
  不到 id 时 resolveCatalogId 抛错,杜绝 listFiles(undefined) 回退
  parentFileId="/" 的静默列根目录路径。
- 错误信息附带请求 URL,非 JSON 响应显式报错,便于复现者直接定位。

新增 3 组回归测试(id 并集与空 id 报错、init 硬失败、URL 候选与
重试语义);139 驱动测试 9/9 通过,tsc --strict、prettier 通过。
未做端到端真实 API 验证(无139 账号凭据)。

Relates to OpenListTeam#112

Co-Authored-By: MiMo (OpenCode) <noreply@opencode.ai>
MoForgt added a commit to MoForgt/OpenList-Worker that referenced this pull request Oct 14, 2026
维护者反馈 OpenListTeam#121(break→throw)并未解决症状 1:按名查找目录成功时
任何 throw 都不会触发,问题出在查找成功之后——解析结果或请求目标
host 静默退化为根目录:

- init() 在 personal_new 取不到 queryRoutePolicy 下发的 personal host
  时硬失败(对齐 Go 版 PersonalCloudHost is empty → Init error),
  不再静默回退默认域 yun.139.com——默认域的 /file/list 是 legacy
  端点,忽略 parentFileId 恒返回根目录内容,且因按名解析仍成功而不
  触发任何报错(issue OpenListTeam#112 症状 1、OpenListTeam#121 无效的原因)。
- 新增 buildRequestUrls:personal_new 拒绝把业务端点发到默认域;
  候选 URL 按 host 直拼(现网 /hcy 形态)优先,404/route not found
  时回退 orchestration 形态(裸 host 补 /orchestration),仅路由类
  错误重试——对齐 gaozhangmin/boxplayer 的候选序列与重试语义。
- /file/list 目录项 id 并集回退 fileId||catalogId||catalogID;仍拿
  不到 id 时 resolveCatalogId 抛错,杜绝 listFiles(undefined) 回退
  parentFileId="/" 的静默列根目录路径。
- 错误信息附带请求 URL,非 JSON 响应显式报错,便于复现者直接定位。

新增 3 组回归测试(id 并集与空 id 报错、init 硬失败、URL 候选与
重试语义);139 驱动测试 9/9 通过,tsc --strict、prettier 通过。
未做端到端真实 API 验证(无139 账号凭据)。

Relates to OpenListTeam#112

Co-Authored-By: MiMo (OpenCode) <noreply@opencode.ai>
pikachuren added 2 commits October 16, 2026 16:32
#121 把 resolveCatalogId 的 break 改为 throw 后问题依旧,因为还有三条
绕过 throw 的静默回退路径:

1) init() 捕获 queryRoutePolicy 失败后仅告警,personalHost 停在默认域
   yun.139.com。新版个人云的 /file/list 只存在于路由下发的 host 上,打到
   默认域会命中旧版端点:该端点忽略 parentFileId、恒返回根目录内容,且
   返回 HTTP 200 + success:true —— 按名解析目录因此仍然成功,任何 throw
   都不会被触发,表现为访问子目录却看到根目录全部文件夹,点击文件又出现
   /手机软件/阅读.apk/阅读.apk 自我嵌套。现对齐 Go 官方驱动
   ensurePersonalCloudHost:personal_new 拿不到路由 host 即初始化失败;
   旧版个人云走 /orchestration 硬编码路径,不依赖路由,仍只告警。

2) /file/list 目录项在部分账号/响应形态下以 catalogId|catalogID 携带 id
   而非 fileId(社区实现 gaozhangmin/boxplayer 的字段映射可佐证)。只读
   fileId 会拿到 undefined,listFiles(undefined) 回退 parentFileId="/"
   静默列出根目录。现改为并集回退,并在任何 id 都拿不到时显式抛错。

3) 路由下发的 host 可能带 /hcy 也可能只暴露 /orchestration,request()
   此前只有单一 URL。现按候选顺序尝试,仅 404 / "route not found" 才回退,
   其余错误原样抛出并附带请求 URL。

顺带:目录名比较改为 trim + NFC 归一化(中文名跨归一化形态不再误判为
不存在);分页游标沿用 Go 官方确认的 data.nextPageCursor,并防御游标
不前进导致的死循环。

新增 6 组回归测试(覆盖 issue #112 两个症状、init 硬失败、id 并集回退、
Unicode 归一化、候选 URL 重试语义),9/9 通过。
现象:部署后子目录(如 /139/手机备份)仍返回根目录内容,且不报任何错。

原因:某些路由 host 形态会命中"忽略 parentFileId、恒返回父级/根目录内容"的
端点,并且返回 HTTP 200 + success:true。此前只按 404 / "route not found"
回退候选 URL,这种"静默错内容"永远发现不了,也不会触发回退。

本次改动:
- 新增内容级自检 detectWrongListing():唯一判据是"一个节点不可能出现在它
  自己的子项里"。请求文件夹 F 的列表里出现 F 自己(/手机备份 的列表里出现
  手机备份、/手机软件 的列表里出现 手机软件)即为服务端忽略 parentFileId
  的铁证,此时将该候选视为失败;
- request() 支持 validate 回调:候选响应过不了自检就换下一个候选 URL,
  全部候选都不过才抛错,错误信息列出所有试过的 URL(会写入存储状态——
  EdgeOne 上看不到函数日志也能据此定位);
- 候选 URL 扩展为多个等价形态(host 直拼 / host+/orchestration /
  host+/hcy+/orchestration / 去掉 /hcy 的两种形态),顺序保持"现网已工作
  形态优先",健康账号仍只发一次请求;另按 boxplayer 语义把路由类业务码
  9000/9008/9100 也视为可回退。

回归测试:新增 3 组(自检命中后自动换候选并返回正确内容、全部候选都返回
错内容时报错并列出 URL、健康账号只产生一次请求),12/12 通过;tsc 无错误。
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.

[BUG] 移动云盘访问子目录出现根目录套娃情况

2 participants