|
|
@@ -99,24 +99,22 @@ export const useSyncStore = createPersistStore(
|
|
|
try {
|
|
|
const remoteState = await client.get(config.username);
|
|
|
if (!remoteState || remoteState === "") {
|
|
|
+ await client.set(config.username, JSON.stringify(localState));
|
|
|
console.log("[Sync] Remote state is empty, using local state instead.");
|
|
|
return
|
|
|
} else {
|
|
|
const parsedRemoteState = JSON.parse(
|
|
|
await client.get(config.username),
|
|
|
) as AppState;
|
|
|
-
|
|
|
mergeAppState(localState, parsedRemoteState);
|
|
|
setLocalAppState(localState);
|
|
|
- }
|
|
|
-
|
|
|
+ }
|
|
|
} catch (e) {
|
|
|
console.log("[Sync] failed to get remote state", e);
|
|
|
throw e;
|
|
|
}
|
|
|
|
|
|
await client.set(config.username, JSON.stringify(localState));
|
|
|
- console.log("client set", localState);
|
|
|
this.markSyncTime();
|
|
|
},
|
|
|
|
|
|
@@ -149,3 +147,161 @@ export const useSyncStore = createPersistStore(
|
|
|
},
|
|
|
},
|
|
|
);
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+**Output:**
|
|
|
+<!-- prettier-ignore -->
|
|
|
+```tsx
|
|
|
+import { getClientConfig } from "../config/client";
|
|
|
+import { Updater } from "../typing";
|
|
|
+import { ApiPath, STORAGE_KEY, StoreKey } from "../constant";
|
|
|
+import { createPersistStore } from "../utils/store";
|
|
|
+import {
|
|
|
+ AppState,
|
|
|
+ getLocalAppState,
|
|
|
+ GetStoreState,
|
|
|
+ mergeAppState,
|
|
|
+ setLocalAppState,
|
|
|
+} from "../utils/sync";
|
|
|
+import { downloadAs, readFromFile } from "../utils";
|
|
|
+import { showToast } from "../components/ui-lib";
|
|
|
+import Locale from "../locales";
|
|
|
+import { createSyncClient, ProviderType } from "../utils/cloud";
|
|
|
+import { corsPath } from "../utils/cors";
|
|
|
+
|
|
|
+export interface WebDavConfig {
|
|
|
+ server: string;
|
|
|
+ username: string;
|
|
|
+ password: string;
|
|
|
+}
|
|
|
+
|
|
|
+const isApp = !!getClientConfig()?.isApp;
|
|
|
+export type SyncStore = GetStoreState<typeof useSyncStore>;
|
|
|
+
|
|
|
+const DEFAULT_SYNC_STATE = {
|
|
|
+ provider: ProviderType.WebDAV,
|
|
|
+ useProxy: true,
|
|
|
+ proxyUrl: corsPath(ApiPath.Cors),
|
|
|
+
|
|
|
+ webdav: {
|
|
|
+ endpoint: "",
|
|
|
+ username: "",
|
|
|
+ password: "",
|
|
|
+ },
|
|
|
+
|
|
|
+ upstash: {
|
|
|
+ endpoint: "",
|
|
|
+ username: STORAGE_KEY,
|
|
|
+ apiKey: "",
|
|
|
+ },
|
|
|
+
|
|
|
+ lastSyncTime: 0,
|
|
|
+ lastProvider: "",
|
|
|
+};
|
|
|
+
|
|
|
+export const useSyncStore = createPersistStore(
|
|
|
+ DEFAULT_SYNC_STATE,
|
|
|
+ (set, get) => ({
|
|
|
+ cloudSync() {
|
|
|
+ const config = get()[get().provider];
|
|
|
+ return Object.values(config).every((c) => c.toString().length > 0);
|
|
|
+ },
|
|
|
+
|
|
|
+ markSyncTime() {
|
|
|
+ set({ lastSyncTime: Date.now(), lastProvider: get().provider });
|
|
|
+ },
|
|
|
+
|
|
|
+ export() {
|
|
|
+ const state = getLocalAppState();
|
|
|
+ const datePart = isApp
|
|
|
+ ? `${new Date().toLocaleDateString().replace(/\//g, "_")} ${new Date()
|
|
|
+ .toLocaleTimeString()
|
|
|
+ .replace(/:/g, "_")}`
|
|
|
+ : new Date().toLocaleString();
|
|
|
+
|
|
|
+ const fileName = `Backup-${datePart}.json`;
|
|
|
+ downloadAs(JSON.stringify(state), fileName);
|
|
|
+ },
|
|
|
+
|
|
|
+ async import() {
|
|
|
+ const rawContent = await readFromFile();
|
|
|
+
|
|
|
+ try {
|
|
|
+ const remoteState = JSON.parse(rawContent) as AppState;
|
|
|
+ const localState = getLocalAppState();
|
|
|
+ mergeAppState(localState, remoteState);
|
|
|
+ setLocalAppState(localState);
|
|
|
+ location.reload();
|
|
|
+ } catch (e) {
|
|
|
+ console.error("[Import]", e);
|
|
|
+ showToast(Locale.Settings.Sync.ImportFailed);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ getClient() {
|
|
|
+ const provider = get().provider;
|
|
|
+ const client = createSyncClient(provider, get());
|
|
|
+ return client;
|
|
|
+ },
|
|
|
+
|
|
|
+ async sync() {
|
|
|
+ const localState = getLocalAppState();
|
|
|
+ const provider = get().provider;
|
|
|
+ const config = get()[provider];
|
|
|
+ const client = this.getClient();
|
|
|
+
|
|
|
+ try {
|
|
|
+ const remoteState = await client.get(config.username);
|
|
|
+ if (!remoteState || remoteState === "") {
|
|
|
+ console.log(
|
|
|
+ "[Sync] Remote state is empty, using local state instead.",
|
|
|
+ );
|
|
|
+ return;
|
|
|
+ } else {
|
|
|
+ const parsedRemoteState = JSON.parse(
|
|
|
+ await client.get(config.username),
|
|
|
+ ) as AppState;
|
|
|
+
|
|
|
+ mergeAppState(localState, parsedRemoteState);
|
|
|
+ setLocalAppState(localState);
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.log("[Sync] failed to get remote state", e);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+
|
|
|
+ await client.set(config.username, JSON.stringify(localState));
|
|
|
+ console.log("client set", localState);
|
|
|
+ this.markSyncTime();
|
|
|
+ },
|
|
|
+
|
|
|
+ async check() {
|
|
|
+ const client = this.getClient();
|
|
|
+ return await client.check();
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ {
|
|
|
+ name: StoreKey.Sync,
|
|
|
+ version: 1.2,
|
|
|
+
|
|
|
+ migrate(persistedState, version) {
|
|
|
+ const newState = persistedState as typeof DEFAULT_SYNC_STATE;
|
|
|
+
|
|
|
+ if (version < 1.1) {
|
|
|
+ newState.upstash.username = STORAGE_KEY;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (version < 1.2) {
|
|
|
+ if (
|
|
|
+ (persistedState as typeof DEFAULT_SYNC_STATE).proxyUrl ===
|
|
|
+ "/api/cors/"
|
|
|
+ ) {
|
|
|
+ newState.proxyUrl = "";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return newState as any;
|
|
|
+ },
|
|
|
+ },
|
|
|
+);
|