mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-27 16:26:16 +08:00
feat: add dual-layer versioning to WebDAV sync (protocol v2 + db-v6)
Separate protocol version from database compatibility version in WebDAV sync paths. Upload writes to v2/db-v6/<profile>, download falls back to legacy v2/<profile> when current path has no data. Extend manifest with optional dbCompatVersion field and add legacy layout detection to UI.
This commit is contained in:
@@ -92,6 +92,10 @@ function formatDate(rfc3339: string): string {
|
||||
return Number.isNaN(d.getTime()) ? rfc3339 : d.toLocaleString();
|
||||
}
|
||||
|
||||
function formatDbCompatVersion(version?: number | null): string | null {
|
||||
return typeof version === "number" ? `db-v${version}` : null;
|
||||
}
|
||||
|
||||
// ─── Types ──────────────────────────────────────────────────
|
||||
|
||||
type ActionState =
|
||||
@@ -395,7 +399,10 @@ export function WebdavSyncSection({ config }: WebdavSyncSectionProps) {
|
||||
if (!info.compatible) {
|
||||
toast.error(
|
||||
t("settings.webdavSync.incompatibleVersion", {
|
||||
version: info.version,
|
||||
protocolVersion: info.protocolVersion,
|
||||
dbCompatVersion:
|
||||
formatDbCompatVersion(info.dbCompatVersion) ??
|
||||
t("common.unknown"),
|
||||
}),
|
||||
);
|
||||
return;
|
||||
@@ -450,6 +457,9 @@ export function WebdavSyncSection({ config }: WebdavSyncSectionProps) {
|
||||
const lastError = config?.status?.lastError?.trim();
|
||||
const showAutoSyncError =
|
||||
!!lastError && config?.status?.lastErrorSource === "auto";
|
||||
const currentRemotePath = `/${form.remoteRoot.trim() || "cc-switch-sync"}/v2/db-v6/${form.profile.trim() || "default"}`;
|
||||
const remoteDbCompatDisplay = formatDbCompatVersion(remoteInfo?.dbCompatVersion);
|
||||
const remoteIsLegacy = remoteInfo?.layout === "legacy";
|
||||
|
||||
// ─── Render ─────────────────────────────────────────────
|
||||
|
||||
@@ -720,8 +730,7 @@ export function WebdavSyncSection({ config }: WebdavSyncSectionProps) {
|
||||
{t("settings.webdavSync.confirmUpload.targetPath")}
|
||||
{": "}
|
||||
<code className="ml-1 text-xs bg-muted px-1.5 py-0.5 rounded">
|
||||
/{form.remoteRoot.trim() || "cc-switch-sync"}/v2/
|
||||
{form.profile.trim() || "default"}
|
||||
{currentRemotePath}
|
||||
</code>
|
||||
</p>
|
||||
{remoteInfo && (
|
||||
@@ -742,14 +751,35 @@ export function WebdavSyncSection({ config }: WebdavSyncSectionProps) {
|
||||
{t("settings.webdavSync.confirmUpload.createdAt")}
|
||||
</dt>
|
||||
<dd>{formatDate(remoteInfo.createdAt)}</dd>
|
||||
<dt className="font-medium text-foreground">
|
||||
{t("settings.webdavSync.confirmUpload.path")}
|
||||
</dt>
|
||||
<dd>
|
||||
<code className="bg-muted px-1.5 py-0.5 rounded">
|
||||
{remoteInfo.remotePath}
|
||||
</code>
|
||||
</dd>
|
||||
{remoteDbCompatDisplay && (
|
||||
<>
|
||||
<dt className="font-medium text-foreground">
|
||||
{t("settings.webdavSync.confirmUpload.dbCompat")}
|
||||
</dt>
|
||||
<dd>{remoteDbCompatDisplay}</dd>
|
||||
</>
|
||||
)}
|
||||
</dl>
|
||||
</div>
|
||||
)}
|
||||
{remoteInfo && (
|
||||
{remoteInfo && !remoteIsLegacy && (
|
||||
<p className="text-destructive font-medium">
|
||||
{t("settings.webdavSync.confirmUpload.warning")}
|
||||
</p>
|
||||
)}
|
||||
{remoteInfo && remoteIsLegacy && (
|
||||
<p className="font-medium text-amber-600 dark:text-amber-400">
|
||||
{t("settings.webdavSync.confirmUpload.legacyNotice")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -793,12 +823,33 @@ export function WebdavSyncSection({ config }: WebdavSyncSectionProps) {
|
||||
{t("settings.webdavSync.confirmDownload.createdAt")}
|
||||
</dt>
|
||||
<dd>{formatDate(remoteInfo.createdAt)}</dd>
|
||||
<dt className="font-medium text-foreground">
|
||||
{t("settings.webdavSync.confirmDownload.path")}
|
||||
</dt>
|
||||
<dd>
|
||||
<code className="text-xs bg-muted px-1.5 py-0.5 rounded">
|
||||
{remoteInfo.remotePath}
|
||||
</code>
|
||||
</dd>
|
||||
{remoteDbCompatDisplay && (
|
||||
<>
|
||||
<dt className="font-medium text-foreground">
|
||||
{t("settings.webdavSync.confirmDownload.dbCompat")}
|
||||
</dt>
|
||||
<dd>{remoteDbCompatDisplay}</dd>
|
||||
</>
|
||||
)}
|
||||
<dt className="font-medium text-foreground">
|
||||
{t("settings.webdavSync.confirmDownload.artifacts")}
|
||||
</dt>
|
||||
<dd>{remoteInfo.artifacts.join(", ")}</dd>
|
||||
</dl>
|
||||
)}
|
||||
{remoteInfo?.layout === "legacy" && (
|
||||
<p className="font-medium text-amber-600 dark:text-amber-400">
|
||||
{t("settings.webdavSync.confirmDownload.legacyNotice")}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-destructive font-medium">
|
||||
{t("settings.webdavSync.confirmDownload.warning")}
|
||||
</p>
|
||||
|
||||
@@ -397,7 +397,7 @@
|
||||
"saveAndTestSuccess": "Config saved, connection OK",
|
||||
"saveAndTestFailed": "Config saved, but connection test failed: {{error}}",
|
||||
"noRemoteData": "No sync data found on the remote server",
|
||||
"incompatibleVersion": "Remote data version incompatible (v{{version}}), current supports v2",
|
||||
"incompatibleVersion": "Remote data is incompatible (protocol v{{protocolVersion}}, database {{dbCompatVersion}}). This client supports protocol v2 / db-v6.",
|
||||
"unsaved": "Unsaved",
|
||||
"saved": "Saved",
|
||||
"unsavedChanges": "Please save config first",
|
||||
@@ -408,7 +408,10 @@
|
||||
"title": "Restore from Cloud",
|
||||
"deviceName": "Uploaded by",
|
||||
"createdAt": "Uploaded at",
|
||||
"path": "Remote path",
|
||||
"dbCompat": "DB compatibility",
|
||||
"artifacts": "Contents",
|
||||
"legacyNotice": "A legacy remote path was detected. After restoring, the next upload will write to the new v2/db-v6 path.",
|
||||
"warning": "This will overwrite all local data and skill configurations",
|
||||
"confirm": "Confirm Restore"
|
||||
},
|
||||
@@ -421,7 +424,10 @@
|
||||
"existingData": "Existing cloud data",
|
||||
"deviceName": "Uploaded by",
|
||||
"createdAt": "Uploaded at",
|
||||
"path": "Remote path",
|
||||
"dbCompat": "DB compatibility",
|
||||
"warning": "This will overwrite existing sync data on the remote server",
|
||||
"legacyNotice": "Legacy remote data was detected. This upload will write to the new v2/db-v6 path and will not overwrite the legacy path.",
|
||||
"confirm": "Confirm Upload"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -397,7 +397,7 @@
|
||||
"saveAndTestSuccess": "設定を保存しました。接続正常です",
|
||||
"saveAndTestFailed": "設定を保存しましたが、接続テストに失敗しました:{{error}}",
|
||||
"noRemoteData": "クラウドに同期データが見つかりません",
|
||||
"incompatibleVersion": "リモートデータのバージョンに互換性がありません(v{{version}})。現在 v2 をサポートしています",
|
||||
"incompatibleVersion": "リモートデータに互換性がありません(プロトコル v{{protocolVersion}}、データベース {{dbCompatVersion}})。このクライアントは protocol v2 / db-v6 をサポートしています。",
|
||||
"unsaved": "未保存",
|
||||
"saved": "保存済み",
|
||||
"unsavedChanges": "先に設定を保存してください",
|
||||
@@ -408,7 +408,10 @@
|
||||
"title": "クラウドから復元",
|
||||
"deviceName": "アップロード元",
|
||||
"createdAt": "アップロード日時",
|
||||
"path": "リモートパス",
|
||||
"dbCompat": "DB 互換レイヤー",
|
||||
"artifacts": "内容",
|
||||
"legacyNotice": "旧レイアウトのリモートパスを検出しました。復元後、次回のアップロードは新しい v2/db-v6 パスに書き込まれます。",
|
||||
"warning": "ローカルのすべてのデータとスキル設定が上書きされます",
|
||||
"confirm": "復元を実行"
|
||||
},
|
||||
@@ -421,7 +424,10 @@
|
||||
"existingData": "クラウドの既存データ",
|
||||
"deviceName": "アップロード元",
|
||||
"createdAt": "アップロード日時",
|
||||
"path": "リモートパス",
|
||||
"dbCompat": "DB 互換レイヤー",
|
||||
"warning": "リモートの既存同期データが上書きされます",
|
||||
"legacyNotice": "旧レイアウトのリモートデータを検出しました。今回のアップロードは新しい v2/db-v6 パスに書き込み、旧パスは上書きしません。",
|
||||
"confirm": "アップロードを実行"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -397,7 +397,7 @@
|
||||
"saveAndTestSuccess": "配置已保存,连接正常",
|
||||
"saveAndTestFailed": "配置已保存,但连接测试失败:{{error}}",
|
||||
"noRemoteData": "云端没有找到同步数据",
|
||||
"incompatibleVersion": "远端数据版本不兼容(v{{version}}),当前支持 v2",
|
||||
"incompatibleVersion": "远端数据版本不兼容(协议 v{{protocolVersion}},数据库 {{dbCompatVersion}}),当前支持协议 v2 / db-v6",
|
||||
"unsaved": "未保存",
|
||||
"saved": "已保存",
|
||||
"unsavedChanges": "请先保存配置",
|
||||
@@ -408,7 +408,10 @@
|
||||
"title": "从云端恢复",
|
||||
"deviceName": "上传设备",
|
||||
"createdAt": "上传时间",
|
||||
"path": "远端路径",
|
||||
"dbCompat": "数据库兼容层",
|
||||
"artifacts": "包含内容",
|
||||
"legacyNotice": "检测到旧版云端路径。恢复完成后,下次上传将写入新路径 v2/db-v6。",
|
||||
"warning": "恢复将覆盖本地所有数据和技能配置",
|
||||
"confirm": "确认恢复"
|
||||
},
|
||||
@@ -421,7 +424,10 @@
|
||||
"existingData": "云端已有数据",
|
||||
"deviceName": "上传设备",
|
||||
"createdAt": "上传时间",
|
||||
"path": "远端路径",
|
||||
"dbCompat": "数据库兼容层",
|
||||
"warning": "将覆盖云端已有的同步数据",
|
||||
"legacyNotice": "检测到旧版云端路径数据。本次上传将写入新路径 v2/db-v6,不会覆盖旧路径。",
|
||||
"confirm": "确认上传"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -102,7 +102,7 @@ export const settingsApi = {
|
||||
return await invoke("import_config_from_file", { filePath });
|
||||
},
|
||||
|
||||
// ─── WebDAV v2 sync ───────────────────────────────────────
|
||||
// ─── WebDAV sync ──────────────────────────────────────────
|
||||
|
||||
async webdavTestConnection(
|
||||
settings: WebDavSyncSettings,
|
||||
|
||||
+8
-2
@@ -169,7 +169,7 @@ export interface VisibleApps {
|
||||
openclaw: boolean;
|
||||
}
|
||||
|
||||
// WebDAV v2 同步状态
|
||||
// WebDAV 同步状态
|
||||
export interface WebDavSyncStatus {
|
||||
lastSyncAt?: number | null;
|
||||
lastError?: string | null;
|
||||
@@ -179,7 +179,7 @@ export interface WebDavSyncStatus {
|
||||
lastRemoteManifestHash?: string | null;
|
||||
}
|
||||
|
||||
// WebDAV v2 同步配置
|
||||
// WebDAV 同步配置
|
||||
export interface WebDavSyncSettings {
|
||||
enabled?: boolean;
|
||||
autoSync?: boolean;
|
||||
@@ -191,14 +191,20 @@ export interface WebDavSyncSettings {
|
||||
status?: WebDavSyncStatus;
|
||||
}
|
||||
|
||||
export type RemoteSnapshotLayout = "current" | "legacy";
|
||||
|
||||
// 远端快照信息(下载前预览)
|
||||
export interface RemoteSnapshotInfo {
|
||||
deviceName: string;
|
||||
createdAt: string;
|
||||
snapshotId: string;
|
||||
version: number;
|
||||
protocolVersion: number;
|
||||
dbCompatVersion?: number | null;
|
||||
compatible: boolean;
|
||||
artifacts: string[];
|
||||
layout: RemoteSnapshotLayout;
|
||||
remotePath: string;
|
||||
}
|
||||
|
||||
// 应用设置类型(用于设置对话框与 Tauri API)
|
||||
|
||||
Reference in New Issue
Block a user