feat(app): wire server dedup - force passthrough + PHOTO_DUPLICATE classification & l10n
Task 8: close the gap between server-side duplicate detection (90dcc58)
and the app.
- ApiClient.mapDioException: normalize 409 wire body reusing
VALIDATION_FAILED with message=PHOTO_DUPLICATE to
ErrorCodes.photoDuplicate (message-based; auto-compat if the contract
later adds a dedicated code)
- l10n zh/en errorPhotoDuplicate; error_l10n maps the code (queue page
inherits via fallback)
- UploadEngine._classifyFailure: PHOTO_DUPLICATE joins the no-auto-retry
bucket (retrying the same init body would 409 again); force to skip
dedup travels only through the enqueue path
- UploadTasks drift table +force column (schema v2, addColumn migration;
legacy rows default false); UploadTaskDraft/UploadTask/prepare carry it
- enqueueForUpload +forcePaths; duplicate dialog "全部上传" (capture page
+ album detail page) now returns DuplicateDialogChoice so confirmed
duplicates enqueue with force=true (server adjudicates owner/admin)
- engine init body carries force only when true (absent otherwise)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
/// | 网络不可达/超时/429 限速 | waitingNetwork | 退避 5s·2^n 封顶 10min,无次数上限 |
|
||||
/// | 401 会话过期 | waitingNetwork | 30s 短退避;全局驱逐由 ApiClient 处理,不烧次数 |
|
||||
/// | 400 UPLOAD_VERIFY_FAILED / 5xx / 未知 | retryableFailed | 退避重试,3 次后停止自动重试 |
|
||||
/// | 409 ALBUM_UPLOAD_DISABLED / UPLOAD_SESSION_CONFLICT / 源文件缺失 | retryableFailed | 不自动重试(人工修正后 retryNow) |
|
||||
/// | 409 ALBUM_UPLOAD_DISABLED / UPLOAD_SESSION_CONFLICT / PHOTO_DUPLICATE / 源文件缺失 | retryableFailed | 不自动重试(人工修正后 retryNow) |
|
||||
/// | presign 403 凭证过期 | (renew 后原状重传) | renewUpload 至多 2 次 |
|
||||
/// - **取消**([cancel]):未开始 → cancelled 后删行删文件(确定语义);
|
||||
/// 进行中 → cancelToken 尽力取消 + 置 cancelled(文件保留,由清理
|
||||
@@ -335,6 +335,9 @@ class UploadEngine {
|
||||
|
||||
Future<void> _transferSteps(UploadTask task, _TransferHandle handle) async {
|
||||
// a. initUpload(幂等:重试复用 clientTaskId,服务端返回原会话)。
|
||||
// force(Task 8):查重弹窗「全部上传」入队时落库的用户确认——重试
|
||||
// 与 retryNow 均沿用任务行标记(用户已确认过的强传不因重试退化为
|
||||
// 再被 409 拒绝)。
|
||||
final init = await _apiClient.initUpload(
|
||||
UploadInitRequest(
|
||||
albumId: task.albumId,
|
||||
@@ -344,6 +347,7 @@ class UploadEngine {
|
||||
bytes: task.bytes,
|
||||
contentType: task.contentType,
|
||||
sourceDeviceId: await _deviceId.value(),
|
||||
force: task.force,
|
||||
),
|
||||
);
|
||||
// 回填会话/照片 ID(状态仍 uploading,旁路写入,见 TaskStore.updateMeta)。
|
||||
@@ -486,10 +490,14 @@ class UploadEngine {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3) 客户端重试无意义(服务端开关/幂等冲突/源文件缺失):不自动
|
||||
// 重试,用户可见 lastError,修正后 retryNow 手动重试。
|
||||
// 3) 客户端重试无意义(服务端开关/幂等冲突/查重拒绝/源文件缺失):
|
||||
// 不自动重试,用户可见 lastError,修正后 retryNow 手动重试。
|
||||
// PHOTO_DUPLICATE(P0-1 Task 8):重发同体 init 必再 409——用户想
|
||||
// 强传需重走选图弹窗「全部上传」(force 仅 retryNow 外的入队路径
|
||||
// 携带;retryNow 保持任务原 force 标记)。
|
||||
if (code == ErrorCodes.albumUploadDisabled ||
|
||||
code == ErrorCodes.uploadSessionConflict ||
|
||||
code == ErrorCodes.photoDuplicate ||
|
||||
code == UploadEngine.sourceMissingCode) {
|
||||
await _fail(
|
||||
task,
|
||||
|
||||
Reference in New Issue
Block a user