云同步配置

This commit is contained in:
王一之 2025-04-18 18:01:05 +08:00
parent 07c4518cba
commit 185ba6e5cc
8 changed files with 127 additions and 50 deletions

View File

@ -5,6 +5,10 @@ import { Resource } from "@App/app/repo/resource";
import { MessageSend } from "@Packages/message/server"; import { MessageSend } from "@Packages/message/server";
import { ScriptMenu, ScriptMenuItem } from "./popup"; import { ScriptMenu, ScriptMenuItem } from "./popup";
import PermissionVerify, { ConfirmParam, UserConfirm } from "./permission_verify"; import PermissionVerify, { ConfirmParam, UserConfirm } from "./permission_verify";
import { FileSystemType } from "@Packages/filesystem/factory";
import { v4 as uuidv4 } from "uuid";
import Cache from "@App/app/cache";
import CacheKey from "@App/app/cache_key";
export class ServiceWorkerClient extends Client { export class ServiceWorkerClient extends Client {
constructor(msg: MessageSend) { constructor(msg: MessageSend) {
@ -156,11 +160,28 @@ export class SynchronizeClient extends Client {
super(msg, "serviceWorker/synchronize"); super(msg, "serviceWorker/synchronize");
} }
backup(uuids?: string[]) { export(uuids?: string[]) {
return this.do("backup", uuids); return this.do("export", uuids);
} }
openImportWindow(filename: string, url: string) { backupToCloud(type: FileSystemType, params: any) {
return this.do("openImportWindow", { filename, url }); return this.do("backupToCloud", { type, params });
}
async openImportWindow(filename: string, file: File | Blob) {
// 打开导入窗口用cache实现数据交互
const url = URL.createObjectURL(file);
setTimeout(() => {
URL.revokeObjectURL(url);
}, 60 * 1000);
const uuid = uuidv4();
await Cache.getInstance().set(CacheKey.importFile(uuid), {
filename: filename,
url: url,
});
// 打开导入窗口用cache实现数据交互
chrome.tabs.create({
url: `/src/import.html?uuid=${uuid}`,
});
} }
} }

View File

@ -50,5 +50,16 @@ export default class ServiceWorkerManager {
break; break;
} }
}); });
// 监听配置变化
this.mq.subscribe("systemConfigChange", (msg) => {
console.log("systemConfigChange", msg);
switch (msg.key) {
case "cloud_sync": {
synchronize.startCloudSync(msg.value);
break;
}
}
});
} }
} }

View File

@ -12,6 +12,9 @@ import { ValueService } from "./value";
import { ResourceService } from "./resource"; import { ResourceService } from "./resource";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { createObjectURL } from "../offscreen/client"; import { createObjectURL } from "../offscreen/client";
import FileSystemFactory, { FileSystemType } from "@Packages/filesystem/factory";
import { systemConfig } from "@App/pages/store/global";
import { CloudSyncConfig } from "@App/pkg/config/config";
export class SynchronizeService { export class SynchronizeService {
logger: Logger; logger: Logger;
@ -169,8 +172,8 @@ export class SynchronizeService {
}; };
} }
// 请求生成备份文件 // 请求导出文件
async requestBackup(uuids?: string[]) { async requestExport(uuids?: string[]) {
const zip = new JSZip(); const zip = new JSZip();
const fs = new ZipFileSystem(zip); const fs = new ZipFileSystem(zip);
await this.backup(fs, uuids); await this.backup(fs, uuids);
@ -192,11 +195,49 @@ export class SynchronizeService {
return Promise.resolve(); return Promise.resolve();
} }
// 请求导入备份文件 // 备份到云端
async openImportWindow(url: string) {} async backupToCloud({ type, params }: { type: FileSystemType; params: any }) {
// 首先生成zip文件
const zip = new JSZip();
const fs = new ZipFileSystem(zip);
await this.backup(fs);
this.logger.info("backup to cloud");
// 然后创建云端文件系统
let cloudFs = await FileSystemFactory.create(type, params);
try {
await cloudFs.createDir("ScriptCat");
cloudFs = await cloudFs.openDir("ScriptCat");
// 云端文件系统写入文件
const file = await cloudFs.create(`scriptcat-backup-${dayjs().format("YYYY-MM-DDTHH-mm-ss")}.zip`);
await file.write(
await zip.generateAsync({
type: "blob",
compression: "DEFLATE",
compressionOptions: {
level: 9,
},
comment: "Created by Scriptcat",
})
);
} catch (e) {
this.logger.error("backup to cloud error", Logger.E(e));
return Promise.reject(e);
}
return Promise.resolve();
}
cloudSync() {}
startCloudSync(value: CloudSyncConfig) {
if (value.enable) {
this.cloudSync();
}
}
init() { init() {
this.group.on("backup", this.requestBackup.bind(this)); this.group.on("export", this.requestExport.bind(this));
this.group.on("import", this.openImportWindow.bind(this)); this.group.on("backupToCloud", this.backupToCloud.bind(this));
// this.group.on("import", this.openImportWindow.bind(this));
} }
} }

View File

@ -365,5 +365,6 @@
"menu_expand_num_after": "个时,自动隐藏", "menu_expand_num_after": "个时,自动隐藏",
"script_name_cannot_be_set_to_empty": "脚本name不可以设置为空", "script_name_cannot_be_set_to_empty": "脚本name不可以设置为空",
"eslint_config_format_error": "eslint配置格式错误", "eslint_config_format_error": "eslint配置格式错误",
"export_success": "导出成功" "export_success": "导出成功",
"get_backup_dir_url_failed": "获取备份目录地址失败"
} }

View File

@ -723,7 +723,7 @@ function ScriptList() {
id: "export", id: "export",
content: t("exporting"), content: t("exporting"),
}); });
new SynchronizeClient(message).backup(uuids).then(() => { new SynchronizeClient(message).export(uuids).then(() => {
Message.success({ Message.success({
id: "export", id: "export",
content: t("export_success"), content: t("export_success"),

View File

@ -170,14 +170,15 @@ function Setting() {
return; return;
} }
} }
const params = { ...systemConfig.backup.params }; const cloudSync = await systemConfig.getCloudSync();
const params = { ...cloudSync.params };
params[fileSystemType] = fileSystemParams; params[fileSystemType] = fileSystemParams;
systemConfig.cloudSync = { systemConfig.setCloudSync({
enable: enableCloudSync, enable: enableCloudSync || false,
syncDelete, syncDelete: syncDelete || false,
filesystem: fileSystemType, filesystem: fileSystemType,
params, params,
}; });
Message.success(t("save_success")!); Message.success(t("save_success")!);
}} }}
> >

View File

@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { Button, Card, Checkbox, Drawer, Empty, Input, List, Message, Modal, Space } from "@arco-design/web-react"; import { Button, Card, Checkbox, Drawer, Empty, Input, List, Message, Modal, Space } from "@arco-design/web-react";
import Title from "@arco-design/web-react/es/Typography/title"; import Title from "@arco-design/web-react/es/Typography/title";
import { formatUnixTime } from "@App/pkg/utils/utils"; import { formatUnixTime } from "@App/pkg/utils/utils";
@ -6,12 +6,11 @@ import FileSystemParams from "@App/pages/components/FileSystemParams";
import { IconQuestionCircleFill } from "@arco-design/web-react/icon"; import { IconQuestionCircleFill } from "@arco-design/web-react/icon";
import { RefInputType } from "@arco-design/web-react/es/Input/interface"; import { RefInputType } from "@arco-design/web-react/es/Input/interface";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { FileSystemType } from "@Packages/filesystem/factory"; import FileSystemFactory, { FileSystemType } from "@Packages/filesystem/factory";
import { File, FileReader } from "@Packages/filesystem/filesystem";
import { message, systemConfig } from "@App/pages/store/global"; import { message, systemConfig } from "@App/pages/store/global";
import { SynchronizeClient } from "@App/app/service/service_worker/client"; import { SynchronizeClient } from "@App/app/service/service_worker/client";
import Cache from "@App/app/cache"; import { set } from "node_modules/yaml/dist/schema/yaml-1.1/set";
import CacheKey from "@App/app/cache_key";
import { v4 as uuidv4 } from "uuid";
const synchronizeClient = new SynchronizeClient(message); const synchronizeClient = new SynchronizeClient(message);
@ -22,16 +21,26 @@ function Tools() {
const [fileSystemParams, setFilesystemParam] = useState<{ const [fileSystemParams, setFilesystemParam] = useState<{
[key: string]: any; [key: string]: any;
}>({}); }>({});
const [vscodeUrl, setVscodeUrl] = useState<string>("");
const [vscodeReconnect, setVscodeReconnect] = useState<boolean>(false);
const [backupFileList, setBackupFileList] = useState<File[]>([]); const [backupFileList, setBackupFileList] = useState<File[]>([]);
const vscodeRef = useRef<RefInputType>(null); const vscodeRef = useRef<RefInputType>(null);
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => { useEffect(() => {
// 获取配置 // 获取配置
systemConfig.getBackup().then((backup) => { const loadConfig = async () => {
const [backup, vscodeUrl] = await Promise.all([
systemConfig.getBackup(),
systemConfig.getVscodeUrl(),
systemConfig.getVscodeReconnect(),
]);
setFilesystemType(backup.filesystem); setFilesystemType(backup.filesystem);
setFilesystemParam(backup.params[backup.filesystem] || {}); setFilesystemParam(backup.params[backup.filesystem] || {});
}); setVscodeUrl(vscodeUrl);
setVscodeReconnect(systemConfig.vscodeReconnect);
};
loadConfig();
}, []); }, []);
return ( return (
@ -55,7 +64,7 @@ function Tools() {
loading={loading.local} loading={loading.local}
onClick={async () => { onClick={async () => {
setLoading((prev) => ({ ...prev, local: true })); setLoading((prev) => ({ ...prev, local: true }));
await synchronizeClient.backup(); await synchronizeClient.export();
setLoading((prev) => ({ ...prev, local: false })); setLoading((prev) => ({ ...prev, local: false }));
}} }}
> >
@ -74,21 +83,9 @@ function Tools() {
if (!file) { if (!file) {
return; return;
} }
const url = URL.createObjectURL(file);
try { try {
const uuid = uuidv4(); await synchronizeClient.openImportWindow(file.name, file);
Cache.getInstance() Message.success(t("select_import_script")!);
.set(CacheKey.importFile(uuid), {
filename: file.name,
url: url,
})
.then(() => {
Message.success(t("select_import_script")!);
// 打开导入窗口
chrome.tabs.create({
url: `/src/import.html?uuid=${uuid}`,
});
});
} catch (e) { } catch (e) {
Message.error(`${t("import_error")}: ${e}`); Message.error(`${t("import_error")}: ${e}`);
} }
@ -123,7 +120,7 @@ function Tools() {
}); });
setLoading((prev) => ({ ...prev, cloud: true })); setLoading((prev) => ({ ...prev, cloud: true }));
Message.info(t("preparing_backup")!); Message.info(t("preparing_backup")!);
syncCtrl synchronizeClient
.backupToCloud(fileSystemType, fileSystemParams) .backupToCloud(fileSystemType, fileSystemParams)
.then(() => { .then(() => {
Message.success(t("backup_success")!); Message.success(t("backup_success")!);
@ -221,12 +218,8 @@ function Tools() {
Message.error(`${t("pull_failed")}: ${e}`); Message.error(`${t("pull_failed")}: ${e}`);
return; return;
} }
const url = URL.createObjectURL(data); synchronizeClient
setTimeout(() => { .openImportWindow(item.name, data)
URL.revokeObjectURL(url);
}, 60 * 100000);
syncCtrl
.openImportWindow(item.name, url)
.then(() => { .then(() => {
Message.success(t("select_import_script")!); Message.success(t("select_import_script")!);
}) })
@ -299,22 +292,24 @@ function Tools() {
<Title heading={6}>{t("vscode_url")}</Title> <Title heading={6}>{t("vscode_url")}</Title>
<Input <Input
ref={vscodeRef} ref={vscodeRef}
defaultValue={systemConfig.vscodeUrl} value={vscodeUrl}
onChange={(value) => { onChange={(value) => {
systemConfig.vscodeUrl = value; setVscodeUrl(value);
}} }}
/> />
<Checkbox <Checkbox
checked={vscodeReconnect}
onChange={(checked) => { onChange={(checked) => {
systemConfig.vscodeReconnect = checked; setVscodeReconnect(checked);
}} }}
defaultChecked={systemConfig.vscodeReconnect}
> >
{t("auto_connect_vscode_service")} {t("auto_connect_vscode_service")}
</Checkbox> </Checkbox>
<Button <Button
type="primary" type="primary"
onClick={() => { onClick={() => {
systemConfig.setVscodeUrl(vscodeUrl);
systemConfig.setVscodeReconnect(vscodeReconnect);
const ctrl = IoC.instance(SystemController) as SystemController; const ctrl = IoC.instance(SystemController) as SystemController;
ctrl ctrl
.connectVSCode() .connectVSCode()

View File

@ -33,6 +33,13 @@ export class SystemConfig {
}); });
} }
addListener(key: string, callback: (value: any) => void) {
this.mq.subscribe(key, (msg) => {
const { value } = msg;
callback(value);
});
}
async getAll(): Promise<{ [key: string]: any }> { async getAll(): Promise<{ [key: string]: any }> {
const ret: { [key: string]: any } = {}; const ret: { [key: string]: any } = {};
const list = await this.storage.keys(); const list = await this.storage.keys();