diff --git a/src-tauri/src/core/cloud_sync/manager.rs b/src-tauri/src/core/cloud_sync/manager.rs index f149ee51..8df23546 100644 --- a/src-tauri/src/core/cloud_sync/manager.rs +++ b/src-tauri/src/core/cloud_sync/manager.rs @@ -1943,7 +1943,7 @@ fn is_non_retryable_automatic_error(error: &AppError) -> bool { /// The frontend must not parse prose error text: keeping the code here means /// rewording a message can never silently disable the recovery button. fn cloud_sync_failure_code(provider: &str, error: &AppError) -> Option<&'static str> { - if !is_gist_provider(provider) { + if provider != "gitee_snippet" { return None; } @@ -1955,7 +1955,7 @@ fn cloud_sync_failure_code(provider: &str, error: &AppError) -> Option<&'static } } -/// `CloudSyncStatus.error_code` value for "the gist is full". +/// `CloudSyncStatus.error_code` value for Gitee snippet capacity failures. pub(super) const GIST_CAPACITY_ERROR_CODE: &str = "gist_capacity"; fn should_record_startup_check_failure(error: &AppError) -> bool { @@ -2309,20 +2309,28 @@ mod tests { } #[test] - fn gist_capacity_failure_code_is_limited_to_gist_providers() { - let capacity_error = AppError::CloudSync(CloudSyncError::SnapshotNotAccepted { - revision: "r1".to_string(), - }); - - assert_eq!( - cloud_sync_failure_code("gitee_snippet", &capacity_error), - Some(GIST_CAPACITY_ERROR_CODE) - ); - assert_eq!( - cloud_sync_failure_code("github_gist", &capacity_error), - Some(GIST_CAPACITY_ERROR_CODE) - ); - assert_eq!(cloud_sync_failure_code("webdav", &capacity_error), None); + fn gist_capacity_failure_code_is_limited_to_gitee_snippet() { + for error in [ + CloudSyncError::SnapshotNotAccepted { + revision: "r1".to_string(), + }, + CloudSyncError::RemoteFileRejected { + filename: "nyaterm-x.blob".to_string(), + }, + ] { + let error = AppError::CloudSync(error); + assert_eq!( + cloud_sync_failure_code("gitee_snippet", &error), + Some(GIST_CAPACITY_ERROR_CODE) + ); + for provider in ["github_gist", "webdav", "s3"] { + assert_eq!( + cloud_sync_failure_code(provider, &error), + None, + "{provider}" + ); + } + } assert_eq!( cloud_sync_failure_code( "gitee_snippet", @@ -2372,6 +2380,27 @@ mod tests { assert!(status.error_code.is_none()); } + #[tokio::test] + async fn record_failure_does_not_publish_capacity_code_for_github_gist() { + let manager = CloudSyncManager::new(); + manager.settings.lock().await.provider = "github_gist".to_string(); + manager.status.lock().await.error_code = Some(GIST_CAPACITY_ERROR_CODE.to_string()); + + manager + .record_failure( + "sync", + "manual_push", + &AppError::CloudSync(CloudSyncError::RemoteFileRejected { + filename: "nyaterm-x.blob".to_string(), + }), + ) + .await; + + let status = manager.status.lock().await.clone(); + assert_eq!(status.state, "failed"); + assert!(status.error_code.is_none()); + } + #[tokio::test] async fn record_failure_clears_current_operation() { let manager = CloudSyncManager::new(); diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 7ff82f1f..777a26ce 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -2562,7 +2562,7 @@ "syncPushSuccess": "Snapshot uploaded.", "syncPruneAndRetry": "Clean remote snapshots & retry", "syncPruneAndRetrySuccess": "Remote snapshots cleaned and push succeeded.", - "syncGistCapacityHint": "Gitee/GitHub gist file limit may be full. Cleaning old snapshots and pushing again usually recovers sync.", + "syncGistCapacityHint": "Gitee Snippet may have reached its file limit. Cleaning old snapshots and pushing again usually recovers sync.", "syncRecoverCurrentSuccess": "Remote metadata recovered.", "syncRemoteIncompleteTitle": "Incomplete Remote Sync Data", "syncResolveDownloadSuccess": "Remote version downloaded.", diff --git a/src/i18n/locales/ko.json b/src/i18n/locales/ko.json index eb857f4e..1d8139e5 100644 --- a/src/i18n/locales/ko.json +++ b/src/i18n/locales/ko.json @@ -2560,7 +2560,7 @@ "syncPushSuccess": "스냅샷을 업로드했습니다.", "syncPruneAndRetry": "원격 스냅샷 정리 후 재시도", "syncPruneAndRetrySuccess": "원격 스냅샷을 정리하고 푸시에 성공했습니다.", - "syncGistCapacityHint": "Gitee/GitHub gist 파일 수 한도에 도달했을 수 있습니다. 오래된 스냅샷을 정리한 뒤 다시 푸시하면 보통 복구됩니다.", + "syncGistCapacityHint": "Gitee gist 파일 수 한도에 도달했을 수 있습니다. 오래된 스냅샷을 정리한 뒤 다시 푸시하면 보통 복구됩니다.", "syncRecoverCurrentSuccess": "원격 동기화 메타데이터를 복구했습니다.", "syncRemoteIncompleteTitle": "불완전한 원격 동기화 데이터", "syncResolveDownloadSuccess": "원격 버전을 다운로드했습니다.", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 73d76fb9..85c3746c 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -2560,7 +2560,7 @@ "syncPushSuccess": "同步快照已上传。", "syncPruneAndRetry": "清理远端旧快照并重试", "syncPruneAndRetrySuccess": "已清理远端旧快照并推送成功。", - "syncGistCapacityHint": "Gitee/GitHub 代码片段可能已达文件数上限。清理旧快照后重新推送通常即可恢复。", + "syncGistCapacityHint": "Gitee 代码片段可能已达文件数上限。清理旧快照后重新推送通常即可恢复。", "syncRecoverCurrentSuccess": "远端同步元数据已恢复。", "syncRemoteIncompleteTitle": "远端同步数据不完整", "syncResolveDownloadSuccess": "远程版本已成功下载。", diff --git a/src/i18n/locales/zh-TW.json b/src/i18n/locales/zh-TW.json index 861371ec..65897fd5 100644 --- a/src/i18n/locales/zh-TW.json +++ b/src/i18n/locales/zh-TW.json @@ -2555,7 +2555,7 @@ "syncPushSuccess": "雲端同步快照已上傳。", "syncPruneAndRetry": "清理遠端舊快照並重試", "syncPruneAndRetrySuccess": "已清理遠端舊快照並推送成功。", - "syncGistCapacityHint": "Gitee/GitHub 程式碼片段可能已達檔案數上限。清理舊快照後重新推送通常即可恢復。", + "syncGistCapacityHint": "Gitee 程式碼片段可能已達檔案數上限。清理舊快照後重新推送通常即可恢復。", "syncRecoverCurrentSuccess": "遠端同步中繼資料已復原。", "syncRemoteIncompleteTitle": "遠端同步資料不完整", "syncResolveDownloadSuccess": "遠端版本已成功下載。", diff --git a/src/lib/cloudSync.test.ts b/src/lib/cloudSync.test.ts index 680c007a..267f1cd9 100644 --- a/src/lib/cloudSync.test.ts +++ b/src/lib/cloudSync.test.ts @@ -40,7 +40,7 @@ describe("cloud sync gist capacity helpers", () => { expect( needsGistCapacityRecovery("gitee_snippet", rewordedMessage, GIST_CAPACITY_ERROR_CODE), ).toBe(true); - expect(needsGistCapacityRecovery("github_gist", "", GIST_CAPACITY_ERROR_CODE)).toBe(true); + expect(needsGistCapacityRecovery("gitee_snippet", "", GIST_CAPACITY_ERROR_CODE)).toBe(true); }); it("falls back to the message when the status carries no code", () => { @@ -48,11 +48,22 @@ describe("cloud sync gist capacity helpers", () => { expect(needsGistCapacityRecovery("gitee_snippet", REJECTED_FILE_MESSAGE, undefined)).toBe(true); }); - it("only offers capacity recovery for gist providers", () => { + it.each([ + NOT_ACCEPTED_MESSAGE, + REJECTED_FILE_MESSAGE, + ])("does not offer GitHub Gist capacity recovery for %s", (message) => { + expect(needsGistCapacityRecovery("github_gist", message)).toBe(false); + expect(needsGistCapacityRecovery("github_gist", message, null)).toBe(false); + expect(needsGistCapacityRecovery("github_gist", message, GIST_CAPACITY_ERROR_CODE)).toBe(false); + }); + + it("only offers capacity recovery for Gitee Snippet", () => { expect(needsGistCapacityRecovery("gitee_snippet", NOT_ACCEPTED_MESSAGE)).toBe(true); - expect(needsGistCapacityRecovery("github_gist", REJECTED_FILE_MESSAGE)).toBe(true); + expect(needsGistCapacityRecovery("github_gist", "", GIST_CAPACITY_ERROR_CODE)).toBe(false); expect(needsGistCapacityRecovery("webdav", NOT_ACCEPTED_MESSAGE)).toBe(false); expect(needsGistCapacityRecovery("s3", REJECTED_FILE_MESSAGE)).toBe(false); + expect(needsGistCapacityRecovery(null, NOT_ACCEPTED_MESSAGE)).toBe(false); + expect(needsGistCapacityRecovery(undefined, REJECTED_FILE_MESSAGE)).toBe(false); expect( needsGistCapacityRecovery("webdav", NOT_ACCEPTED_MESSAGE, GIST_CAPACITY_ERROR_CODE), ).toBe(false); diff --git a/src/lib/cloudSync.ts b/src/lib/cloudSync.ts index e60c1ea5..74a7ffee 100644 --- a/src/lib/cloudSync.ts +++ b/src/lib/cloudSync.ts @@ -239,12 +239,12 @@ export function isRemoteInconsistentConflict(conflict?: CloudConflictPreview | n return conflict?.kind === "remote_inconsistent"; } -/** Providers that keep every sync object in one gist-style, file-count limited list. */ +/** Providers that keep every sync object in one flat gist-style file list. */ export function isGistCloudProvider(provider?: string | null) { return provider === "gitee_snippet" || provider === "github_gist"; } -/** `CloudSyncStatus.error_code` published when the remote gist is full. */ +/** `CloudSyncStatus.error_code` published for Gitee snippet capacity failures. */ export const GIST_CAPACITY_ERROR_CODE = "gist_capacity"; /** @@ -262,13 +262,13 @@ export function isGistCapacitySyncFailure(message?: string | null) { ); } -/** Capacity recovery only applies to gist providers: other backends fail for other reasons. */ +/** Capacity recovery only applies to Gitee Snippet's hard file-count limit. */ export function needsGistCapacityRecovery( provider?: string | null, message?: string | null, errorCode?: string | null, ) { - if (!isGistCloudProvider(provider)) { + if (provider !== "gitee_snippet") { return false; } return errorCode === GIST_CAPACITY_ERROR_CODE || isGistCapacitySyncFailure(message);