debug mode: export sync plans
This commit is contained in:
parent
b9c17076b4
commit
62aea9d330
37
src/debugMode.ts
Normal file
37
src/debugMode.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { TAbstractFile, TFolder, TFile, Vault } from "obsidian";
|
||||||
|
|
||||||
|
import * as lf from "lovefield-ts/dist/es6/lf.js";
|
||||||
|
|
||||||
|
import type { SyncPlanType } from "./sync";
|
||||||
|
import {
|
||||||
|
insertSyncPlanRecord,
|
||||||
|
clearAllSyncPlanRecords,
|
||||||
|
readAllSyncPlanRecordTexts,
|
||||||
|
} from "./localdb";
|
||||||
|
import { mkdirpInVault } from "./misc";
|
||||||
|
|
||||||
|
const DEFAULT_DEBUG_FOLDER = "_debug_save_remote/";
|
||||||
|
const DEFAULT_SYNC_PLANS_HISTORY_FILE_PREFIX = "sync_plans_hist_exported_on_";
|
||||||
|
|
||||||
|
export const exportSyncPlansToFiles = async (
|
||||||
|
db: lf.DatabaseConnection,
|
||||||
|
vault: Vault
|
||||||
|
) => {
|
||||||
|
console.log("exporting");
|
||||||
|
await mkdirpInVault(DEFAULT_DEBUG_FOLDER, vault);
|
||||||
|
const records = await readAllSyncPlanRecordTexts(db);
|
||||||
|
let md = "";
|
||||||
|
if (records.length === 0) {
|
||||||
|
md = "No sync plans history found";
|
||||||
|
} else {
|
||||||
|
md =
|
||||||
|
"Sync plans found:\n\n" +
|
||||||
|
records.map((x) => "```json\n" + x + "\n```\n").join("\n");
|
||||||
|
}
|
||||||
|
const ts = Date.now();
|
||||||
|
const filePath = `${DEFAULT_DEBUG_FOLDER}${DEFAULT_SYNC_PLANS_HISTORY_FILE_PREFIX}${ts}.md`;
|
||||||
|
await vault.create(filePath, md, {
|
||||||
|
mtime: ts,
|
||||||
|
});
|
||||||
|
console.log("finish exporting");
|
||||||
|
};
|
||||||
@ -2,12 +2,14 @@ import * as lf from "lovefield-ts/dist/es6/lf.js";
|
|||||||
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";
|
||||||
|
|
||||||
export type DatabaseConnection = lf.DatabaseConnection;
|
export type DatabaseConnection = lf.DatabaseConnection;
|
||||||
|
|
||||||
export const DEFAULT_DB_NAME = "saveremotedb";
|
export const DEFAULT_DB_NAME = "saveremotedb";
|
||||||
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 interface FileFolderHistoryRecord {
|
export interface FileFolderHistoryRecord {
|
||||||
key: string;
|
key: string;
|
||||||
@ -32,6 +34,12 @@ export interface SyncMetaMappingRecord {
|
|||||||
key_type: "folder" | "file";
|
key_type: "folder" | "file";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SyncPlanRecord {
|
||||||
|
ts: number;
|
||||||
|
remote_type: string;
|
||||||
|
sync_plan: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const prepareDBs = async () => {
|
export const prepareDBs = async () => {
|
||||||
const schemaBuilder = lf.schema.create(DEFAULT_DB_NAME, 1);
|
const schemaBuilder = lf.schema.create(DEFAULT_DB_NAME, 1);
|
||||||
schemaBuilder
|
schemaBuilder
|
||||||
@ -68,6 +76,15 @@ export const prepareDBs = async () => {
|
|||||||
.addPrimaryKey(["id"], true)
|
.addPrimaryKey(["id"], true)
|
||||||
.addIndex("idxkey", ["local_key", "remote_key"]);
|
.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({
|
const db = await schemaBuilder.connect({
|
||||||
storeType: lf.DataStoreType.INDEXED_DB,
|
storeType: lf.DataStoreType.INDEXED_DB,
|
||||||
});
|
});
|
||||||
@ -258,3 +275,37 @@ export const getSyncMetaMappingByRemoteKeyS3 = async (
|
|||||||
|
|
||||||
throw Error("something bad in sync meta mapping!");
|
throw Error("something bad in sync meta mapping!");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const insertSyncPlanRecord = async (
|
||||||
|
db: lf.DatabaseConnection,
|
||||||
|
syncPlan: SyncPlanType
|
||||||
|
) => {
|
||||||
|
const schema = db.getSchema().table(DEFAULT_SYNC_PLANS_HISTORY);
|
||||||
|
const row = schema.createRow({
|
||||||
|
ts: syncPlan.ts,
|
||||||
|
remote_type: syncPlan.remoteType,
|
||||||
|
sync_plan: JSON.stringify(syncPlan, null, 2),
|
||||||
|
} as SyncPlanRecord);
|
||||||
|
await db.insertOrReplace().into(schema).values([row]).exec();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clearAllSyncPlanRecords = async (db: lf.DatabaseConnection) => {
|
||||||
|
const tbl = db.getSchema().table(DEFAULT_SYNC_PLANS_HISTORY);
|
||||||
|
await db.delete().from(tbl).exec();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const readAllSyncPlanRecordTexts = async (db: lf.DatabaseConnection) => {
|
||||||
|
const schema = db.getSchema().table(DEFAULT_SYNC_PLANS_HISTORY);
|
||||||
|
|
||||||
|
const records = (await db
|
||||||
|
.select()
|
||||||
|
.from(schema)
|
||||||
|
.orderBy(schema.col("ts"), lf.Order.DESC)
|
||||||
|
.exec()) as SyncPlanRecord[];
|
||||||
|
|
||||||
|
if (records === undefined) {
|
||||||
|
return [] as string[];
|
||||||
|
} else {
|
||||||
|
return records.map((x) => x.sync_plan);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
27
src/main.ts
27
src/main.ts
@ -11,7 +11,7 @@ import {
|
|||||||
TFolder,
|
TFolder,
|
||||||
} from "obsidian";
|
} from "obsidian";
|
||||||
import * as CodeMirror from "codemirror";
|
import * as CodeMirror from "codemirror";
|
||||||
import type { DatabaseConnection } from "./localdb";
|
import { clearAllSyncPlanRecords, DatabaseConnection } from "./localdb";
|
||||||
import {
|
import {
|
||||||
prepareDBs,
|
prepareDBs,
|
||||||
destroyDBs,
|
destroyDBs,
|
||||||
@ -19,11 +19,13 @@ import {
|
|||||||
insertDeleteRecord,
|
insertDeleteRecord,
|
||||||
insertRenameRecord,
|
insertRenameRecord,
|
||||||
getAllDeleteRenameRecords,
|
getAllDeleteRenameRecords,
|
||||||
|
insertSyncPlanRecord,
|
||||||
} from "./localdb";
|
} from "./localdb";
|
||||||
|
|
||||||
import type { SyncStatusType } from "./sync";
|
import type { SyncStatusType } from "./sync";
|
||||||
import { getSyncPlan, doActualSync } from "./sync";
|
import { getSyncPlan, doActualSync } from "./sync";
|
||||||
import { DEFAULT_S3_CONFIG, getS3Client, listFromRemote, S3Config } from "./s3";
|
import { DEFAULT_S3_CONFIG, getS3Client, listFromRemote, S3Config } from "./s3";
|
||||||
|
import { exportSyncPlansToFiles } from "./debugMode";
|
||||||
|
|
||||||
interface SaveRemotePluginSettings {
|
interface SaveRemotePluginSettings {
|
||||||
s3?: S3Config;
|
s3?: S3Config;
|
||||||
@ -95,6 +97,7 @@ export default class SaveRemotePlugin extends Plugin {
|
|||||||
this.settings.password
|
this.settings.password
|
||||||
);
|
);
|
||||||
console.log(syncPlan.mixedStates); // for debugging
|
console.log(syncPlan.mixedStates); // for debugging
|
||||||
|
await insertSyncPlanRecord(this.db, syncPlan);
|
||||||
|
|
||||||
// The operations above are read only and kind of safe.
|
// The operations above are read only and kind of safe.
|
||||||
// The operations below begins to write or delete (!!!) something.
|
// The operations below begins to write or delete (!!!) something.
|
||||||
@ -259,5 +262,27 @@ class SaveRemoteSettingTab extends PluginSettingTab {
|
|||||||
);
|
);
|
||||||
|
|
||||||
containerEl.createEl("h2", { text: "Debug" });
|
containerEl.createEl("h2", { text: "Debug" });
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("export sync plans")
|
||||||
|
.setDesc("export sync plans")
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText("Export");
|
||||||
|
button.onClick(async () => {
|
||||||
|
await exportSyncPlansToFiles(this.plugin.db, this.app.vault);
|
||||||
|
new Notice("sync plans history exported");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("delete sync plans history in db")
|
||||||
|
.setDesc("delete sync plans history in db")
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText("Delete History");
|
||||||
|
button.onClick(async () => {
|
||||||
|
await clearAllSyncPlanRecords(this.plugin.db);
|
||||||
|
new Notice("sync plans history (in db) deleted");
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,9 +52,12 @@ export const getFolderLevels = (x: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const mkdirpInVault = async (thePath: string, vault: Vault) => {
|
export const mkdirpInVault = async (thePath: string, vault: Vault) => {
|
||||||
|
console.log(thePath);
|
||||||
const foldersToBuild = getFolderLevels(thePath);
|
const foldersToBuild = getFolderLevels(thePath);
|
||||||
|
console.log(foldersToBuild);
|
||||||
for (const folder of foldersToBuild) {
|
for (const folder of foldersToBuild) {
|
||||||
const r = await vault.adapter.exists(folder);
|
const r = await vault.adapter.exists(folder);
|
||||||
|
console.log(r);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
console.log(`mkdir ${folder}`);
|
console.log(`mkdir ${folder}`);
|
||||||
await vault.adapter.mkdir(folder);
|
await vault.adapter.mkdir(folder);
|
||||||
|
|||||||
13
src/sync.ts
13
src/sync.ts
@ -16,7 +16,7 @@ import {
|
|||||||
deleteFromRemote,
|
deleteFromRemote,
|
||||||
downloadFromRemote,
|
downloadFromRemote,
|
||||||
} from "./s3";
|
} from "./s3";
|
||||||
import { mkdirpInVault, SUPPORTED_SERVICES_TYPE } from "./misc";
|
import { mkdirpInVault, SUPPORTED_SERVICES_TYPE, isHiddenPath } from "./misc";
|
||||||
import { decryptBase32ToString, encryptStringToBase32 } from "./encrypt";
|
import { decryptBase32ToString, encryptStringToBase32 } from "./encrypt";
|
||||||
|
|
||||||
export type SyncStatusType =
|
export type SyncStatusType =
|
||||||
@ -55,7 +55,7 @@ interface FileOrFolderMixedState {
|
|||||||
remote_encrypted_key?: string;
|
remote_encrypted_key?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SyncPlanType {
|
export interface SyncPlanType {
|
||||||
ts: number;
|
ts: number;
|
||||||
remoteType: SUPPORTED_SERVICES_TYPE;
|
remoteType: SUPPORTED_SERVICES_TYPE;
|
||||||
mixedStates: Record<string, FileOrFolderMixedState>;
|
mixedStates: Record<string, FileOrFolderMixedState>;
|
||||||
@ -103,6 +103,9 @@ const ensembleMixedStates = async (
|
|||||||
remote_encrypted_key: remoteEncryptedKey,
|
remote_encrypted_key: remoteEncryptedKey,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (isHiddenPath(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (results.hasOwnProperty(key)) {
|
if (results.hasOwnProperty(key)) {
|
||||||
results[key].key = r.key;
|
results[key].key = r.key;
|
||||||
results[key].exist_remote = r.exist_remote;
|
results[key].exist_remote = r.exist_remote;
|
||||||
@ -141,6 +144,9 @@ const ensembleMixedStates = async (
|
|||||||
throw Error(`unexpected ${entry}`);
|
throw Error(`unexpected ${entry}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isHiddenPath(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (results.hasOwnProperty(key)) {
|
if (results.hasOwnProperty(key)) {
|
||||||
results[key].key = r.key;
|
results[key].key = r.key;
|
||||||
results[key].exist_local = r.exist_local;
|
results[key].exist_local = r.exist_local;
|
||||||
@ -168,6 +174,9 @@ const ensembleMixedStates = async (
|
|||||||
delete_time_local: entry.action_when,
|
delete_time_local: entry.action_when,
|
||||||
} as FileOrFolderMixedState;
|
} as FileOrFolderMixedState;
|
||||||
|
|
||||||
|
if (isHiddenPath(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (results.hasOwnProperty(key)) {
|
if (results.hasOwnProperty(key)) {
|
||||||
results[key].key = r.key;
|
results[key].key = r.key;
|
||||||
results[key].delete_time_local = r.delete_time_local;
|
results[key].delete_time_local = r.delete_time_local;
|
||||||
|
|||||||
@ -2,39 +2,71 @@ import * as fs from "fs";
|
|||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
|
||||||
import * as misc from '../src/misc'
|
import * as misc from "../src/misc";
|
||||||
|
|
||||||
describe("Misc: hidden file", () => {
|
describe("Misc: hidden file", () => {
|
||||||
it("should find hidden file correctly", () => {
|
it("should find hidden file correctly", () => {
|
||||||
let item = '';
|
let item = "";
|
||||||
expect(misc.isHiddenPath(item)).to.be.false;
|
expect(misc.isHiddenPath(item)).to.be.false;
|
||||||
|
|
||||||
item = '.'
|
item = ".";
|
||||||
expect(misc.isHiddenPath(item)).to.be.false;
|
expect(misc.isHiddenPath(item)).to.be.false;
|
||||||
|
|
||||||
item = '..'
|
item = "..";
|
||||||
expect(misc.isHiddenPath(item)).to.be.false;
|
expect(misc.isHiddenPath(item)).to.be.false;
|
||||||
|
|
||||||
item = '/x/y/z/../././../a/b/c'
|
item = "/x/y/z/../././../a/b/c";
|
||||||
expect(misc.isHiddenPath(item)).to.be.false;
|
expect(misc.isHiddenPath(item)).to.be.false;
|
||||||
|
|
||||||
item = '.hidden'
|
item = ".hidden";
|
||||||
expect(misc.isHiddenPath(item)).to.be.true;
|
expect(misc.isHiddenPath(item)).to.be.true;
|
||||||
|
|
||||||
item = '_hidden_loose'
|
item = "_hidden_loose";
|
||||||
expect(misc.isHiddenPath(item)).to.be.true;
|
expect(misc.isHiddenPath(item)).to.be.true;
|
||||||
expect(misc.isHiddenPath(item, false)).to.be.false;
|
expect(misc.isHiddenPath(item, false)).to.be.false;
|
||||||
|
|
||||||
item = '/sdd/_hidden_loose'
|
item = "/sdd/_hidden_loose";
|
||||||
expect(misc.isHiddenPath(item)).to.be.true;
|
expect(misc.isHiddenPath(item)).to.be.true;
|
||||||
|
|
||||||
item = 'what/../_hidden_loose/what/what/what'
|
item = "what/../_hidden_loose/what/what/what";
|
||||||
expect(misc.isHiddenPath(item)).to.be.true;
|
expect(misc.isHiddenPath(item)).to.be.true;
|
||||||
|
|
||||||
item = 'what/../_hidden_loose/what/what/what'
|
item = "what/../_hidden_loose/what/what/what";
|
||||||
expect(misc.isHiddenPath(item, false)).to.be.false;
|
expect(misc.isHiddenPath(item, false)).to.be.false;
|
||||||
|
|
||||||
item = 'what/../_hidden_loose/../.hidden/what/what/what'
|
item = "what/../_hidden_loose/../.hidden/what/what/what";
|
||||||
expect(misc.isHiddenPath(item, false)).to.be.true;
|
expect(misc.isHiddenPath(item, false)).to.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Misc: get folder levels", () => {
|
||||||
|
it("should ignore empty path", () => {
|
||||||
|
const item = "";
|
||||||
|
expect(misc.getFolderLevels(item)).to.be.empty;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should ignore single file", () => {
|
||||||
|
const item = "xxx";
|
||||||
|
expect(misc.getFolderLevels(item)).to.be.empty;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should detect path ending with /", () => {
|
||||||
|
const item = "xxx/";
|
||||||
|
const res = ["xxx"];
|
||||||
|
expect(misc.getFolderLevels(item)).to.deep.equal(res);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly split folders and files", () => {
|
||||||
|
const item = "xxx/yyy/zzz.md";
|
||||||
|
const res = ["xxx", "xxx/yyy"];
|
||||||
|
expect(misc.getFolderLevels(item)).to.deep.equal(res);
|
||||||
|
|
||||||
|
const item2 = "xxx/yyy/zzz";
|
||||||
|
const res2 = ["xxx", "xxx/yyy"];
|
||||||
|
expect(misc.getFolderLevels(item2)).to.deep.equal(res2);
|
||||||
|
|
||||||
|
const item3 = "xxx/yyy/zzz/";
|
||||||
|
const res3 = ["xxx", "xxx/yyy", "xxx/yyy/zzz"];
|
||||||
|
expect(misc.getFolderLevels(item3)).to.deep.equal(res3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user