mirror of
https://github.com/rubickCenter/rubick
synced 2025-06-08 11:34:10 +08:00
✨ 支持 webdav 数据多端同步
This commit is contained in:
parent
d442fae447
commit
b94d725d69
@ -36,4 +36,11 @@ window.market = {
|
|||||||
removeLocalStartPlugin(plugin) {
|
removeLocalStartPlugin(plugin) {
|
||||||
ipcSend('removeLocalStartPlugin', { plugin });
|
ipcSend('removeLocalStartPlugin', { plugin });
|
||||||
},
|
},
|
||||||
|
dbDump(target) {
|
||||||
|
ipcSend('dbDump', { target });
|
||||||
|
},
|
||||||
|
|
||||||
|
dbImport(target) {
|
||||||
|
ipcSend('dbImport', { target });
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 3.2 KiB |
@ -99,6 +99,9 @@ export default {
|
|||||||
localstart: {
|
localstart: {
|
||||||
title: 'Local Start',
|
title: 'Local Start',
|
||||||
},
|
},
|
||||||
|
database: {
|
||||||
|
title: 'Data Synchronization',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
dev: {
|
dev: {
|
||||||
title: 'Developer',
|
title: 'Developer',
|
||||||
|
@ -97,6 +97,9 @@ export default {
|
|||||||
localstart: {
|
localstart: {
|
||||||
title: '本地启动',
|
title: '本地启动',
|
||||||
},
|
},
|
||||||
|
database: {
|
||||||
|
title: '多端数据同步',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
dev: {
|
dev: {
|
||||||
title: '开发者',
|
title: '开发者',
|
||||||
|
241
feature/src/views/settings/database.vue
Normal file
241
feature/src/views/settings/database.vue
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
<template>
|
||||||
|
<div class="export-header">
|
||||||
|
<a-button @click="exportData" size="small" type="primary" style="margin-right: 10px;">
|
||||||
|
导出数据
|
||||||
|
<template #icon>
|
||||||
|
<ExportOutlined />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="importData" danger size="small" style="margin-right: 10px;">
|
||||||
|
导入数据
|
||||||
|
<template #icon>
|
||||||
|
<ImportOutlined />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
<a-button type="link" size="small" @click="showSetting = true">
|
||||||
|
<template #icon>
|
||||||
|
<SettingOutlined />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<a-list item-layout="horizontal" :data-source="dataPlugins">
|
||||||
|
<template #renderItem="{ item }">
|
||||||
|
<a-list-item>
|
||||||
|
<template #actions>
|
||||||
|
<a v-if="!item.plugin.isdownload && !item.plugin.isloading" key="list-loadmore-edit"
|
||||||
|
@click="() => downloadPlugin(item.plugin)">
|
||||||
|
<CloudDownloadOutlined style="font-size: 18px;"/>
|
||||||
|
</a>
|
||||||
|
<a v-if="item.plugin.isloading" key="list-loadmore-edit">
|
||||||
|
<LoadingOutlined style="font-size: 18px;"/>
|
||||||
|
</a>
|
||||||
|
<a key="list-loadmore-edit" @click="() => showKeys(item)">
|
||||||
|
<DatabaseOutlined style="font-size: 18px;"/>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
<a-list-item-meta :description="`${item.keys.length} 份文档`">
|
||||||
|
<template #title>
|
||||||
|
<div>
|
||||||
|
<span>{{ item.plugin.pluginName }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #avatar>
|
||||||
|
<a-avatar shape="square" :src="item.plugin.logo"/>
|
||||||
|
</template>
|
||||||
|
</a-list-item-meta>
|
||||||
|
</a-list-item>
|
||||||
|
</template>
|
||||||
|
</a-list>
|
||||||
|
<a-drawer
|
||||||
|
v-model:visible="open"
|
||||||
|
width="400"
|
||||||
|
:closable="false"
|
||||||
|
:title="currentSelect.plugin.pluginName"
|
||||||
|
placement="right"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
class="key-item"
|
||||||
|
:key="key"
|
||||||
|
@click="() => showDetail(key)"
|
||||||
|
v-for="key in currentSelect.keys"
|
||||||
|
>
|
||||||
|
{{ key }}
|
||||||
|
</p>
|
||||||
|
</a-drawer>
|
||||||
|
<a-modal
|
||||||
|
centered
|
||||||
|
bodyStyle="max-height: 500px; overflow: auto"
|
||||||
|
:footer="null"
|
||||||
|
:closable="false"
|
||||||
|
v-model:visible="show"
|
||||||
|
>
|
||||||
|
<pre>{{ JSON.stringify(detail, null, 2) }}</pre>
|
||||||
|
</a-modal>
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="showSetting"
|
||||||
|
title="坚果云 webdav 配置"
|
||||||
|
:footer="null"
|
||||||
|
>
|
||||||
|
<a-alert style="margin-bottom: 20px;" type="info" show-icon>
|
||||||
|
<template #message>
|
||||||
|
<div
|
||||||
|
@click="openHelp"
|
||||||
|
class="openHelp">
|
||||||
|
点击查看如何获取坚果云账号密码
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
<a-form
|
||||||
|
:model="formState"
|
||||||
|
name="basic"
|
||||||
|
:label-col="{ span: 8 }"
|
||||||
|
:wrapper-col="{ span: 16 }"
|
||||||
|
autocomplete="off"
|
||||||
|
@finish="handleOk"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
label="username"
|
||||||
|
name="username"
|
||||||
|
:rules="[{ required: true, message: '请填写 username!' }]"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="formState.username" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
label="password"
|
||||||
|
name="password"
|
||||||
|
:rules="[{ required: true, message: '请填写 password!' }]"
|
||||||
|
>
|
||||||
|
<a-input-password v-model:value="formState.password" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
|
||||||
|
<a-button type="primary" html-type="submit">保存设置</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { useStore } from 'vuex';
|
||||||
|
import { computed, ref, reactive } from 'vue';
|
||||||
|
import {
|
||||||
|
DatabaseOutlined,
|
||||||
|
CloudDownloadOutlined,
|
||||||
|
LoadingOutlined,
|
||||||
|
ExportOutlined,
|
||||||
|
ImportOutlined,
|
||||||
|
SettingOutlined,
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
import { message, Modal } from 'ant-design-vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const open = ref(false);
|
||||||
|
const show = ref(false);
|
||||||
|
const showSetting = ref(false);
|
||||||
|
const currentSelect = ref({ plugin: {} });
|
||||||
|
const detail = ref({});
|
||||||
|
|
||||||
|
const defaultConfig = window.rubick.dbStorage.getItem('rubick-db-jg-webdav') || {
|
||||||
|
url: 'https://dav.jianguoyun.com/dav/',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
};
|
||||||
|
const formState = reactive(defaultConfig);
|
||||||
|
|
||||||
|
const showKeys = (item) => {
|
||||||
|
open.value = true;
|
||||||
|
currentSelect.value = item;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
window.rubick.dbStorage.setItem('rubick-db-jg-webdav', JSON.parse(JSON.stringify(formState)));
|
||||||
|
message.success('保存成功');
|
||||||
|
showSetting.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const showDetail = (key) => {
|
||||||
|
show.value = true;
|
||||||
|
detail.value = window.rubick.db.get(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportData = () => {
|
||||||
|
if (!formState.password || !formState.username) {
|
||||||
|
return showSetting.value = true;
|
||||||
|
}
|
||||||
|
window.market.dbDump(JSON.parse(JSON.stringify(formState)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const importData = () => {
|
||||||
|
if (!formState.password || !formState.username) {
|
||||||
|
return showSetting.value = true;
|
||||||
|
}
|
||||||
|
Modal.confirm({
|
||||||
|
title: '导入确认',
|
||||||
|
content: '导入坚果云数据将会覆盖本地数据,是否确认导入?',
|
||||||
|
onOk() {
|
||||||
|
window.market.dbImport(JSON.parse(JSON.stringify(formState)));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const openHelp = () => {
|
||||||
|
window.rubick.shellOpenExternal('https://help.jianguoyun.com/?p=2064')
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
|
const pluginsData = window.rubick.db.get('RUBICK_PLUGIN_INFO');
|
||||||
|
|
||||||
|
const totalPlugins = computed(() => store.state.totalPlugins);
|
||||||
|
|
||||||
|
const dataPlugins = computed(() => {
|
||||||
|
if (!pluginsData) return [];
|
||||||
|
return pluginsData.data.map((item) => {
|
||||||
|
let plugin = null;
|
||||||
|
if (['rubick-system-feature'].includes(item.name)) {
|
||||||
|
plugin = {
|
||||||
|
pluginName: '主程序',
|
||||||
|
isdownload: true,
|
||||||
|
logo: require('../../assets/logo.png'),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
plugin = totalPlugins.value.find((p) => p.name === item.name);
|
||||||
|
}
|
||||||
|
const data = item.keys.map((key) => window.rubick.db.get(key));
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
plugin,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const startDownload = (name) => store.dispatch('startDownload', name);
|
||||||
|
const successDownload = (name) => store.dispatch('successDownload', name);
|
||||||
|
|
||||||
|
const downloadPlugin = async (plugin) => {
|
||||||
|
startDownload(plugin.name);
|
||||||
|
await window.market.downloadPlugin(plugin);
|
||||||
|
message.success(t('feature.dev.installSuccess', { pluginName: plugin.name }));
|
||||||
|
successDownload(plugin.name);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="less">
|
||||||
|
.openHelp {
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.export-header {
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.key-item {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--ant-primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -32,10 +32,16 @@
|
|||||||
<!-- </template>-->
|
<!-- </template>-->
|
||||||
<!-- {{ $t('feature.settings.superPanel.title') }}-->
|
<!-- {{ $t('feature.settings.superPanel.title') }}-->
|
||||||
<!-- </a-menu-item>-->
|
<!-- </a-menu-item>-->
|
||||||
<a-menu-item key="localhost">
|
<a-menu-item key="database">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<DatabaseOutlined />
|
<DatabaseOutlined />
|
||||||
</template>
|
</template>
|
||||||
|
{{ $t('feature.settings.database.title') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="localhost">
|
||||||
|
<template #icon>
|
||||||
|
<SafetyOutlined />
|
||||||
|
</template>
|
||||||
{{ $t('feature.settings.intranet.title') }}
|
{{ $t('feature.settings.intranet.title') }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
@ -227,6 +233,7 @@
|
|||||||
<SuperPanel v-if="currentSelect[0] === 'superpanel'" />
|
<SuperPanel v-if="currentSelect[0] === 'superpanel'" />
|
||||||
<Localhost v-if="currentSelect[0] === 'localhost'" />
|
<Localhost v-if="currentSelect[0] === 'localhost'" />
|
||||||
<LocalStart v-if="currentSelect[0] === 'localstart'" />
|
<LocalStart v-if="currentSelect[0] === 'localstart'" />
|
||||||
|
<DataBase v-if="currentSelect[0] === 'database'" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -240,6 +247,7 @@ import {
|
|||||||
PlusCircleOutlined,
|
PlusCircleOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
FolderOpenOutlined,
|
FolderOpenOutlined,
|
||||||
|
SafetyOutlined,
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
import { ref, reactive, watch, toRefs, computed } from 'vue';
|
import { ref, reactive, watch, toRefs, computed } from 'vue';
|
||||||
@ -248,6 +256,7 @@ import Localhost from './localhost.vue';
|
|||||||
import SuperPanel from './super-panel.vue';
|
import SuperPanel from './super-panel.vue';
|
||||||
import UserInfo from './user-info';
|
import UserInfo from './user-info';
|
||||||
import LocalStart from './local-start';
|
import LocalStart from './local-start';
|
||||||
|
import DataBase from './database';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import localConfig from '@/confOp';
|
import localConfig from '@/confOp';
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "rubick",
|
"name": "rubick",
|
||||||
"version": "3.1.1",
|
"version": "3.2.0",
|
||||||
"author": "muwoo <2424880409@qq.com>",
|
"author": "muwoo <2424880409@qq.com>",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -31,13 +31,17 @@
|
|||||||
"get-mac-apps": "^1.0.2",
|
"get-mac-apps": "^1.0.2",
|
||||||
"got": "^11.8.3",
|
"got": "^11.8.3",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
|
"memorystream": "^0.3.1",
|
||||||
"node-key-sender": "^1.0.11",
|
"node-key-sender": "^1.0.11",
|
||||||
"pinyin-match": "^1.2.4",
|
"pinyin-match": "^1.2.4",
|
||||||
"pouchdb": "^7.2.2",
|
"pouchdb": "^7.2.2",
|
||||||
|
"pouchdb-load": "^1.4.6",
|
||||||
|
"pouchdb-replication-stream": "^1.2.9",
|
||||||
"simple-plist": "0.2.1",
|
"simple-plist": "0.2.1",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
"vue-router": "^4.0.0-0",
|
"vue-router": "^4.0.0-0",
|
||||||
"vuex": "^4.0.0-0",
|
"vuex": "^4.0.0-0",
|
||||||
|
"webdav": "4.11.3",
|
||||||
"worker-loader": "^3.0.8"
|
"worker-loader": "^3.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -36,4 +36,11 @@ window.market = {
|
|||||||
removeLocalStartPlugin(plugin) {
|
removeLocalStartPlugin(plugin) {
|
||||||
ipcSend('removeLocalStartPlugin', { plugin });
|
ipcSend('removeLocalStartPlugin', { plugin });
|
||||||
},
|
},
|
||||||
|
dbDump(target) {
|
||||||
|
ipcSend('dbDump', { target });
|
||||||
|
},
|
||||||
|
|
||||||
|
dbImport(target) {
|
||||||
|
ipcSend('dbImport', { target });
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,20 @@
|
|||||||
import path from "path";
|
import path from 'path';
|
||||||
import fs from "fs";
|
import fs from 'fs';
|
||||||
import PouchDB from "pouchdb";
|
import PouchDB from 'pouchdb';
|
||||||
|
import { DBError, Doc, DocRes } from './types';
|
||||||
|
import WebDavOP from './webdav';
|
||||||
|
|
||||||
import { DBError, Doc, DocRes } from "./types";
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const replicationStream = require('pouchdb-replication-stream/dist/pouchdb.replication-stream.min.js');
|
||||||
|
// const load = require('pouchdb-load/dist/pouchdb.load.min.js');
|
||||||
|
|
||||||
export default class {
|
PouchDB.plugin(replicationStream.plugin);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const load = require('pouchdb-load');
|
||||||
|
PouchDB.plugin({ loadIt: load.load });
|
||||||
|
PouchDB.adapter('writableStream', replicationStream.adapters.writableStream);
|
||||||
|
|
||||||
|
export default class DB {
|
||||||
readonly docMaxByteLength;
|
readonly docMaxByteLength;
|
||||||
readonly docAttachmentMaxByteLength;
|
readonly docAttachmentMaxByteLength;
|
||||||
public dbpath;
|
public dbpath;
|
||||||
@ -15,7 +25,7 @@ export default class {
|
|||||||
this.docMaxByteLength = 2 * 1024 * 1024; // 2M
|
this.docMaxByteLength = 2 * 1024 * 1024; // 2M
|
||||||
this.docAttachmentMaxByteLength = 20 * 1024 * 1024; // 20M
|
this.docAttachmentMaxByteLength = 20 * 1024 * 1024; // 20M
|
||||||
this.dbpath = dbPath;
|
this.dbpath = dbPath;
|
||||||
this.defaultDbName = path.join(dbPath, "default");
|
this.defaultDbName = path.join(dbPath, 'default');
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {
|
init(): void {
|
||||||
@ -24,11 +34,11 @@ export default class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getDocId(name: string, id: string): string {
|
getDocId(name: string, id: string): string {
|
||||||
return name + "/" + id;
|
return name + '/' + id;
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceDocId(name: string, id: string): string {
|
replaceDocId(name: string, id: string): string {
|
||||||
return id.replace(name + "/", "");
|
return id.replace(name + '/', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
errorInfo(name: string, message: string): DBError {
|
errorInfo(name: string, message: string): DBError {
|
||||||
@ -38,7 +48,7 @@ export default class {
|
|||||||
private checkDocSize(doc: Doc<any>) {
|
private checkDocSize(doc: Doc<any>) {
|
||||||
if (Buffer.byteLength(JSON.stringify(doc)) > this.docMaxByteLength) {
|
if (Buffer.byteLength(JSON.stringify(doc)) > this.docMaxByteLength) {
|
||||||
return this.errorInfo(
|
return this.errorInfo(
|
||||||
"exception",
|
'exception',
|
||||||
`doc max size ${this.docMaxByteLength / 1024 / 1024} M`
|
`doc max size ${this.docMaxByteLength / 1024 / 1024} M`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -78,15 +88,15 @@ export default class {
|
|||||||
async remove(name: string, doc: Doc<any> | string) {
|
async remove(name: string, doc: Doc<any> | string) {
|
||||||
try {
|
try {
|
||||||
let target;
|
let target;
|
||||||
if ("object" == typeof doc) {
|
if ('object' == typeof doc) {
|
||||||
target = doc;
|
target = doc;
|
||||||
if (!target._id || "string" !== typeof target._id) {
|
if (!target._id || 'string' !== typeof target._id) {
|
||||||
return this.errorInfo("exception", "doc _id error");
|
return this.errorInfo('exception', 'doc _id error');
|
||||||
}
|
}
|
||||||
target._id = this.getDocId(name, target._id);
|
target._id = this.getDocId(name, target._id);
|
||||||
} else {
|
} else {
|
||||||
if ("string" !== typeof doc) {
|
if ('string' !== typeof doc) {
|
||||||
return this.errorInfo("exception", "param error");
|
return this.errorInfo('exception', 'param error');
|
||||||
}
|
}
|
||||||
target = await this.pouchDB.get(this.getDocId(name, doc));
|
target = await this.pouchDB.get(this.getDocId(name, doc));
|
||||||
}
|
}
|
||||||
@ -94,7 +104,7 @@ export default class {
|
|||||||
target._id = result.id = this.replaceDocId(name, result.id);
|
target._id = result.id = this.replaceDocId(name, result.id);
|
||||||
return result;
|
return result;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if ("object" === typeof doc) {
|
if ('object' === typeof doc) {
|
||||||
doc._id = this.replaceDocId(name, doc._id);
|
doc._id = this.replaceDocId(name, doc._id);
|
||||||
}
|
}
|
||||||
return this.errorInfo(e.name, e.message);
|
return this.errorInfo(e.name, e.message);
|
||||||
@ -107,11 +117,11 @@ export default class {
|
|||||||
): Promise<DBError | Array<DocRes>> {
|
): Promise<DBError | Array<DocRes>> {
|
||||||
let result;
|
let result;
|
||||||
try {
|
try {
|
||||||
if (!Array.isArray(docs)) return this.errorInfo("exception", "not array");
|
if (!Array.isArray(docs)) return this.errorInfo('exception', 'not array');
|
||||||
if (docs.find((e) => !e._id))
|
if (docs.find((e) => !e._id))
|
||||||
return this.errorInfo("exception", "doc not _id field");
|
return this.errorInfo('exception', 'doc not _id field');
|
||||||
if (new Set(docs.map((e) => e._id)).size !== docs.length)
|
if (new Set(docs.map((e) => e._id)).size !== docs.length)
|
||||||
return this.errorInfo("exception", "_id value exists as");
|
return this.errorInfo('exception', '_id value exists as');
|
||||||
for (const doc of docs) {
|
for (const doc of docs) {
|
||||||
const err = this.checkDocSize(doc);
|
const err = this.checkDocSize(doc);
|
||||||
if (err) return err;
|
if (err) return err;
|
||||||
@ -144,20 +154,20 @@ export default class {
|
|||||||
): Promise<DBError | Array<DocRes>> {
|
): Promise<DBError | Array<DocRes>> {
|
||||||
const config: any = { include_docs: true };
|
const config: any = { include_docs: true };
|
||||||
if (key) {
|
if (key) {
|
||||||
if ("string" == typeof key) {
|
if ('string' == typeof key) {
|
||||||
config.startkey = this.getDocId(name, key);
|
config.startkey = this.getDocId(name, key);
|
||||||
config.endkey = config.startkey + "";
|
config.endkey = config.startkey + '';
|
||||||
} else {
|
} else {
|
||||||
if (!Array.isArray(key))
|
if (!Array.isArray(key))
|
||||||
return this.errorInfo(
|
return this.errorInfo(
|
||||||
"exception",
|
'exception',
|
||||||
"param only key(string) or keys(Array[string])"
|
'param only key(string) or keys(Array[string])'
|
||||||
);
|
);
|
||||||
config.keys = key.map((key) => this.getDocId(name, key));
|
config.keys = key.map((key) => this.getDocId(name, key));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
config.startkey = this.getDocId(name, "");
|
config.startkey = this.getDocId(name, '');
|
||||||
config.endkey = config.startkey + "";
|
config.endkey = config.startkey + '';
|
||||||
}
|
}
|
||||||
const result: Array<any> = [];
|
const result: Array<any> = [];
|
||||||
try {
|
try {
|
||||||
@ -172,4 +182,26 @@ export default class {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async dumpDb(config: {
|
||||||
|
url: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}): Promise<void> {
|
||||||
|
const webdavClient = new WebDavOP(config);
|
||||||
|
webdavClient.createWriteStream(this.pouchDB);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async importDb(config: {
|
||||||
|
url: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}): Promise<void> {
|
||||||
|
const webdavClient = new WebDavOP(config);
|
||||||
|
await this.pouchDB.destroy();
|
||||||
|
const syncDb = new DB(this.dbpath);
|
||||||
|
syncDb.init();
|
||||||
|
this.pouchDB = syncDb.pouchDB;
|
||||||
|
await webdavClient.createReadStream(this.pouchDB);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
92
src/core/db/webdav.ts
Normal file
92
src/core/db/webdav.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { Notification, app } from 'electron';
|
||||||
|
import MemoryStream from 'memorystream';
|
||||||
|
|
||||||
|
import { createClient } from 'webdav';
|
||||||
|
import { WebDAVClient } from 'webdav/dist/node/types';
|
||||||
|
|
||||||
|
type WebDavOptions = {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DBInstance = {
|
||||||
|
loadIt: (stream: unknown) => void;
|
||||||
|
dump: (stream: unknown) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class WebDav {
|
||||||
|
public client: WebDAVClient;
|
||||||
|
private cloudPath = '/rubick/db.txt';
|
||||||
|
|
||||||
|
constructor({ username, password, url }: WebDavOptions) {
|
||||||
|
this.client = createClient(url, {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
this.client
|
||||||
|
.exists('/')
|
||||||
|
.then((result) => {
|
||||||
|
!result &&
|
||||||
|
new Notification({
|
||||||
|
title: '导出失败',
|
||||||
|
body: 'webdav 连接失败',
|
||||||
|
}).show();
|
||||||
|
})
|
||||||
|
.catch((r) => {
|
||||||
|
new Notification({
|
||||||
|
title: '导出失败',
|
||||||
|
body: 'WebDav连接出错' + r,
|
||||||
|
}).show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async createWriteStream(dbInstance: DBInstance): Promise<void> {
|
||||||
|
try {
|
||||||
|
const result = await this.client.exists('/rubick');
|
||||||
|
if (!result) {
|
||||||
|
await this.client.createDirectory('/rubick');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
new Notification({
|
||||||
|
title: '导出失败',
|
||||||
|
body: 'WebDav目录创建出错:' + e,
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
const ws = new MemoryStream();
|
||||||
|
dbInstance.dump(ws);
|
||||||
|
ws.pipe(
|
||||||
|
this.client.createWriteStream(this.cloudPath, {}, () => {
|
||||||
|
new Notification({
|
||||||
|
title: '已导出到坚果云',
|
||||||
|
body: `文件目录为:${this.cloudPath}`,
|
||||||
|
}).show();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createReadStream(dbInstance: DBInstance): Promise<void> {
|
||||||
|
try {
|
||||||
|
const result = await this.client.exists(this.cloudPath);
|
||||||
|
if (!result) {
|
||||||
|
return new Notification({
|
||||||
|
title: '导入失败',
|
||||||
|
body: '请确认坚果云上已存在数据',
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
const str = await this.client.getFileContents(this.cloudPath, {
|
||||||
|
format: 'text',
|
||||||
|
});
|
||||||
|
await dbInstance.loadIt(str);
|
||||||
|
new Notification({
|
||||||
|
title: '导入成功',
|
||||||
|
body: '数据已导入到 rubick,主应用数据需重启后生效',
|
||||||
|
}).show();
|
||||||
|
} catch (e) {
|
||||||
|
new Notification({
|
||||||
|
title: '导入失败',
|
||||||
|
body: 'WebDav目录导入出错:' + e,
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,6 @@ const runnerInstance = runner();
|
|||||||
const detachInstance = detach();
|
const detachInstance = detach();
|
||||||
|
|
||||||
class API extends DBInstance {
|
class API extends DBInstance {
|
||||||
public currentPlugin: null | any = null;
|
|
||||||
init(mainWindow: BrowserWindow) {
|
init(mainWindow: BrowserWindow) {
|
||||||
// 响应 preload.js 事件
|
// 响应 preload.js 事件
|
||||||
ipcMain.on('msg-trigger', async (event, arg) => {
|
ipcMain.on('msg-trigger', async (event, arg) => {
|
||||||
|
@ -5,8 +5,29 @@ const dbInstance = new LocalDb(app.getPath('userData'));
|
|||||||
dbInstance.init();
|
dbInstance.init();
|
||||||
|
|
||||||
export default class DBInstance {
|
export default class DBInstance {
|
||||||
|
public currentPlugin: null | any = null;
|
||||||
private DBKEY = 'RUBICK_DB_DEFAULT';
|
private DBKEY = 'RUBICK_DB_DEFAULT';
|
||||||
public dbPut({ data }) {
|
private DB_INFO_KET = 'RUBICK_PLUGIN_INFO';
|
||||||
|
public async dbPut({ data }) {
|
||||||
|
// 记录插件有哪些 dbkey,用于后续的数据同步
|
||||||
|
if (this.currentPlugin && this.currentPlugin.name) {
|
||||||
|
let dbInfo: any = await dbInstance.get(this.DBKEY, this.DB_INFO_KET);
|
||||||
|
if (!dbInfo) {
|
||||||
|
dbInfo = { data: [], _id: this.DB_INFO_KET };
|
||||||
|
}
|
||||||
|
const item = dbInfo.data.find(
|
||||||
|
(it) => it.name === this.currentPlugin.name
|
||||||
|
);
|
||||||
|
if (item) {
|
||||||
|
!item.keys.includes(data.data._id) && item.keys.push(data.data._id);
|
||||||
|
} else {
|
||||||
|
dbInfo.data.push({
|
||||||
|
name: this.currentPlugin.name,
|
||||||
|
keys: [data.data._id],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
dbInstance.put(this.DBKEY, dbInfo);
|
||||||
|
}
|
||||||
return dbInstance.put(this.DBKEY, data.data);
|
return dbInstance.put(this.DBKEY, data.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,4 +46,12 @@ export default class DBInstance {
|
|||||||
public dbAllDocs({ data }) {
|
public dbAllDocs({ data }) {
|
||||||
return dbInstance.allDocs(this.DBKEY, data.key);
|
return dbInstance.allDocs(this.DBKEY, data.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public dbDump({ data }) {
|
||||||
|
return dbInstance.dumpDb(data.target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public dbImport({ data }) {
|
||||||
|
return dbInstance.importDb(data.target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ const createPluginManager = (): any => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (result && result.value) {
|
if (result && result.value) {
|
||||||
appList.value = [...appList.value, ...result.value];
|
appList.value.push(...result.value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user