feat(mobile): upload engine with preparation, batching states and retries

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-19 02:50:40 +08:00
parent d96e56f653
commit 3ddb0ea2d4
23 changed files with 3232 additions and 12 deletions
@@ -292,6 +292,10 @@ class TaskStore {
/// 状态迁移(带合法迁移守护 + updatedAt 刷新),返回迁移后的任务。
///
/// patch 语义(与 [UploadTaskPatch] 文档一致):**未提供的列保持不变**,
/// 显式清除走 clear* 布尔;serverUploadId/serverPhotoId 仅在提供时
/// 写入(write-once 回填,无清除通道)。
///
/// 抛 [StateError]:任务不存在,或 `当前 → to` 不在合法迁移表
/// ([assertTransition])。
Future<UploadTask> updateState(
@@ -313,12 +317,54 @@ class TaskStore {
: Value(patch.attempt!),
nextRetryAt: patch.clearNextRetryAt
? const Value(null)
: Value(patch.nextRetryAt?.toUtc()),
serverUploadId: Value(patch.serverUploadId),
serverPhotoId: Value(patch.serverPhotoId),
: patch.nextRetryAt == null
? const Value.absent()
: Value(patch.nextRetryAt!.toUtc()),
serverUploadId: patch.serverUploadId == null
? const Value.absent()
: Value(patch.serverUploadId),
serverPhotoId: patch.serverPhotoId == null
? const Value.absent()
: Value(patch.serverPhotoId),
lastError: patch.clearLastError
? const Value(null)
: Value(patch.lastError),
: patch.lastError == null
? const Value.absent()
: Value(patch.lastError),
updatedAt: Value(_utcAt(now)),
),
);
return await _byId(id);
});
}
/// 旁路字段回填(**不迁移状态**):引擎在 initUpload 成功后写
/// serverUploadId/serverPhotoId——此时状态已为 uploading,无合法自迁移,
/// 需绕过状态机直接回填(仅刷新 updatedAt)。bytes 供准备器重试换源后
/// 对齐声明值(副本 stat 与行内声明可能有历史偏差)。
///
/// 提供的字段写入,未提供的保持不变(与 [updateState] 的 patch 语义
/// 一致)。
Future<UploadTask> updateMeta(
String id, {
String? serverUploadId,
String? serverPhotoId,
int? bytes,
DateTime? now,
}) {
return _db.transaction(() async {
await _byId(id);
await (_db.update(_db.uploadTasks)
..where((t) => t.id.equals(id)))
.write(
UploadTasksCompanion(
serverUploadId: serverUploadId == null
? const Value.absent()
: Value(serverUploadId),
serverPhotoId: serverPhotoId == null
? const Value.absent()
: Value(serverPhotoId),
bytes: bytes == null ? const Value.absent() : Value(bytes),
updatedAt: Value(_utcAt(now)),
),
);