云同步配置

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 { ScriptMenu, ScriptMenuItem } from "./popup";
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 {
constructor(msg: MessageSend) {
@ -156,11 +160,28 @@ export class SynchronizeClient extends Client {
super(msg, "serviceWorker/synchronize");
}
backup(uuids?: string[]) {
return this.do("backup", uuids);
export(uuids?: string[]) {
return this.do("export", uuids);
}
openImportWindow(filename: string, url: string) {
return this.do("openImportWindow", { filename, url });
backupToCloud(type: FileSystemType, params: any) {
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;
}
});
// 监听配置变化
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 dayjs from "dayjs";
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 {
logger: Logger;
@ -169,8 +172,8 @@ export class SynchronizeService {
};
}
// 请求生成备份文件
async requestBackup(uuids?: string[]) {
// 请求导出文件
async requestExport(uuids?: string[]) {
const zip = new JSZip();
const fs = new ZipFileSystem(zip);
await this.backup(fs, uuids);
@ -192,11 +195,49 @@ export class SynchronizeService {
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() {
this.group.on("backup", this.requestBackup.bind(this));
this.group.on("import", this.openImportWindow.bind(this));
this.group.on("export", this.requestExport.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": "个时,自动隐藏",
"script_name_cannot_be_set_to_empty": "脚本name不可以设置为空",
"eslint_config_format_error": "eslint配置格式错误",
"export_success": "导出成功"
"export_success": "导出成功",
"get_backup_dir_url_failed": "获取备份目录地址失败"
}

View File

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

View File

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