use localforage
This commit is contained in:
parent
be2b168c6c
commit
16c3c11fd8
@ -41,7 +41,7 @@
|
|||||||
"aws-crt": "^1.10.1",
|
"aws-crt": "^1.10.1",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"codemirror": "^5.63.1",
|
"codemirror": "^5.63.1",
|
||||||
"lovefield-ts": "^0.7.0",
|
"localforage": "^1.10.0",
|
||||||
"mime-types": "^2.1.33",
|
"mime-types": "^2.1.33",
|
||||||
"obsidian": "^0.12.0",
|
"obsidian": "^0.12.0",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
|
|||||||
@ -1,22 +1,14 @@
|
|||||||
import { TAbstractFile, TFolder, TFile, Vault } from "obsidian";
|
import { TAbstractFile, TFolder, TFile, Vault } from "obsidian";
|
||||||
|
|
||||||
import * as lf from "lovefield-ts/dist/es6/lf.js";
|
|
||||||
|
|
||||||
import type { SyncPlanType } from "./sync";
|
import type { SyncPlanType } from "./sync";
|
||||||
import {
|
import { readAllSyncPlanRecordTexts } from "./localdb";
|
||||||
insertSyncPlanRecord,
|
import type { InternalDBs } from "./localdb";
|
||||||
clearAllSyncPlanRecords,
|
|
||||||
readAllSyncPlanRecordTexts,
|
|
||||||
} from "./localdb";
|
|
||||||
import { mkdirpInVault } from "./misc";
|
import { mkdirpInVault } from "./misc";
|
||||||
|
|
||||||
const DEFAULT_DEBUG_FOLDER = "_debug_save_remote/";
|
const DEFAULT_DEBUG_FOLDER = "_debug_save_remote/";
|
||||||
const DEFAULT_SYNC_PLANS_HISTORY_FILE_PREFIX = "sync_plans_hist_exported_on_";
|
const DEFAULT_SYNC_PLANS_HISTORY_FILE_PREFIX = "sync_plans_hist_exported_on_";
|
||||||
|
|
||||||
export const exportSyncPlansToFiles = async (
|
export const exportSyncPlansToFiles = async (db: InternalDBs, vault: Vault) => {
|
||||||
db: lf.DatabaseConnection,
|
|
||||||
vault: Vault
|
|
||||||
) => {
|
|
||||||
console.log("exporting");
|
console.log("exporting");
|
||||||
await mkdirpInVault(DEFAULT_DEBUG_FOLDER, vault);
|
await mkdirpInVault(DEFAULT_DEBUG_FOLDER, vault);
|
||||||
const records = await readAllSyncPlanRecordTexts(db);
|
const records = await readAllSyncPlanRecordTexts(db);
|
||||||
|
|||||||
319
src/localdb.ts
319
src/localdb.ts
@ -1,12 +1,14 @@
|
|||||||
import * as lf from "lovefield-ts/dist/es6/lf.js";
|
import localforage from "localforage";
|
||||||
import { TAbstractFile, TFile, TFolder } from "obsidian";
|
import { TAbstractFile, TFile, TFolder } from "obsidian";
|
||||||
|
|
||||||
import type { SUPPORTED_SERVICES_TYPE } from "./misc";
|
import type { SUPPORTED_SERVICES_TYPE } from "./misc";
|
||||||
import type { SyncPlanType } from "./sync";
|
import type { SyncPlanType } from "./sync";
|
||||||
|
|
||||||
export type DatabaseConnection = lf.DatabaseConnection;
|
export type LocalForage = typeof localforage;
|
||||||
|
|
||||||
|
export const DEFAULT_DB_VERSION_NUMBER: number = 20211114;
|
||||||
export const DEFAULT_DB_NAME = "saveremotedb";
|
export const DEFAULT_DB_NAME = "saveremotedb";
|
||||||
|
export const DEFAULT_TBL_VERSION = "schemaversion";
|
||||||
export const DEFAULT_TBL_DELETE_HISTORY = "filefolderoperationhistory";
|
export const DEFAULT_TBL_DELETE_HISTORY = "filefolderoperationhistory";
|
||||||
export const DEFAULT_TBL_SYNC_MAPPING = "syncmetadatahistory";
|
export const DEFAULT_TBL_SYNC_MAPPING = "syncmetadatahistory";
|
||||||
export const DEFAULT_SYNC_PLANS_HISTORY = "syncplanshistory";
|
export const DEFAULT_SYNC_PLANS_HISTORY = "syncplanshistory";
|
||||||
@ -16,128 +18,105 @@ export interface FileFolderHistoryRecord {
|
|||||||
ctime: number;
|
ctime: number;
|
||||||
mtime: number;
|
mtime: number;
|
||||||
size: number;
|
size: number;
|
||||||
action_when: number;
|
actionWhen: number;
|
||||||
action_type: "delete" | "rename";
|
actionType: "delete" | "rename";
|
||||||
key_type: "folder" | "file";
|
keyType: "folder" | "file";
|
||||||
rename_to: string;
|
renameTo: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SyncMetaMappingRecord {
|
interface SyncMetaMappingRecord {
|
||||||
local_key: string;
|
localKey: string;
|
||||||
remote_key: string;
|
remoteKey: string;
|
||||||
local_size: number;
|
localSize: number;
|
||||||
remote_size: number;
|
remoteSize: number;
|
||||||
local_mtime: number;
|
localMtime: number;
|
||||||
remote_mtime: number;
|
remoteMtime: number;
|
||||||
remote_extra_key: string;
|
remoteExtraKey: string;
|
||||||
remote_type: SUPPORTED_SERVICES_TYPE;
|
remoteType: SUPPORTED_SERVICES_TYPE;
|
||||||
key_type: "folder" | "file";
|
keyType: "folder" | "file";
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SyncPlanRecord {
|
interface SyncPlanRecord {
|
||||||
ts: number;
|
ts: number;
|
||||||
remote_type: string;
|
remoteType: string;
|
||||||
sync_plan: string;
|
syncPlan: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InternalDBs {
|
||||||
|
versionTbl: LocalForage;
|
||||||
|
deleteHistoryTbl: LocalForage;
|
||||||
|
syncMappingTbl: LocalForage;
|
||||||
|
syncPlansTbl: LocalForage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const prepareDBs = async () => {
|
export const prepareDBs = async () => {
|
||||||
const schemaBuilder = lf.schema.create(DEFAULT_DB_NAME, 1);
|
const db = {
|
||||||
schemaBuilder
|
versionTbl: localforage.createInstance({
|
||||||
.createTable(DEFAULT_TBL_DELETE_HISTORY)
|
name: DEFAULT_DB_NAME,
|
||||||
.addColumn("id", lf.Type.INTEGER)
|
storeName: DEFAULT_TBL_VERSION,
|
||||||
.addColumn("key", lf.Type.STRING)
|
}),
|
||||||
.addColumn("ctime", lf.Type.INTEGER)
|
deleteHistoryTbl: localforage.createInstance({
|
||||||
.addColumn("mtime", lf.Type.INTEGER)
|
name: DEFAULT_DB_NAME,
|
||||||
.addColumn("size", lf.Type.INTEGER)
|
storeName: DEFAULT_TBL_DELETE_HISTORY,
|
||||||
.addColumn("action_when", lf.Type.INTEGER)
|
}),
|
||||||
.addColumn("action_type", lf.Type.STRING)
|
syncMappingTbl: localforage.createInstance({
|
||||||
.addColumn("key_type", lf.Type.STRING)
|
name: DEFAULT_DB_NAME,
|
||||||
.addPrimaryKey(["id"], true)
|
storeName: DEFAULT_TBL_SYNC_MAPPING,
|
||||||
.addIndex("idxKey", ["key"]);
|
}),
|
||||||
|
syncPlansTbl: localforage.createInstance({
|
||||||
|
name: DEFAULT_DB_NAME,
|
||||||
|
storeName: DEFAULT_SYNC_PLANS_HISTORY,
|
||||||
|
}),
|
||||||
|
} as InternalDBs;
|
||||||
|
|
||||||
schemaBuilder
|
const originalVersion = (await db.versionTbl.getItem("version")) as number;
|
||||||
.createTable(DEFAULT_TBL_SYNC_MAPPING)
|
if (originalVersion === null) {
|
||||||
.addColumn("id", lf.Type.INTEGER)
|
await db.versionTbl.setItem("version", DEFAULT_DB_VERSION_NUMBER);
|
||||||
.addColumn("local_key", lf.Type.STRING)
|
} else if (originalVersion === DEFAULT_DB_VERSION_NUMBER) {
|
||||||
.addColumn("remote_key", lf.Type.STRING)
|
// do nothing
|
||||||
.addColumn("local_size", lf.Type.INTEGER)
|
} else {
|
||||||
.addColumn("remote_size", lf.Type.INTEGER)
|
await migrateDBs(db, originalVersion, DEFAULT_DB_VERSION_NUMBER);
|
||||||
.addColumn("local_mtime", lf.Type.INTEGER)
|
}
|
||||||
.addColumn("remote_mtime", lf.Type.INTEGER)
|
|
||||||
.addColumn("key_type", lf.Type.STRING)
|
|
||||||
.addColumn("remote_extra_key", lf.Type.STRING)
|
|
||||||
.addColumn("remote_type", lf.Type.STRING)
|
|
||||||
.addNullable([
|
|
||||||
"remote_extra_key",
|
|
||||||
"remote_mtime",
|
|
||||||
"remote_size",
|
|
||||||
"local_mtime",
|
|
||||||
])
|
|
||||||
.addPrimaryKey(["id"], true)
|
|
||||||
.addIndex("idxkey", ["local_key", "remote_key"]);
|
|
||||||
|
|
||||||
schemaBuilder
|
|
||||||
.createTable(DEFAULT_SYNC_PLANS_HISTORY)
|
|
||||||
.addColumn("id", lf.Type.INTEGER)
|
|
||||||
.addColumn("ts", lf.Type.INTEGER)
|
|
||||||
.addColumn("remote_type", lf.Type.STRING)
|
|
||||||
.addColumn("sync_plan", lf.Type.STRING)
|
|
||||||
.addPrimaryKey(["id"], true)
|
|
||||||
.addIndex("tskey", ["ts"]);
|
|
||||||
|
|
||||||
const db = await schemaBuilder.connect({
|
|
||||||
storeType: lf.DataStoreType.INDEXED_DB,
|
|
||||||
});
|
|
||||||
console.log("db connected");
|
console.log("db connected");
|
||||||
return db;
|
return db;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const destroyDBs = async (db: lf.DatabaseConnection) => {
|
export const destroyDBs = async () => {
|
||||||
db.close();
|
await localforage.dropInstance({
|
||||||
const req = indexedDB.deleteDatabase(DEFAULT_DB_NAME);
|
name: DEFAULT_DB_NAME,
|
||||||
req.onsuccess = (event) => {
|
});
|
||||||
console.log("db deleted");
|
console.log("db deleted");
|
||||||
};
|
|
||||||
req.onblocked = (event) => {
|
|
||||||
console.warn("trying to delete db but it was blocked");
|
|
||||||
};
|
|
||||||
req.onerror = (event) => {
|
|
||||||
console.error("tried to delete db but something bad!");
|
|
||||||
console.error(event);
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadDeleteRenameHistoryTable = async (
|
const migrateDBs = async (db: InternalDBs, oldVer: number, newVer: number) => {
|
||||||
db: lf.DatabaseConnection
|
if (oldVer === newVer) {
|
||||||
) => {
|
return;
|
||||||
const schema = db.getSchema().table(DEFAULT_TBL_DELETE_HISTORY);
|
}
|
||||||
const tbl = db.getSchema().table(DEFAULT_TBL_DELETE_HISTORY);
|
// not implemented
|
||||||
|
throw Error(`not supported internal db changes from ${oldVer} to ${newVer}`);
|
||||||
|
};
|
||||||
|
|
||||||
const records = await db
|
export const loadDeleteRenameHistoryTable = async (db: InternalDBs) => {
|
||||||
.select()
|
const records = [] as FileFolderHistoryRecord[];
|
||||||
.from(schema)
|
await db.deleteHistoryTbl.iterate((value, key, iterationNumber) => {
|
||||||
.orderBy(schema.col("action_when"), lf.Order.ASC)
|
records.push(value as FileFolderHistoryRecord);
|
||||||
.exec();
|
});
|
||||||
|
records.sort((a, b) => a.actionWhen - b.actionWhen); // ascending
|
||||||
return records as FileFolderHistoryRecord[];
|
return records;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clearDeleteRenameHistoryOfKey = async (
|
export const clearDeleteRenameHistoryOfKey = async (
|
||||||
db: lf.DatabaseConnection,
|
db: InternalDBs,
|
||||||
key: string
|
key: string
|
||||||
) => {
|
) => {
|
||||||
const schema = db.getSchema().table(DEFAULT_TBL_DELETE_HISTORY);
|
await db.deleteHistoryTbl.removeItem(key);
|
||||||
const tbl = db.getSchema().table(DEFAULT_TBL_DELETE_HISTORY);
|
|
||||||
|
|
||||||
await db.delete().from(tbl).where(tbl.col("key").eq(key)).exec();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const insertDeleteRecord = async (
|
export const insertDeleteRecord = async (
|
||||||
db: lf.DatabaseConnection,
|
db: InternalDBs,
|
||||||
fileOrFolder: TAbstractFile
|
fileOrFolder: TAbstractFile
|
||||||
) => {
|
) => {
|
||||||
const schema = db.getSchema().table(DEFAULT_TBL_DELETE_HISTORY);
|
|
||||||
const tbl = db.getSchema().table(DEFAULT_TBL_DELETE_HISTORY);
|
|
||||||
// console.log(fileOrFolder);
|
// console.log(fileOrFolder);
|
||||||
let k: FileFolderHistoryRecord;
|
let k: FileFolderHistoryRecord;
|
||||||
if (fileOrFolder instanceof TFile) {
|
if (fileOrFolder instanceof TFile) {
|
||||||
@ -146,10 +125,10 @@ export const insertDeleteRecord = async (
|
|||||||
ctime: fileOrFolder.stat.ctime,
|
ctime: fileOrFolder.stat.ctime,
|
||||||
mtime: fileOrFolder.stat.mtime,
|
mtime: fileOrFolder.stat.mtime,
|
||||||
size: fileOrFolder.stat.size,
|
size: fileOrFolder.stat.size,
|
||||||
action_when: Date.now(),
|
actionWhen: Date.now(),
|
||||||
action_type: "delete",
|
actionType: "delete",
|
||||||
key_type: "file",
|
keyType: "file",
|
||||||
rename_to: "",
|
renameTo: "",
|
||||||
};
|
};
|
||||||
} else if (fileOrFolder instanceof TFolder) {
|
} else if (fileOrFolder instanceof TFolder) {
|
||||||
// key should endswith "/"
|
// key should endswith "/"
|
||||||
@ -161,23 +140,20 @@ export const insertDeleteRecord = async (
|
|||||||
ctime: 0,
|
ctime: 0,
|
||||||
mtime: 0,
|
mtime: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
action_when: Date.now(),
|
actionWhen: Date.now(),
|
||||||
action_type: "delete",
|
actionType: "delete",
|
||||||
key_type: "folder",
|
keyType: "folder",
|
||||||
rename_to: "",
|
renameTo: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const row = tbl.createRow(k);
|
await db.deleteHistoryTbl.setItem(k.key, k);
|
||||||
await db.insertOrReplace().into(tbl).values([row]).exec();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const insertRenameRecord = async (
|
export const insertRenameRecord = async (
|
||||||
db: lf.DatabaseConnection,
|
db: InternalDBs,
|
||||||
fileOrFolder: TAbstractFile,
|
fileOrFolder: TAbstractFile,
|
||||||
oldPath: string
|
oldPath: string
|
||||||
) => {
|
) => {
|
||||||
const schema = db.getSchema().table(DEFAULT_TBL_DELETE_HISTORY);
|
|
||||||
const tbl = db.getSchema().table(DEFAULT_TBL_DELETE_HISTORY);
|
|
||||||
// console.log(fileOrFolder);
|
// console.log(fileOrFolder);
|
||||||
let k: FileFolderHistoryRecord;
|
let k: FileFolderHistoryRecord;
|
||||||
if (fileOrFolder instanceof TFile) {
|
if (fileOrFolder instanceof TFile) {
|
||||||
@ -186,10 +162,10 @@ export const insertRenameRecord = async (
|
|||||||
ctime: fileOrFolder.stat.ctime,
|
ctime: fileOrFolder.stat.ctime,
|
||||||
mtime: fileOrFolder.stat.mtime,
|
mtime: fileOrFolder.stat.mtime,
|
||||||
size: fileOrFolder.stat.size,
|
size: fileOrFolder.stat.size,
|
||||||
action_when: Date.now(),
|
actionWhen: Date.now(),
|
||||||
action_type: "rename",
|
actionType: "rename",
|
||||||
key_type: "file",
|
keyType: "file",
|
||||||
rename_to: fileOrFolder.path,
|
renameTo: fileOrFolder.path,
|
||||||
};
|
};
|
||||||
} else if (fileOrFolder instanceof TFolder) {
|
} else if (fileOrFolder instanceof TFolder) {
|
||||||
const key = oldPath.endsWith("/") ? oldPath : `${oldPath}/`;
|
const key = oldPath.endsWith("/") ? oldPath : `${oldPath}/`;
|
||||||
@ -201,25 +177,17 @@ export const insertRenameRecord = async (
|
|||||||
ctime: 0,
|
ctime: 0,
|
||||||
mtime: 0,
|
mtime: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
action_when: Date.now(),
|
actionWhen: Date.now(),
|
||||||
action_type: "rename",
|
actionType: "rename",
|
||||||
key_type: "folder",
|
keyType: "folder",
|
||||||
rename_to: renameTo,
|
renameTo: renameTo,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const row = tbl.createRow(k);
|
await db.deleteHistoryTbl.setItem(k.key, k);
|
||||||
await db.insertOrReplace().into(tbl).values([row]).exec();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAllDeleteRenameRecords = async (db: lf.DatabaseConnection) => {
|
|
||||||
const schema = db.getSchema().table(DEFAULT_TBL_DELETE_HISTORY);
|
|
||||||
const res1 = await db.select().from(schema).exec();
|
|
||||||
const res2 = res1 as FileFolderHistoryRecord[];
|
|
||||||
return res2;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const upsertSyncMetaMappingDataS3 = async (
|
export const upsertSyncMetaMappingDataS3 = async (
|
||||||
db: lf.DatabaseConnection,
|
db: InternalDBs,
|
||||||
localKey: string,
|
localKey: string,
|
||||||
localMTime: number,
|
localMTime: number,
|
||||||
localSize: number,
|
localSize: number,
|
||||||
@ -228,89 +196,78 @@ export const upsertSyncMetaMappingDataS3 = async (
|
|||||||
remoteSize: number,
|
remoteSize: number,
|
||||||
remoteExtraKey: string /* ETag from s3 */
|
remoteExtraKey: string /* ETag from s3 */
|
||||||
) => {
|
) => {
|
||||||
const schema = db.getSchema().table(DEFAULT_TBL_SYNC_MAPPING);
|
|
||||||
const aggregratedInfo: SyncMetaMappingRecord = {
|
const aggregratedInfo: SyncMetaMappingRecord = {
|
||||||
local_key: localKey,
|
localKey: localKey,
|
||||||
local_mtime: localMTime,
|
localMtime: localMTime,
|
||||||
local_size: localSize,
|
localSize: localSize,
|
||||||
remote_key: remoteKey,
|
remoteKey: remoteKey,
|
||||||
remote_mtime: remoteMTime,
|
remoteMtime: remoteMTime,
|
||||||
remote_size: remoteSize,
|
remoteSize: remoteSize,
|
||||||
remote_extra_key: remoteExtraKey,
|
remoteExtraKey: remoteExtraKey,
|
||||||
remote_type: "s3",
|
remoteType: "s3",
|
||||||
key_type: localKey.endsWith("/") ? "folder" : "file",
|
keyType: localKey.endsWith("/") ? "folder" : "file",
|
||||||
};
|
};
|
||||||
const row = schema.createRow(aggregratedInfo);
|
await db.syncMappingTbl.setItem(remoteKey, aggregratedInfo);
|
||||||
await db.insertOrReplace().into(schema).values([row]).exec();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSyncMetaMappingByRemoteKeyS3 = async (
|
export const getSyncMetaMappingByRemoteKeyS3 = async (
|
||||||
db: lf.DatabaseConnection,
|
db: InternalDBs,
|
||||||
remoteKey: string,
|
remoteKey: string,
|
||||||
remoteMTime: number,
|
remoteMTime: number,
|
||||||
remoteExtraKey: string
|
remoteExtraKey: string
|
||||||
) => {
|
) => {
|
||||||
const schema = db.getSchema().table(DEFAULT_TBL_SYNC_MAPPING);
|
const potentialItem = (await db.syncMappingTbl.getItem(
|
||||||
const tbl = db.getSchema().table(DEFAULT_TBL_SYNC_MAPPING);
|
remoteKey
|
||||||
const res = (await db
|
)) as SyncMetaMappingRecord;
|
||||||
.select()
|
|
||||||
.from(tbl)
|
|
||||||
.where(
|
|
||||||
lf.op.and(
|
|
||||||
tbl.col("remote_key").eq(remoteKey),
|
|
||||||
tbl.col("remote_mtime").eq(remoteMTime),
|
|
||||||
tbl.col("remote_extra_key").eq(remoteExtraKey),
|
|
||||||
tbl.col("remote_type").eq("s3")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.exec()) as SyncMetaMappingRecord[];
|
|
||||||
|
|
||||||
if (res.length === 1) {
|
if (potentialItem === null) {
|
||||||
return res[0];
|
// no result was found
|
||||||
}
|
|
||||||
|
|
||||||
if (res.length === 0) {
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw Error("something bad in sync meta mapping!");
|
if (
|
||||||
|
potentialItem.remoteKey === remoteKey &&
|
||||||
|
potentialItem.remoteMtime === remoteMTime &&
|
||||||
|
potentialItem.remoteExtraKey === remoteExtraKey &&
|
||||||
|
potentialItem.remoteType === "s3"
|
||||||
|
) {
|
||||||
|
// the result was found
|
||||||
|
return potentialItem;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clearAllSyncMetaMapping = async (db: lf.DatabaseConnection) => {
|
export const clearAllSyncMetaMapping = async (db: InternalDBs) => {
|
||||||
const tbl = db.getSchema().table(DEFAULT_TBL_SYNC_MAPPING);
|
await db.syncMappingTbl.clear();
|
||||||
await db.delete().from(tbl).exec();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const insertSyncPlanRecord = async (
|
export const insertSyncPlanRecord = async (
|
||||||
db: lf.DatabaseConnection,
|
db: InternalDBs,
|
||||||
syncPlan: SyncPlanType
|
syncPlan: SyncPlanType
|
||||||
) => {
|
) => {
|
||||||
const schema = db.getSchema().table(DEFAULT_SYNC_PLANS_HISTORY);
|
const record = {
|
||||||
const row = schema.createRow({
|
|
||||||
ts: syncPlan.ts,
|
ts: syncPlan.ts,
|
||||||
remote_type: syncPlan.remoteType,
|
remoteType: syncPlan.remoteType,
|
||||||
sync_plan: JSON.stringify(syncPlan, null, 2),
|
syncPlan: JSON.stringify(syncPlan /* directly stringify */, null, 2),
|
||||||
} as SyncPlanRecord);
|
} as SyncPlanRecord;
|
||||||
await db.insertOrReplace().into(schema).values([row]).exec();
|
await db.syncPlansTbl.setItem(`${syncPlan.ts}`, record);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clearAllSyncPlanRecords = async (db: lf.DatabaseConnection) => {
|
export const clearAllSyncPlanRecords = async (db: InternalDBs) => {
|
||||||
const tbl = db.getSchema().table(DEFAULT_SYNC_PLANS_HISTORY);
|
await db.syncPlansTbl.clear();
|
||||||
await db.delete().from(tbl).exec();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const readAllSyncPlanRecordTexts = async (db: lf.DatabaseConnection) => {
|
export const readAllSyncPlanRecordTexts = async (db: InternalDBs) => {
|
||||||
const schema = db.getSchema().table(DEFAULT_SYNC_PLANS_HISTORY);
|
const records = [] as SyncPlanRecord[];
|
||||||
|
await db.syncPlansTbl.iterate((value, key, iterationNumber) => {
|
||||||
const records = (await db
|
records.push(value as SyncPlanRecord);
|
||||||
.select()
|
});
|
||||||
.from(schema)
|
records.sort((a, b) => -(a.ts - b.ts)); // descending
|
||||||
.orderBy(schema.col("ts"), lf.Order.DESC)
|
|
||||||
.exec()) as SyncPlanRecord[];
|
|
||||||
|
|
||||||
if (records === undefined) {
|
if (records === undefined) {
|
||||||
return [] as string[];
|
return [] as string[];
|
||||||
} else {
|
} else {
|
||||||
return records.map((x) => x.sync_plan);
|
return records.map((x) => x.syncPlan);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
11
src/main.ts
11
src/main.ts
@ -11,20 +11,17 @@ import {
|
|||||||
TFolder,
|
TFolder,
|
||||||
} from "obsidian";
|
} from "obsidian";
|
||||||
import * as CodeMirror from "codemirror";
|
import * as CodeMirror from "codemirror";
|
||||||
import {
|
|
||||||
clearAllSyncPlanRecords,
|
|
||||||
clearAllSyncMetaMapping,
|
|
||||||
DatabaseConnection,
|
|
||||||
} from "./localdb";
|
|
||||||
import {
|
import {
|
||||||
prepareDBs,
|
prepareDBs,
|
||||||
destroyDBs,
|
destroyDBs,
|
||||||
loadDeleteRenameHistoryTable,
|
loadDeleteRenameHistoryTable,
|
||||||
|
clearAllSyncPlanRecords,
|
||||||
|
clearAllSyncMetaMapping,
|
||||||
insertDeleteRecord,
|
insertDeleteRecord,
|
||||||
insertRenameRecord,
|
insertRenameRecord,
|
||||||
getAllDeleteRenameRecords,
|
|
||||||
insertSyncPlanRecord,
|
insertSyncPlanRecord,
|
||||||
} from "./localdb";
|
} from "./localdb";
|
||||||
|
import type { InternalDBs } from "./localdb";
|
||||||
|
|
||||||
import type { SyncStatusType, PasswordCheckType } from "./sync";
|
import type { SyncStatusType, PasswordCheckType } from "./sync";
|
||||||
import { isPasswordOk, getSyncPlan, doActualSync } from "./sync";
|
import { isPasswordOk, getSyncPlan, doActualSync } from "./sync";
|
||||||
@ -50,7 +47,7 @@ const DEFAULT_SETTINGS: SaveRemotePluginSettings = {
|
|||||||
export default class SaveRemotePlugin extends Plugin {
|
export default class SaveRemotePlugin extends Plugin {
|
||||||
settings: SaveRemotePluginSettings;
|
settings: SaveRemotePluginSettings;
|
||||||
cm: CodeMirror.Editor;
|
cm: CodeMirror.Editor;
|
||||||
db: DatabaseConnection;
|
db: InternalDBs;
|
||||||
syncStatus: SyncStatusType;
|
syncStatus: SyncStatusType;
|
||||||
|
|
||||||
async onload() {
|
async onload() {
|
||||||
|
|||||||
22
src/sync.ts
22
src/sync.ts
@ -1,14 +1,14 @@
|
|||||||
import { TAbstractFile, TFolder, TFile, Vault } from "obsidian";
|
import { TAbstractFile, TFolder, TFile, Vault } from "obsidian";
|
||||||
|
|
||||||
import { S3Client } from "@aws-sdk/client-s3";
|
import { S3Client } from "@aws-sdk/client-s3";
|
||||||
import * as lf from "lovefield-ts/dist/es6/lf.js";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
clearDeleteRenameHistoryOfKey,
|
clearDeleteRenameHistoryOfKey,
|
||||||
FileFolderHistoryRecord,
|
|
||||||
upsertSyncMetaMappingDataS3,
|
upsertSyncMetaMappingDataS3,
|
||||||
getSyncMetaMappingByRemoteKeyS3,
|
getSyncMetaMappingByRemoteKeyS3,
|
||||||
} from "./localdb";
|
} from "./localdb";
|
||||||
|
import type { FileFolderHistoryRecord, InternalDBs } from "./localdb";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
S3Config,
|
S3Config,
|
||||||
S3ObjectType,
|
S3ObjectType,
|
||||||
@ -146,7 +146,7 @@ const ensembleMixedStates = async (
|
|||||||
remote: S3ObjectType[],
|
remote: S3ObjectType[],
|
||||||
local: TAbstractFile[],
|
local: TAbstractFile[],
|
||||||
deleteHistory: FileFolderHistoryRecord[],
|
deleteHistory: FileFolderHistoryRecord[],
|
||||||
db: lf.DatabaseConnection,
|
db: InternalDBs,
|
||||||
password: string = ""
|
password: string = ""
|
||||||
) => {
|
) => {
|
||||||
const results = {} as Record<string, FileOrFolderMixedState>;
|
const results = {} as Record<string, FileOrFolderMixedState>;
|
||||||
@ -167,12 +167,12 @@ const ensembleMixedStates = async (
|
|||||||
|
|
||||||
let r = {} as FileOrFolderMixedState;
|
let r = {} as FileOrFolderMixedState;
|
||||||
if (backwardMapping !== undefined) {
|
if (backwardMapping !== undefined) {
|
||||||
key = backwardMapping.local_key;
|
key = backwardMapping.localKey;
|
||||||
r = {
|
r = {
|
||||||
key: key,
|
key: key,
|
||||||
exist_remote: true,
|
exist_remote: true,
|
||||||
mtime_remote: backwardMapping.local_mtime,
|
mtime_remote: backwardMapping.localMtime,
|
||||||
size_remote: backwardMapping.local_size,
|
size_remote: backwardMapping.localSize,
|
||||||
remote_encrypted_key: remoteEncryptedKey,
|
remote_encrypted_key: remoteEncryptedKey,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@ -240,11 +240,11 @@ const ensembleMixedStates = async (
|
|||||||
|
|
||||||
for (const entry of deleteHistory) {
|
for (const entry of deleteHistory) {
|
||||||
let key = entry.key;
|
let key = entry.key;
|
||||||
if (entry.key_type === "folder") {
|
if (entry.keyType === "folder") {
|
||||||
if (!entry.key.endsWith("/")) {
|
if (!entry.key.endsWith("/")) {
|
||||||
key = `${entry.key}/`;
|
key = `${entry.key}/`;
|
||||||
}
|
}
|
||||||
} else if (entry.key_type === "file") {
|
} else if (entry.keyType === "file") {
|
||||||
// pass
|
// pass
|
||||||
} else {
|
} else {
|
||||||
throw Error(`unexpected ${entry}`);
|
throw Error(`unexpected ${entry}`);
|
||||||
@ -252,7 +252,7 @@ const ensembleMixedStates = async (
|
|||||||
|
|
||||||
const r = {
|
const r = {
|
||||||
key: key,
|
key: key,
|
||||||
delete_time_local: entry.action_when,
|
delete_time_local: entry.actionWhen,
|
||||||
} as FileOrFolderMixedState;
|
} as FileOrFolderMixedState;
|
||||||
|
|
||||||
if (isHiddenPath(key)) {
|
if (isHiddenPath(key)) {
|
||||||
@ -405,7 +405,7 @@ export const getSyncPlan = async (
|
|||||||
remote: S3ObjectType[],
|
remote: S3ObjectType[],
|
||||||
local: TAbstractFile[],
|
local: TAbstractFile[],
|
||||||
deleteHistory: FileFolderHistoryRecord[],
|
deleteHistory: FileFolderHistoryRecord[],
|
||||||
db: lf.DatabaseConnection,
|
db: InternalDBs,
|
||||||
password: string = ""
|
password: string = ""
|
||||||
) => {
|
) => {
|
||||||
const mixedStates = await ensembleMixedStates(
|
const mixedStates = await ensembleMixedStates(
|
||||||
@ -429,7 +429,7 @@ export const getSyncPlan = async (
|
|||||||
export const doActualSync = async (
|
export const doActualSync = async (
|
||||||
s3Client: S3Client,
|
s3Client: S3Client,
|
||||||
s3Config: S3Config,
|
s3Config: S3Config,
|
||||||
db: lf.DatabaseConnection,
|
db: InternalDBs,
|
||||||
vault: Vault,
|
vault: Vault,
|
||||||
syncPlan: SyncPlanType,
|
syncPlan: SyncPlanType,
|
||||||
password: string = ""
|
password: string = ""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user