* feat: 添加断点续传功能 - 实现自动保存上传进度到临时文件 - 支持上传中断后自动恢复并跳过已上传文件 - 为 Video 结构体添加 Clone trait - 添加 .envrc 用于 direnv 自动加载 Nix 环境 - 更新 flake.lock 依赖 * fix: Auto-truncate video titles exceeding 80 characters B站API限制分P视频标题不能超过80个字符。当文件名过长时,上传会在提交阶段失败并报错"第(N)个视频的标题过长,已经超过80个字符"。 此次修改在所有上传线路(Upos/Kodo/Cos)中添加自动截断逻辑: - 添加 Video::truncate_title() 辅助函数,按字符(非字节)截断 - 文件名超过80字符时,自动设置title字段为截断版本(77字符+"...") - 文件名不超过80字符时,title保持None,使用完整文件名 影响范围: - crates/biliup/src/uploader/bilibili.rs - 新增truncate_title方法 - crates/biliup/src/uploader/line/upos.rs - Upos线路应用截断 - crates/biliup/src/uploader/line/kodo.rs - Kodo线路应用截断 - crates/biliup/src/uploader/line/cos.rs - Cos线路应用截断 * feat: Auto-retry on upload rate limit (code 601) 当遇到B站限流错误(code: 601, "您上传视频过快")时,自动等待后重试,最多重试5次,等待时间递增。 - 在 pre_upload 中添加重试循环 - 解析错误响应JSON,检测 code: 601 - 递增等待时间:第1次等待1分钟,第2次2分钟,第3次3分钟,第4次4分钟 - 最多重试5次(从原来的3次增加) - 记录警告日志,显示重试进度和等待时间 - 配合断点续传功能,提高批量上传成功率 * refactor: 将重试逻辑从库层移至 CLI 层 主要变更: - 新增 RateLimit 错误类型以区分限流错误(code: 601) - 移除 line.rs 中 pre_upload() 的重试循环,遇到 601 错误直接返回 - 在 CLI 层 (uploader.rs) 实现重试逻辑 * fix: 使用跨平台路径获取断点续传文件位置 - 添加 dirs 依赖 - 使用 dirs::data_local_dir() 获取平台相关的数据目录 - Windows 下使用 %LOCALAPPDATA%,避免 /tmp 路径不存在导致的 panic - 如果无法获取数据目录,回退到 std::env::temp_dir() * refactor: 使用通用 retry 工具函数重构重试逻辑 主要变更: - 改造 lib.rs 中的 retry 函数,新增 retry_with_config 支持: - 自定义最大重试次数 - 条件重试(可指定只在特定错误时重试) - 保留原有的指数退避和抖动策略 - 在 CLI 层使用 retry_with_config 替换手写的重试循环 - 代码从 30+ 行简化为 15 行 - 更新 dirs 依赖从 5.0 到 6.0 * feat: 添加账号级上传互斥锁机制 为防止多个进程同时使用同一账号上传触发限流,实现账号级互斥锁: - 新增 upload_lock.rs 模块: - 基于文件锁实现跨进程互斥 - 使用账号 mid 作为锁标识符 - 自动清理超过30分钟的僵尸锁 - Drop 时自动释放锁 - 集成到上传流程(uploader.rs): - 在上传前检查是否有其他进程正在等待限流恢复 - 检测到限流错误时尝试获取锁 - 如果无法获取锁(其他进程已持有),本进程立即退出 - 使用 Arc<Mutex<>> 在闭包间共享锁状态 工作流程: 1. 进程1 上传时遇到限流 (Rate Limit) 2. 进程1 获取锁并进入重试等待 3. 进程2 启动并检测到锁已被占用 4. 进程2 立即退出,避免继续触发限流 5. 进程1 重试成功后释放锁
99 lines
2.4 KiB
JSON
99 lines
2.4 KiB
JSON
{
|
|
"nodes": {
|
|
"crane": {
|
|
"locked": {
|
|
"lastModified": 1764903584,
|
|
"narHash": "sha256-RSkJtNtx0SEaQiYqsoFoRynwfZLo2OZ9z6rUq1DJR6g=",
|
|
"owner": "ipetkov",
|
|
"repo": "crane",
|
|
"rev": "2b3a5a88d852575758e1eb6ac9ee677fcd633fc1",
|
|
"type": "github"
|
|
},
|
|
"original": {
|
|
"owner": "ipetkov",
|
|
"repo": "crane",
|
|
"type": "github"
|
|
}
|
|
},
|
|
"flake-utils": {
|
|
"inputs": {
|
|
"systems": "systems"
|
|
},
|
|
"locked": {
|
|
"lastModified": 1731533236,
|
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
"owner": "numtide",
|
|
"repo": "flake-utils",
|
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
"type": "github"
|
|
},
|
|
"original": {
|
|
"owner": "numtide",
|
|
"repo": "flake-utils",
|
|
"type": "github"
|
|
}
|
|
},
|
|
"nixpkgs": {
|
|
"locked": {
|
|
"lastModified": 1764667669,
|
|
"narHash": "sha256-7WUCZfmqLAssbDqwg9cUDAXrSoXN79eEEq17qhTNM/Y=",
|
|
"owner": "NixOS",
|
|
"repo": "nixpkgs",
|
|
"rev": "418468ac9527e799809c900eda37cbff999199b6",
|
|
"type": "github"
|
|
},
|
|
"original": {
|
|
"owner": "NixOS",
|
|
"ref": "nixos-unstable",
|
|
"repo": "nixpkgs",
|
|
"type": "github"
|
|
}
|
|
},
|
|
"root": {
|
|
"inputs": {
|
|
"crane": "crane",
|
|
"flake-utils": "flake-utils",
|
|
"nixpkgs": "nixpkgs",
|
|
"rust-overlay": "rust-overlay"
|
|
}
|
|
},
|
|
"rust-overlay": {
|
|
"inputs": {
|
|
"nixpkgs": [
|
|
"nixpkgs"
|
|
]
|
|
},
|
|
"locked": {
|
|
"lastModified": 1764902447,
|
|
"narHash": "sha256-wNqkDBj+tjK619sTHPEA7uhjr7DHHEY8OsFou31dxy0=",
|
|
"owner": "oxalica",
|
|
"repo": "rust-overlay",
|
|
"rev": "d914a744a83098eeb28125d2848ad383b209223f",
|
|
"type": "github"
|
|
},
|
|
"original": {
|
|
"owner": "oxalica",
|
|
"repo": "rust-overlay",
|
|
"type": "github"
|
|
}
|
|
},
|
|
"systems": {
|
|
"locked": {
|
|
"lastModified": 1681028828,
|
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
"owner": "nix-systems",
|
|
"repo": "default",
|
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
"type": "github"
|
|
},
|
|
"original": {
|
|
"owner": "nix-systems",
|
|
"repo": "default",
|
|
"type": "github"
|
|
}
|
|
}
|
|
},
|
|
"root": "root",
|
|
"version": 7
|
|
}
|