mirror of
https://github.com/rubickCenter/rubick
synced 2025-10-27 07:01:26 +08:00
Merge branch 'feat/v3.0.0' of https://github.com/rubickCenter/rubick into feat/v3.0.0
This commit is contained in:
@@ -1,10 +1,20 @@
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import PouchDB from "pouchdb";
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
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 docAttachmentMaxByteLength;
|
||||
public dbpath;
|
||||
@@ -15,7 +25,7 @@ export default class {
|
||||
this.docMaxByteLength = 2 * 1024 * 1024; // 2M
|
||||
this.docAttachmentMaxByteLength = 20 * 1024 * 1024; // 20M
|
||||
this.dbpath = dbPath;
|
||||
this.defaultDbName = path.join(dbPath, "default");
|
||||
this.defaultDbName = path.join(dbPath, 'default');
|
||||
}
|
||||
|
||||
init(): void {
|
||||
@@ -24,11 +34,11 @@ export default class {
|
||||
}
|
||||
|
||||
getDocId(name: string, id: string): string {
|
||||
return name + "/" + id;
|
||||
return name + '/' + id;
|
||||
}
|
||||
|
||||
replaceDocId(name: string, id: string): string {
|
||||
return id.replace(name + "/", "");
|
||||
return id.replace(name + '/', '');
|
||||
}
|
||||
|
||||
errorInfo(name: string, message: string): DBError {
|
||||
@@ -38,7 +48,7 @@ export default class {
|
||||
private checkDocSize(doc: Doc<any>) {
|
||||
if (Buffer.byteLength(JSON.stringify(doc)) > this.docMaxByteLength) {
|
||||
return this.errorInfo(
|
||||
"exception",
|
||||
'exception',
|
||||
`doc max size ${this.docMaxByteLength / 1024 / 1024} M`
|
||||
);
|
||||
}
|
||||
@@ -78,15 +88,15 @@ export default class {
|
||||
async remove(name: string, doc: Doc<any> | string) {
|
||||
try {
|
||||
let target;
|
||||
if ("object" == typeof doc) {
|
||||
if ('object' == typeof doc) {
|
||||
target = doc;
|
||||
if (!target._id || "string" !== typeof target._id) {
|
||||
return this.errorInfo("exception", "doc _id error");
|
||||
if (!target._id || 'string' !== typeof target._id) {
|
||||
return this.errorInfo('exception', 'doc _id error');
|
||||
}
|
||||
target._id = this.getDocId(name, target._id);
|
||||
} else {
|
||||
if ("string" !== typeof doc) {
|
||||
return this.errorInfo("exception", "param error");
|
||||
if ('string' !== typeof doc) {
|
||||
return this.errorInfo('exception', 'param error');
|
||||
}
|
||||
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);
|
||||
return result;
|
||||
} catch (e: any) {
|
||||
if ("object" === typeof doc) {
|
||||
if ('object' === typeof doc) {
|
||||
doc._id = this.replaceDocId(name, doc._id);
|
||||
}
|
||||
return this.errorInfo(e.name, e.message);
|
||||
@@ -107,11 +117,11 @@ export default class {
|
||||
): Promise<DBError | Array<DocRes>> {
|
||||
let result;
|
||||
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))
|
||||
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)
|
||||
return this.errorInfo("exception", "_id value exists as");
|
||||
return this.errorInfo('exception', '_id value exists as');
|
||||
for (const doc of docs) {
|
||||
const err = this.checkDocSize(doc);
|
||||
if (err) return err;
|
||||
@@ -144,20 +154,20 @@ export default class {
|
||||
): Promise<DBError | Array<DocRes>> {
|
||||
const config: any = { include_docs: true };
|
||||
if (key) {
|
||||
if ("string" == typeof key) {
|
||||
if ('string' == typeof key) {
|
||||
config.startkey = this.getDocId(name, key);
|
||||
config.endkey = config.startkey + "";
|
||||
config.endkey = config.startkey + '';
|
||||
} else {
|
||||
if (!Array.isArray(key))
|
||||
return this.errorInfo(
|
||||
"exception",
|
||||
"param only key(string) or keys(Array[string])"
|
||||
'exception',
|
||||
'param only key(string) or keys(Array[string])'
|
||||
);
|
||||
config.keys = key.map((key) => this.getDocId(name, key));
|
||||
}
|
||||
} else {
|
||||
config.startkey = this.getDocId(name, "");
|
||||
config.endkey = config.startkey + "";
|
||||
config.startkey = this.getDocId(name, '');
|
||||
config.endkey = config.startkey + '';
|
||||
}
|
||||
const result: Array<any> = [];
|
||||
try {
|
||||
@@ -172,4 +182,26 @@ export default class {
|
||||
}
|
||||
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();
|
||||
|
||||
class API extends DBInstance {
|
||||
public currentPlugin: null | any = null;
|
||||
init(mainWindow: BrowserWindow) {
|
||||
// 响应 preload.js 事件
|
||||
ipcMain.on('msg-trigger', async (event, arg) => {
|
||||
|
||||
@@ -5,8 +5,29 @@ const dbInstance = new LocalDb(app.getPath('userData'));
|
||||
dbInstance.init();
|
||||
|
||||
export default class DBInstance {
|
||||
public currentPlugin: null | any = null;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -25,4 +46,12 @@ export default class DBInstance {
|
||||
public dbAllDocs({ data }) {
|
||||
return dbInstance.allDocs(this.DBKEY, data.key);
|
||||
}
|
||||
|
||||
public dbDump({ data }) {
|
||||
return dbInstance.dumpDb(data.target);
|
||||
}
|
||||
|
||||
public dbImport({ data }) {
|
||||
return dbInstance.importDb(data.target);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user