Redesign conflict UX: in-place copies like Dropbox/iCloud (4.16)
Instead of moving conflict files to a separate conflict/ directory,
keep them in the original directory with naming convention:
{name} (Warpgate Conflict {YYYY-MM-DD HH-mm}).{ext}
Benefits:
- Lightroom/Finder see both versions side by side
- Preserved extension ensures app compatibility
- Matches Dropbox/iCloud behavior users already know
- Conflict copies auto-sync to NAS via rclone (backed up)
Remote-deleted + local-dirty: file stays in place (no rename),
marked as orphan-conflict, user decides whether to re-upload.
Updated: decision matrix diagrams, scenario walkthroughs,
cache_files lifecycle, CLI commands, config section, directory
structure description.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
823d20606a
commit
d40997312b
@ -258,7 +258,8 @@ flowchart LR
|
|||||||
- `warpgate cache-clean` — 清理缓存
|
- `warpgate cache-clean` — 清理缓存
|
||||||
- `warpgate warmup` — 手动预热
|
- `warpgate warmup` — 手动预热
|
||||||
- `warpgate bwlimit` — 动态调整带宽限制
|
- `warpgate bwlimit` — 动态调整带宽限制
|
||||||
- `warpgate conflicts` — 查看和处理冲突文件
|
- `warpgate conflicts` — 列出所有冲突文件(跨目录聚合),显示原文件、冲突副本、时间
|
||||||
|
- `warpgate conflicts resolve <path> --keep=local|remote|both` — 解决冲突
|
||||||
- `warpgate ingest` — 手动触发 SD 卡导入
|
- `warpgate ingest` — 手动触发 SD 卡导入
|
||||||
- `warpgate verify` — 双卡校验
|
- `warpgate verify` — 双卡校验
|
||||||
- `warpgate log` — 查看实时日志
|
- `warpgate log` — 查看实时日志
|
||||||
@ -284,10 +285,33 @@ flowchart LR
|
|||||||
- 写回队列在恢复连接后自动续传
|
- 写回队列在恢复连接后自动续传
|
||||||
- 连接超时参数可配置
|
- 连接超时参数可配置
|
||||||
|
|
||||||
#### 4.16 写冲突通知
|
#### 4.16 写冲突处理
|
||||||
- 冲突发生时通知用户(CLI 提示 / 日志 / 可选 Webhook)
|
|
||||||
- 冲突文件清单管理
|
**冲突副本命名**:`{name} (Warpgate Conflict {YYYY-MM-DD HH-mm}).{ext}`
|
||||||
- 手动解决冲突工具
|
|
||||||
|
冲突副本保留在**原目录**(而非移到独立 conflict/ 目录),让 Lightroom/Finder 等应用可以直接看到并打开。保留原始扩展名,确保应用兼容性。这与 Dropbox/iCloud 的冲突处理方式一致,用户已有认知习惯。
|
||||||
|
|
||||||
|
**各冲突场景的处理**:
|
||||||
|
|
||||||
|
| 场景 | 主文件 | 冲突副本 | 说明 |
|
||||||
|
|------|--------|----------|------|
|
||||||
|
| 远程更新胜出(remote > local) | 拉取远程版本作为 `IMG_001.cr3` | 本地版本重命名为 `IMG_001 (Warpgate Conflict ...).cr3` | 两个版本都在原目录,用户可直接对比 |
|
||||||
|
| 本地更新胜出(local > remote) | 保持本地版本并回写 | 不产生冲突副本 | 正常回写 |
|
||||||
|
| 远程已删 + 本地有脏数据 | 本地文件**原地保留不改名** | 无 | metadata 中标记为 orphan-conflict;通知用户"NAS 上已删除,本地编辑是唯一副本,请决定是否重新上传" |
|
||||||
|
|
||||||
|
**冲突副本的生命周期**:
|
||||||
|
|
||||||
|
- 冲突副本在 FUSE 挂载点内,rclone 自动视为新脏文件 → Write-back Controller 将其同步到 NAS(冲突副本也有远程备份,与 Dropbox 行为一致)
|
||||||
|
- 用户在 Lightroom/Finder 中直接看到两个版本,自行决定保留哪个
|
||||||
|
- 删除不需要的版本即可,rclone 自动同步删除到 NAS
|
||||||
|
|
||||||
|
**管理工具**:
|
||||||
|
|
||||||
|
- `warpgate conflicts` — 列出所有冲突文件(跨目录聚合),显示:原文件、冲突副本路径、冲突时间、哪个版本胜出
|
||||||
|
- `warpgate conflicts resolve <path> --keep=local|remote|both` — 批量解决冲突:保留本地/远程/两者
|
||||||
|
- 超过 `CONFLICT_RETAIN_DAYS` 天的冲突副本,由定时清理任务自动删除(清理前记录日志)
|
||||||
|
|
||||||
|
**通知**:冲突发生时通知用户(CLI 提示 / 日志 / 可选 Webhook),包含冲突文件路径和建议操作
|
||||||
|
|
||||||
### P2(后续迭代)
|
### P2(后续迭代)
|
||||||
|
|
||||||
@ -430,7 +454,7 @@ flowchart TD
|
|||||||
CmpMtime -->|"remote > cache<br/>远程更新"| Conflict1["远程胜<br/>拉远程新版本<br/>本地存冲突副本"]
|
CmpMtime -->|"remote > cache<br/>远程更新"| Conflict1["远程胜<br/>拉远程新版本<br/>本地存冲突副本"]
|
||||||
CmpMtime -->|"mtime 相等<br/>内容可能不同"| LocalWins["本地胜<br/>(写入者优先)"]
|
CmpMtime -->|"mtime 相等<br/>内容可能不同"| LocalWins["本地胜<br/>(写入者优先)"]
|
||||||
|
|
||||||
Deleted -->|"⚠️ 不回写"| Respect["尊重远程删除<br/>脏文件移到 conflict/<br/>通知用户"]
|
Deleted -->|"⚠️ 不回写"| Respect["尊重远程删除<br/>本地文件原地保留<br/>标记 orphan-conflict<br/>通知用户"]
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5.4b SD 卡导入文件的回写决策(补充)
|
### 5.4b SD 卡导入文件的回写决策(补充)
|
||||||
@ -483,7 +507,7 @@ flowchart TD
|
|||||||
WB --> Stat["SFTP stat 查远程:<br/>remote_mtime = Day3"]
|
WB --> Stat["SFTP stat 查远程:<br/>remote_mtime = Day3"]
|
||||||
Stat --> Conflict["remote(Day3) != origin(Day0)<br/>→ 远程被改过!"]
|
Stat --> Conflict["remote(Day3) != origin(Day0)<br/>→ 远程被改过!"]
|
||||||
Conflict --> Compare["cache(Day1) vs remote(Day3)<br/>Day3 > Day1 → 远程胜"]
|
Conflict --> Compare["cache(Day1) vs remote(Day3)<br/>Day3 > Day1 → 远程胜"]
|
||||||
Compare --> Actions["本地脏版本 → conflict/<br/>拉远程新版本覆盖缓存<br/>更新 origin=Day3, clean<br/>通知用户"]
|
Compare --> Actions["本地版本重命名为<br/>(Warpgate Conflict ...)<br/>拉远程新版本<br/>更新 origin=Day3, clean<br/>通知用户"]
|
||||||
Actions --> Result["✅ 不丢数据,两版本都保留"]
|
Actions --> Result["✅ 不丢数据,两版本都保留"]
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -496,8 +520,8 @@ flowchart TD
|
|||||||
D3 --> D5["Day 5: Cache 开机,准备回写"]
|
D3 --> D5["Day 5: Cache 开机,准备回写"]
|
||||||
D5 --> Stat["Write-back Controller 查远程:<br/>photo.cr3 不存在"]
|
D5 --> Stat["Write-back Controller 查远程:<br/>photo.cr3 不存在"]
|
||||||
Stat --> Conflict["远程已删 + 本地有脏数据 → 冲突"]
|
Stat --> Conflict["远程已删 + 本地有脏数据 → 冲突"]
|
||||||
Conflict --> Actions["❌ 不回写(尊重远程删除)<br/>脏版本 → conflict/<br/>通知用户"]
|
Conflict --> Actions["❌ 不回写(尊重远程删除)<br/>本地文件原地保留<br/>标记 orphan-conflict<br/>通知用户"]
|
||||||
Actions --> Result["✅ 不会复活已删文件<br/>用户可从 conflict/ 恢复"]
|
Actions --> Result["✅ 不会复活已删文件<br/>用户可决定是否重新上传"]
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 场景 4:NAS 删了文件,Cache 上是 clean 的
|
#### 场景 4:NAS 删了文件,Cache 上是 clean 的
|
||||||
@ -581,8 +605,8 @@ flowchart TD
|
|||||||
│ │ └── IMG_0002.cr3 # 或通过 FUSE 挂载点写入的 SD 卡导入文件
|
│ │ └── IMG_0002.cr3 # 或通过 FUSE 挂载点写入的 SD 卡导入文件
|
||||||
│ └── ...
|
│ └── ...
|
||||||
├── metadata.db # SQLite 元数据库(WAL 模式,详见 5.7.7)
|
├── metadata.db # SQLite 元数据库(WAL 模式,详见 5.7.7)
|
||||||
├── conflict/ # 冲突文件暂存目录
|
├── conflict/ # 冲突副本自动清理暂存(CONFLICT_RETAIN_DAYS 到期后移入)
|
||||||
│ └── IMG_0001.cr3.local-20260216-143022
|
│ └── (仅用于到期自动清理前的归档,正常冲突副本在原目录)
|
||||||
├── ingest_staging/ # SD 卡导入暂存目录(导入状态机使用,详见 5.9)
|
├── ingest_staging/ # SD 卡导入暂存目录(导入状态机使用,详见 5.9)
|
||||||
│ └── <session-id>/ # 每次导入会话独立目录
|
│ └── <session-id>/ # 每次导入会话独立目录
|
||||||
└── timemachine/ # Time Machine 备份目录(独立于 rclone VFS,详见 4.10)
|
└── timemachine/ # Time Machine 备份目录(独立于 rclone VFS,详见 4.10)
|
||||||
@ -629,8 +653,11 @@ SD 卡导入新文件 → INSERT (state=dirty, source=ingest, origin_mtime
|
|||||||
回写失败 → UPDATE (writeback_retry += 1)
|
回写失败 → UPDATE (writeback_retry += 1)
|
||||||
缓存被 LRU 淘汰 → DELETE(仅 state=clean 可被淘汰)
|
缓存被 LRU 淘汰 → DELETE(仅 state=clean 可被淘汰)
|
||||||
检测到远程删除(clean)→ DELETE
|
检测到远程删除(clean)→ DELETE
|
||||||
检测到冲突 → UPDATE (state=conflict)
|
检测到冲突(远程胜) → 原文件 UPDATE (拉远程版本, state=clean)
|
||||||
冲突处理完成 → DELETE 或 UPDATE (state=clean)
|
冲突副本 INSERT (重命名后的本地版本, state=dirty, source=conflict)
|
||||||
|
冲突副本通过 rclone 自动回写到 NAS
|
||||||
|
检测到冲突(远程已删)→ UPDATE (state=conflict, 标记 orphan-conflict, 原地保留不改名)
|
||||||
|
冲突处理完成 → DELETE(用户删除不需要的版本)或 UPDATE (state=clean)
|
||||||
```
|
```
|
||||||
|
|
||||||
**表 2:dir_snapshots — 目录级轮询快照**
|
**表 2:dir_snapshots — 目录级轮询快照**
|
||||||
@ -1042,7 +1069,7 @@ flowchart TD
|
|||||||
|
|
||||||
Decision -->|"正常回写"| Upload["上传 + 更新 origin_mtime<br/>state → clean"]
|
Decision -->|"正常回写"| Upload["上传 + 更新 origin_mtime<br/>state → clean"]
|
||||||
Decision -->|"冲突"| ConflictKeep["保留双版本 + 通知用户"]
|
Decision -->|"冲突"| ConflictKeep["保留双版本 + 通知用户"]
|
||||||
Decision -->|"远程已删"| NoWrite["不回写 + 移到 conflict/"]
|
Decision -->|"远程已删"| NoWrite["不回写<br/>原地保留,标记 orphan-conflict"]
|
||||||
|
|
||||||
Upload -->|"上传失败"| Retry["重试(指数退避)<br/>10s, 20s, 40s... 最多 10 次"]
|
Upload -->|"上传失败"| Retry["重试(指数退避)<br/>10s, 20s, 40s... 最多 10 次"]
|
||||||
Retry -->|"最终失败"| Keep["保留本地,state=dirty<br/>记录日志"]
|
Retry -->|"最终失败"| Keep["保留本地,state=dirty<br/>记录日志"]
|
||||||
@ -1141,13 +1168,14 @@ flowchart TD
|
|||||||
|
|
||||||
| 参数 | 说明 | 默认值 | 建议值 |
|
| 参数 | 说明 | 默认值 | 建议值 |
|
||||||
|------|------|--------|--------|
|
|------|------|--------|--------|
|
||||||
| `CONFLICT_DIR` | 冲突文件存放目录 | `{CACHE_DIR}/conflict` | - |
|
|
||||||
| `CONFLICT_STRATEGY` | 冲突策略 | `mtime_wins` | `mtime_wins` |
|
| `CONFLICT_STRATEGY` | 冲突策略 | `mtime_wins` | `mtime_wins` |
|
||||||
| `CONFLICT_NOTIFY` | 冲突通知方式 | `log` | `log` / `webhook` |
|
| `CONFLICT_NOTIFY` | 冲突通知方式 | `log` | `log` / `webhook` |
|
||||||
| `CONFLICT_RETAIN_DAYS` | 冲突副本保留天数 | `30` | - |
|
| `CONFLICT_RETAIN_DAYS` | 冲突副本保留天数 | `30` | - |
|
||||||
| `CONFLICT_CLEANUP_SCHEDULE` | 冲突目录自动清理时间 | `04:00` | 与 FULL_SYNC_SCHEDULE 错开 |
|
| `CONFLICT_CLEANUP_SCHEDULE` | 冲突副本自动清理时间 | `04:00` | 与 FULL_SYNC_SCHEDULE 错开 |
|
||||||
|
|
||||||
**冲突目录清理进程**:每天在 `CONFLICT_CLEANUP_SCHEDULE` 时间自动扫描 `CONFLICT_DIR`,删除超过 `CONFLICT_RETAIN_DAYS` 天的冲突副本。清理前记录日志。
|
**冲突副本命名**:`{name} (Warpgate Conflict {YYYY-MM-DD HH-mm}).{ext}`,保留在原目录中。
|
||||||
|
|
||||||
|
**清理进程**:每天在 `CONFLICT_CLEANUP_SCHEDULE` 扫描所有匹配 `(Warpgate Conflict ...)` 命名模式的文件,超过 `CONFLICT_RETAIN_DAYS` 天的自动删除(本地 + 已同步到 NAS 的副本一并删除)。清理前记录日志。
|
||||||
|
|
||||||
### 多协议配置
|
### 多协议配置
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user