mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-13 11:01:16 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f96cc6b82 | ||
|
|
8c8f5d045f | ||
|
|
40cf8be890 | ||
|
|
6b03dbbe75 | ||
|
|
74425f75d2 | ||
|
|
ac7c622466 | ||
|
|
4b32365694 | ||
|
|
728edac283 | ||
|
|
ab9c0190bb | ||
|
|
5a7610d411 | ||
|
|
4691ae1463 |
@@ -92,9 +92,6 @@ After installing Self-hosted LiveSync on the device, select `Open setup URI` fro
|
|||||||
Answer the following.
|
Answer the following.
|
||||||
|
|
||||||
- `Yes` to `Importing LiveSync's conf, OK?`
|
- `Yes` to `Importing LiveSync's conf, OK?`
|
||||||
- `No` to `Keep local DB?`
|
- `Set it up as secondary or subsequent device` to `How would you like to set it up?`.
|
||||||
- `Yes` to `Keep remote DB?`
|
|
||||||
- `No` to `Rebuild the database?`
|
|
||||||
- `Yes` to `Replicate once?`
|
|
||||||
|
|
||||||
Then, The configuration will now take effect and replication will start. Your files will be synchronised soon!
|
Then, The configuration will now take effect and replication will start. Your files will be synchronised soon!
|
||||||
@@ -85,13 +85,10 @@ All done! と表示されれば完了です。自動的に、`Copy setup URI`が
|
|||||||
クリップボードにSetup URIが保存されますので、これを2台目以降のデバイスに何らかの方法で転送してください。
|
クリップボードにSetup URIが保存されますので、これを2台目以降のデバイスに何らかの方法で転送してください。
|
||||||
|
|
||||||
# 2台目以降の設定方法
|
# 2台目以降の設定方法
|
||||||
2台目の端末にSelf-hosted LiveSyncをインストールしたあと、コマンドパレットから`Open setup URI`を選択し、転送したsetup URIを入力します。その後、パスフレーズを入力するとセットアップ用のウィザードが開きます。
|
2台目の端末にSelf-hosted LiveSyncをインストールしたあと、コマンドパレットから`Open setup URI`を選択し、転送したsetup URIを入力します。その後、パスフレーズを入力するとセットアップ用のウィザードが開きます。
|
||||||
下記のように答えてください。
|
下記のように答えてください。
|
||||||
|
|
||||||
- `Importing LiveSync's conf, OK?` に `Yes`
|
- `Importing LiveSync's conf, OK?` に `Yes`
|
||||||
- `Keep local DB?` に `No`
|
- `How would you like to set it up?` に `Set it up as secondary or subsequent device`
|
||||||
- `Keep remote DB?` に `Yes`
|
|
||||||
- `Rebuild the database?` に `No`
|
|
||||||
- `Replicate once?` に `Yes`
|
|
||||||
|
|
||||||
これで設定が反映され、レプリケーションが開始されます。
|
これで設定が反映され、レプリケーションが開始されます。
|
||||||
@@ -32,16 +32,18 @@ max_age = 3600
|
|||||||
|
|
||||||
Make `local.ini` and run with docker run like this, you can launch the CouchDB.
|
Make `local.ini` and run with docker run like this, you can launch the CouchDB.
|
||||||
```
|
```
|
||||||
$ docker run --rm -it -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password -v .local.ini:/opt/couchdb/etc/local.ini -p 5984:5984 couchdb
|
$ docker run --rm -it -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password -v /path/to/local.ini:/opt/couchdb/etc/local.ini -p 5984:5984 couchdb
|
||||||
```
|
```
|
||||||
|
*Remember to replace the path with the path to your local.ini*
|
||||||
Note: At this time, the file owner of local.ini became 5984:5984. It's the limitation docker image. please change the owner before editing local.ini again.
|
Note: At this time, the file owner of local.ini became 5984:5984. It's the limitation docker image. please change the owner before editing local.ini again.
|
||||||
|
|
||||||
If you could confirm that Self-hosted LiveSync can sync with the server, launch docker image as background as you like.
|
If you could confirm that Self-hosted LiveSync can sync with the server, launch docker image as background as you like.
|
||||||
|
|
||||||
example)
|
Example to run docker in detached mode:
|
||||||
```
|
```
|
||||||
$ docker run -d --restart always -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password -v .local.ini:/opt/couchdb/etc/local.ini -p 5984:5984 couchdb
|
$ docker run -d --restart always -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password -v /path/to/local.ini:/opt/couchdb/etc/local.ini -p 5984:5984 couchdb
|
||||||
```
|
```
|
||||||
|
*Remember to replace the path with the path to your local.ini*
|
||||||
|
|
||||||
## Access from mobile device
|
## Access from mobile device
|
||||||
If you want to access Self-hosted LiveSync from mobile devices, you need a valid SSL certificate.
|
If you want to access Self-hosted LiveSync from mobile devices, you need a valid SSL certificate.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-livesync",
|
"id": "obsidian-livesync",
|
||||||
"name": "Self-hosted LiveSync",
|
"name": "Self-hosted LiveSync",
|
||||||
"version": "0.15.3",
|
"version": "0.15.7",
|
||||||
"minAppVersion": "0.9.12",
|
"minAppVersion": "0.9.12",
|
||||||
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||||
"author": "vorotamoroz",
|
"author": "vorotamoroz",
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.15.3",
|
"version": "0.15.7",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.15.3",
|
"version": "0.15.7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"diff-match-patch": "^1.0.5",
|
"diff-match-patch": "^1.0.5",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.15.3",
|
"version": "0.15.7",
|
||||||
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -164,6 +164,24 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
const infoWarnForSubsequent = setupWizardEl.createEl("div", { text: `To set up second or subsequent device, please use 'Copy setup URI' and 'Open setup URI'` });
|
||||||
|
infoWarnForSubsequent.addClass("op-warn-info");
|
||||||
|
new Setting(setupWizardEl)
|
||||||
|
.setName("Copy setup URI")
|
||||||
|
.addButton((text) => {
|
||||||
|
text.setButtonText("Copy setup URI").onClick(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
this.plugin.app.commands.executeCommandById("obsidian-livesync:livesync-copysetupuri")
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.addButton((text) => {
|
||||||
|
text.setButtonText("Open setup URI").onClick(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
this.plugin.app.commands.executeCommandById("obsidian-livesync:livesync-opensetupuri")
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
addScreenElement("110", setupWizardEl);
|
addScreenElement("110", setupWizardEl);
|
||||||
|
|
||||||
@@ -361,7 +379,17 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
await applyEncryption(true);
|
await applyEncryption(true);
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
.addButton((button) =>
|
||||||
|
button
|
||||||
|
.setButtonText("Apply w/o rebuilding")
|
||||||
|
.setWarning()
|
||||||
|
.setDisabled(false)
|
||||||
|
.setClass("sls-btn-right")
|
||||||
|
.onClick(async () => {
|
||||||
|
await applyEncryption(false);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
const rebuildDB = async (method: "localOnly" | "remoteOnly" | "rebuildBothByThisDevice") => {
|
const rebuildDB = async (method: "localOnly" | "remoteOnly" | "rebuildBothByThisDevice") => {
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export class PopoverSelectString extends FuzzySuggestModal<string> {
|
|||||||
constructor(app: App, note: string, placeholder: string | null, getItemsFun: () => string[], callback: (e: string) => void) {
|
constructor(app: App, note: string, placeholder: string | null, getItemsFun: () => string[], callback: (e: string) => void) {
|
||||||
super(app);
|
super(app);
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.setPlaceholder(placeholder ?? "y/n) " + note);
|
this.setPlaceholder((placeholder ?? "y/n) ") + note);
|
||||||
if (getItemsFun) this.getItemsFun = getItemsFun;
|
if (getItemsFun) this.getItemsFun = getItemsFun;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/lib
2
src/lib
Submodule src/lib updated: 2c39c15177...d8d83b7f46
467
src/main.ts
467
src/main.ts
@@ -41,7 +41,7 @@ setNoticeClass(Notice);
|
|||||||
const ICHeader = "i:";
|
const ICHeader = "i:";
|
||||||
const ICHeaderEnd = "i;";
|
const ICHeaderEnd = "i;";
|
||||||
const ICHeaderLength = ICHeader.length;
|
const ICHeaderLength = ICHeader.length;
|
||||||
|
const FileWatchEventQueueMax = 10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns is internal chunk of file
|
* returns is internal chunk of file
|
||||||
@@ -82,7 +82,7 @@ const askYesNo = (app: App, message: string): Promise<"yes" | "no"> => {
|
|||||||
const askSelectString = (app: App, message: string, items: string[]): Promise<string> => {
|
const askSelectString = (app: App, message: string, items: string[]): Promise<string> => {
|
||||||
const getItemsFun = () => items;
|
const getItemsFun = () => items;
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
const popover = new PopoverSelectString(app, message, "Select file)", getItemsFun, (result) => res(result));
|
const popover = new PopoverSelectString(app, message, "", getItemsFun, (result) => res(result));
|
||||||
popover.open();
|
popover.open();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -109,6 +109,19 @@ function recentlyTouched(file: TFile) {
|
|||||||
function clearTouched() {
|
function clearTouched() {
|
||||||
touchedFiles = [];
|
touchedFiles = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CacheData = string | ArrayBuffer;
|
||||||
|
type FileEventType = "CREATE" | "DELETE" | "CHANGED" | "RENAME";
|
||||||
|
type FileEventArgs = {
|
||||||
|
file: TAbstractFile;
|
||||||
|
cache?: CacheData;
|
||||||
|
oldPath?: string;
|
||||||
|
ctx?: any;
|
||||||
|
}
|
||||||
|
type FileEventItem = {
|
||||||
|
type: FileEventType,
|
||||||
|
args: FileEventArgs
|
||||||
|
}
|
||||||
export default class ObsidianLiveSyncPlugin extends Plugin {
|
export default class ObsidianLiveSyncPlugin extends Plugin {
|
||||||
settings: ObsidianLiveSyncSettings;
|
settings: ObsidianLiveSyncSettings;
|
||||||
localDatabase: LocalPouchDB;
|
localDatabase: LocalPouchDB;
|
||||||
@@ -118,6 +131,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
suspended: boolean;
|
suspended: boolean;
|
||||||
deviceAndVaultName: string;
|
deviceAndVaultName: string;
|
||||||
isMobile = false;
|
isMobile = false;
|
||||||
|
isReady = false;
|
||||||
|
|
||||||
|
watchedFileEventQueue = [] as FileEventItem[];
|
||||||
|
|
||||||
getVaultName(): string {
|
getVaultName(): string {
|
||||||
return this.app.vault.getName() + (this.settings?.additionalSuffixOfDatabaseName ? ("-" + this.settings.additionalSuffixOfDatabaseName) : "");
|
return this.app.vault.getName() + (this.settings?.additionalSuffixOfDatabaseName ? ("-" + this.settings.additionalSuffixOfDatabaseName) : "");
|
||||||
@@ -277,10 +293,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.refreshStatusText = this.refreshStatusText.bind(this);
|
this.refreshStatusText = this.refreshStatusText.bind(this);
|
||||||
|
|
||||||
this.statusBar2 = this.addStatusBarItem();
|
this.statusBar2 = this.addStatusBarItem();
|
||||||
// this.watchVaultChange = debounce(this.watchVaultChange.bind(this), delay, false);
|
|
||||||
// this.watchVaultDelete = debounce(this.watchVaultDelete.bind(this), delay, false);
|
|
||||||
// this.watchVaultRename = debounce(this.watchVaultRename.bind(this), delay, false);
|
|
||||||
|
|
||||||
this.watchVaultChange = this.watchVaultChange.bind(this);
|
this.watchVaultChange = this.watchVaultChange.bind(this);
|
||||||
this.watchVaultCreate = this.watchVaultCreate.bind(this);
|
this.watchVaultCreate = this.watchVaultCreate.bind(this);
|
||||||
this.watchVaultDelete = this.watchVaultDelete.bind(this);
|
this.watchVaultDelete = this.watchVaultDelete.bind(this);
|
||||||
@@ -298,6 +310,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
// this.registerWatchEvents();
|
// this.registerWatchEvents();
|
||||||
this.addSettingTab(new ObsidianLiveSyncSettingTab(this.app, this));
|
this.addSettingTab(new ObsidianLiveSyncSettingTab(this.app, this));
|
||||||
|
|
||||||
|
this.registerFileWatchEvents();
|
||||||
this.app.workspace.onLayoutReady(async () => {
|
this.app.workspace.onLayoutReady(async () => {
|
||||||
if (this.localDatabase.isReady)
|
if (this.localDatabase.isReady)
|
||||||
try {
|
try {
|
||||||
@@ -340,11 +353,31 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
const configURIBase = "obsidian://setuplivesync?settings=";
|
const configURIBase = "obsidian://setuplivesync?settings=";
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-copysetupuri",
|
id: "livesync-copysetupuri",
|
||||||
name: "Copy setup URI (beta)",
|
name: "Copy setup URI",
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
const encryptingPassphrase = await askString(this.app, "Encrypt your settings", "Passphrase", "");
|
const encryptingPassphrase = await askString(this.app, "Encrypt your settings", "Passphrase", "");
|
||||||
if (encryptingPassphrase === false) return;
|
if (encryptingPassphrase === false) return;
|
||||||
const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(this.settings), encryptingPassphrase));
|
const setting = { ...this.settings };
|
||||||
|
const keys = Object.keys(setting) as (keyof ObsidianLiveSyncSettings)[];
|
||||||
|
for (const k of keys) {
|
||||||
|
if (JSON.stringify(k in setting ? setting[k] : "") == JSON.stringify(k in DEFAULT_SETTINGS ? DEFAULT_SETTINGS[k] : "*")) {
|
||||||
|
delete setting[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(setting), encryptingPassphrase));
|
||||||
|
const uri = `${configURIBase}${encryptedSetting}`;
|
||||||
|
await navigator.clipboard.writeText(uri);
|
||||||
|
Logger("Setup URI copied to clipboard", LOG_LEVEL.NOTICE);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.addCommand({
|
||||||
|
id: "livesync-copysetupurifull",
|
||||||
|
name: "Copy setup URI (Full)",
|
||||||
|
callback: async () => {
|
||||||
|
const encryptingPassphrase = await askString(this.app, "Encrypt your settings", "Passphrase", "");
|
||||||
|
if (encryptingPassphrase === false) return;
|
||||||
|
const setting = { ...this.settings };
|
||||||
|
const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(setting), encryptingPassphrase));
|
||||||
const uri = `${configURIBase}${encryptedSetting}`;
|
const uri = `${configURIBase}${encryptedSetting}`;
|
||||||
await navigator.clipboard.writeText(uri);
|
await navigator.clipboard.writeText(uri);
|
||||||
Logger("Setup URI copied to clipboard", LOG_LEVEL.NOTICE);
|
Logger("Setup URI copied to clipboard", LOG_LEVEL.NOTICE);
|
||||||
@@ -352,9 +385,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
});
|
});
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-opensetupuri",
|
id: "livesync-opensetupuri",
|
||||||
name: "Open setup URI (beta)",
|
name: "Open setup URI",
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
const setupURI = await askString(this.app, "Set up manually", "Set up URI", `${configURIBase}aaaaa`);
|
const setupURI = await askString(this.app, "Easy setup", "Set up URI", `${configURIBase}aaaaa`);
|
||||||
if (setupURI === false) return;
|
if (setupURI === false) return;
|
||||||
if (!setupURI.startsWith(`${configURIBase}`)) {
|
if (!setupURI.startsWith(`${configURIBase}`)) {
|
||||||
Logger("Set up URI looks wrong.", LOG_LEVEL.NOTICE);
|
Logger("Set up URI looks wrong.", LOG_LEVEL.NOTICE);
|
||||||
@@ -374,58 +407,91 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
if (newConf) {
|
if (newConf) {
|
||||||
const result = await askYesNo(this.app, "Importing LiveSync's conf, OK?");
|
const result = await askYesNo(this.app, "Importing LiveSync's conf, OK?");
|
||||||
if (result == "yes") {
|
if (result == "yes") {
|
||||||
const newSettingW = Object.assign({}, this.settings, newConf);
|
const newSettingW = Object.assign({}, DEFAULT_SETTINGS, newConf);
|
||||||
// stopping once.
|
|
||||||
this.localDatabase.closeReplication();
|
this.localDatabase.closeReplication();
|
||||||
this.settings.suspendFileWatching = true;
|
this.settings.suspendFileWatching = true;
|
||||||
console.dir(newSettingW);
|
console.dir(newSettingW);
|
||||||
const keepLocalDB = await askYesNo(this.app, "Keep local DB?");
|
const setupJustImport = "Just import setting";
|
||||||
const keepRemoteDB = await askYesNo(this.app, "Keep remote DB?");
|
const setupAsNew = "Set it up as secondary or subsequent device";
|
||||||
if (keepLocalDB == "yes" && keepRemoteDB == "yes") {
|
const setupAgain = "Reconfigure and reconstitute the data";
|
||||||
// nothing to do. so peaceful.
|
const setupManually = "Leave everything to me";
|
||||||
|
|
||||||
|
const setupType = await askSelectString(this.app, "How would you like to set it up?", [setupAsNew, setupAgain, setupJustImport, setupManually]);
|
||||||
|
if (setupType == setupJustImport) {
|
||||||
this.settings = newSettingW;
|
this.settings = newSettingW;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
const replicate = await askYesNo(this.app, "Unlock and replicate?");
|
} else if (setupType == setupAsNew) {
|
||||||
if (replicate == "yes") {
|
this.settings = newSettingW;
|
||||||
await this.replicate(true);
|
await this.saveSettings();
|
||||||
await this.markRemoteUnlocked();
|
await this.resetLocalOldDatabase();
|
||||||
}
|
await this.resetLocalDatabase();
|
||||||
Logger("Configuration loaded.", LOG_LEVEL.NOTICE);
|
await this.localDatabase.initializeDatabase();
|
||||||
return;
|
await this.markRemoteResolved();
|
||||||
}
|
await this.replicate(true);
|
||||||
if (keepLocalDB == "no" && keepRemoteDB == "no") {
|
} else if (setupType == setupAgain) {
|
||||||
const reset = await askYesNo(this.app, "Drop everything?");
|
const confirm = "I know this operation will rebuild all my databases with files on this device, and files that are on the remote database and I didn't synchronize to any other devices will be lost and want to proceed indeed.";
|
||||||
if (reset != "yes") {
|
if (await askSelectString(this.app, "Do you really want to do this?", ["Cancel", confirm]) != confirm) {
|
||||||
Logger("Cancelled", LOG_LEVEL.NOTICE);
|
|
||||||
this.settings = oldConf;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
await this.saveSettings();
|
||||||
let initDB;
|
await this.resetLocalOldDatabase();
|
||||||
this.settings = newSettingW;
|
await this.resetLocalDatabase();
|
||||||
await this.saveSettings();
|
await this.localDatabase.initializeDatabase();
|
||||||
if (keepLocalDB == "no") {
|
await this.initializeDatabase(true);
|
||||||
this.resetLocalOldDatabase();
|
|
||||||
this.resetLocalDatabase();
|
|
||||||
this.localDatabase.initializeDatabase();
|
|
||||||
const rebuild = await askYesNo(this.app, "Rebuild the database?");
|
|
||||||
if (rebuild == "yes") {
|
|
||||||
initDB = this.initializeDatabase(true);
|
|
||||||
} else {
|
|
||||||
this.markRemoteResolved();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (keepRemoteDB == "no") {
|
|
||||||
await this.tryResetRemoteDatabase();
|
await this.tryResetRemoteDatabase();
|
||||||
await this.markRemoteLocked();
|
await this.markRemoteLocked();
|
||||||
}
|
await this.markRemoteResolved();
|
||||||
if (keepLocalDB == "no" || keepRemoteDB == "no") {
|
await this.replicate(true);
|
||||||
const replicate = await askYesNo(this.app, "Replicate once?");
|
|
||||||
if (replicate == "yes") {
|
} else if (setupType == setupManually) {
|
||||||
if (initDB != null) {
|
const keepLocalDB = await askYesNo(this.app, "Keep local DB?");
|
||||||
await initDB;
|
const keepRemoteDB = await askYesNo(this.app, "Keep remote DB?");
|
||||||
|
if (keepLocalDB == "yes" && keepRemoteDB == "yes") {
|
||||||
|
// nothing to do. so peaceful.
|
||||||
|
this.settings = newSettingW;
|
||||||
|
await this.saveSettings();
|
||||||
|
const replicate = await askYesNo(this.app, "Unlock and replicate?");
|
||||||
|
if (replicate == "yes") {
|
||||||
|
await this.replicate(true);
|
||||||
|
await this.markRemoteUnlocked();
|
||||||
|
}
|
||||||
|
Logger("Configuration loaded.", LOG_LEVEL.NOTICE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (keepLocalDB == "no" && keepRemoteDB == "no") {
|
||||||
|
const reset = await askYesNo(this.app, "Drop everything?");
|
||||||
|
if (reset != "yes") {
|
||||||
|
Logger("Cancelled", LOG_LEVEL.NOTICE);
|
||||||
|
this.settings = oldConf;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let initDB;
|
||||||
|
this.settings = newSettingW;
|
||||||
|
await this.saveSettings();
|
||||||
|
if (keepLocalDB == "no") {
|
||||||
|
this.resetLocalOldDatabase();
|
||||||
|
this.resetLocalDatabase();
|
||||||
|
this.localDatabase.initializeDatabase();
|
||||||
|
const rebuild = await askYesNo(this.app, "Rebuild the database?");
|
||||||
|
if (rebuild == "yes") {
|
||||||
|
initDB = this.initializeDatabase(true);
|
||||||
|
} else {
|
||||||
|
this.markRemoteResolved();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keepRemoteDB == "no") {
|
||||||
|
await this.tryResetRemoteDatabase();
|
||||||
|
await this.markRemoteLocked();
|
||||||
|
}
|
||||||
|
if (keepLocalDB == "no" || keepRemoteDB == "no") {
|
||||||
|
const replicate = await askYesNo(this.app, "Replicate once?");
|
||||||
|
if (replicate == "yes") {
|
||||||
|
if (initDB != null) {
|
||||||
|
await initDB;
|
||||||
|
}
|
||||||
|
await this.replicate(true);
|
||||||
}
|
}
|
||||||
await this.replicate(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -647,12 +713,14 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
|
|
||||||
gcTimerHandler: any = null;
|
gcTimerHandler: any = null;
|
||||||
|
|
||||||
|
registerFileWatchEvents() {
|
||||||
registerWatchEvents() {
|
|
||||||
this.registerEvent(this.app.vault.on("modify", this.watchVaultChange));
|
this.registerEvent(this.app.vault.on("modify", this.watchVaultChange));
|
||||||
this.registerEvent(this.app.vault.on("delete", this.watchVaultDelete));
|
this.registerEvent(this.app.vault.on("delete", this.watchVaultDelete));
|
||||||
this.registerEvent(this.app.vault.on("rename", this.watchVaultRename));
|
this.registerEvent(this.app.vault.on("rename", this.watchVaultRename));
|
||||||
this.registerEvent(this.app.vault.on("create", this.watchVaultCreate));
|
this.registerEvent(this.app.vault.on("create", this.watchVaultCreate));
|
||||||
|
}
|
||||||
|
|
||||||
|
registerWatchEvents() {
|
||||||
this.registerEvent(this.app.workspace.on("file-open", this.watchWorkspaceOpen));
|
this.registerEvent(this.app.workspace.on("file-open", this.watchWorkspaceOpen));
|
||||||
window.addEventListener("visibilitychange", this.watchWindowVisibility);
|
window.addEventListener("visibilitychange", this.watchWindowVisibility);
|
||||||
window.addEventListener("online", this.watchOnline);
|
window.addEventListener("online", this.watchOnline);
|
||||||
@@ -675,6 +743,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
|
|
||||||
async watchWindowVisibilityAsync() {
|
async watchWindowVisibilityAsync() {
|
||||||
if (this.settings.suspendFileWatching) return;
|
if (this.settings.suspendFileWatching) return;
|
||||||
|
if (!this.isReady) return;
|
||||||
// if (this.suspended) return;
|
// if (this.suspended) return;
|
||||||
const isHidden = document.hidden;
|
const isHidden = document.hidden;
|
||||||
await this.applyBatchChange();
|
await this.applyBatchChange();
|
||||||
@@ -699,12 +768,125 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache file and waiting to can be proceed.
|
||||||
|
async appendWatchEvent(type: FileEventType, file: TAbstractFile, oldPath?: string, ctx?: any) {
|
||||||
|
// check really we can process.
|
||||||
|
if (!this.isTargetFile(file)) return;
|
||||||
|
if (this.settings.suspendFileWatching) return;
|
||||||
|
|
||||||
|
let cache: null | string | ArrayBuffer;
|
||||||
|
// new file or something changed, cache the changes.
|
||||||
|
if (file instanceof TFile && (type == "CREATE" || type == "CHANGED")) {
|
||||||
|
if (recentlyTouched(file)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isPlainText(file.name)) {
|
||||||
|
cache = await this.app.vault.readBinary(file);
|
||||||
|
} else {
|
||||||
|
// cache = await this.app.vault.read(file);
|
||||||
|
cache = await this.app.vault.cachedRead(file);
|
||||||
|
if (!cache) cache = await this.app.vault.read(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (this.settings.batchSave) {
|
||||||
|
// if the latest event is the same type, omit that
|
||||||
|
// a.md MODIFY <- this should be cancelled when a.md MODIFIED
|
||||||
|
// b.md MODIFY <- this should be cancelled when b.md MODIFIED
|
||||||
|
// a.md MODIFY
|
||||||
|
// a.md CREATE
|
||||||
|
// :
|
||||||
|
let i = this.watchedFileEventQueue.length;
|
||||||
|
while (i >= 0) {
|
||||||
|
i--;
|
||||||
|
if (i < 0) break;
|
||||||
|
if (this.watchedFileEventQueue[i].args.file.path != file.path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (this.watchedFileEventQueue[i].type != type) break;
|
||||||
|
this.watchedFileEventQueue.remove(this.watchedFileEventQueue[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.watchedFileEventQueue.push({
|
||||||
|
type,
|
||||||
|
args: {
|
||||||
|
file,
|
||||||
|
oldPath,
|
||||||
|
cache,
|
||||||
|
ctx
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.refreshStatusText();
|
||||||
|
if (this.isReady) {
|
||||||
|
await this.procFileEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
async procFileEvent(applyBatch?: boolean) {
|
||||||
|
if (!this.isReady) return;
|
||||||
|
if (this.settings.batchSave) {
|
||||||
|
if (!applyBatch && this.watchedFileEventQueue.length < FileWatchEventQueueMax) {
|
||||||
|
// Defer till applying batch save or queue has been grown enough.
|
||||||
|
// or 120 seconds after.
|
||||||
|
setTrigger("applyBatchAuto", 120000, () => {
|
||||||
|
this.procFileEvent(true);
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clearTrigger("applyBatchAuto");
|
||||||
|
const ret = await runWithLock("procFiles", false, async () => {
|
||||||
|
const procs = [...this.watchedFileEventQueue];
|
||||||
|
this.watchedFileEventQueue = [];
|
||||||
|
for (const queue of procs) {
|
||||||
|
const file = queue.args.file;
|
||||||
|
const cache = queue.args.cache;
|
||||||
|
if ((queue.type == "CREATE" || queue.type == "CHANGED") && file instanceof TFile) {
|
||||||
|
await this.updateIntoDB(file, false, cache);
|
||||||
|
}
|
||||||
|
if (queue.type == "DELETE") {
|
||||||
|
if (file instanceof TFile) {
|
||||||
|
await this.deleteFromDB(file);
|
||||||
|
} else if (file instanceof TFolder) {
|
||||||
|
await this.deleteFolderOnDB(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (queue.type == "RENAME") {
|
||||||
|
await this.watchVaultRenameAsync(file, queue.args.oldPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.refreshStatusText();
|
||||||
|
})
|
||||||
|
this.refreshStatusText();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
watchVaultCreate(file: TAbstractFile, ctx?: any) {
|
||||||
|
this.appendWatchEvent("CREATE", file, null, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
watchVaultChange(file: TAbstractFile, ctx?: any) {
|
||||||
|
this.appendWatchEvent("CHANGED", file, null, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
watchVaultDelete(file: TAbstractFile, ctx?: any) {
|
||||||
|
this.appendWatchEvent("DELETE", file, null, ctx);
|
||||||
|
}
|
||||||
|
watchVaultRename(file: TAbstractFile, oldFile: string, ctx?: any) {
|
||||||
|
this.appendWatchEvent("RENAME", file, oldFile, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
watchWorkspaceOpen(file: TFile) {
|
watchWorkspaceOpen(file: TFile) {
|
||||||
if (this.settings.suspendFileWatching) return;
|
if (this.settings.suspendFileWatching) return;
|
||||||
|
if (!this.isReady) return;
|
||||||
this.watchWorkspaceOpenAsync(file);
|
this.watchWorkspaceOpenAsync(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
async watchWorkspaceOpenAsync(file: TFile) {
|
async watchWorkspaceOpenAsync(file: TFile) {
|
||||||
|
if (this.settings.suspendFileWatching) return;
|
||||||
|
if (!this.isReady) return;
|
||||||
await this.applyBatchChange();
|
await this.applyBatchChange();
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
return;
|
return;
|
||||||
@@ -715,100 +897,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
await this.showIfConflicted(file);
|
await this.showIfConflicted(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
watchVaultCreate(file: TFile, ...args: any[]) {
|
|
||||||
if (!this.isTargetFile(file)) return;
|
|
||||||
if (this.settings.suspendFileWatching) return;
|
|
||||||
if (recentlyTouched(file)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.watchVaultChangeAsync(file, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
watchVaultChange(file: TAbstractFile, ...args: any[]) {
|
|
||||||
if (!this.isTargetFile(file)) return;
|
|
||||||
if (!(file instanceof TFile)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (recentlyTouched(file)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.settings.suspendFileWatching) return;
|
|
||||||
|
|
||||||
// If batchSave is enabled, queue all changes and do nothing.
|
|
||||||
if (this.settings.batchSave) {
|
|
||||||
~(async () => {
|
|
||||||
const meta = await this.localDatabase.getDBEntryMeta(file.path);
|
|
||||||
if (meta != false) {
|
|
||||||
const localMtime = ~~(file.stat.mtime / 1000);
|
|
||||||
const docMtime = ~~(meta.mtime / 1000);
|
|
||||||
if (localMtime !== docMtime) {
|
|
||||||
// Perhaps we have to modify (to using newer doc), but we don't be sure to every device's clock is adjusted.
|
|
||||||
this.batchFileChange = Array.from(new Set([...this.batchFileChange, file.path]));
|
|
||||||
this.refreshStatusText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.watchVaultChangeAsync(file, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
async applyBatchChange() {
|
async applyBatchChange() {
|
||||||
if (!this.settings.batchSave || this.batchFileChange.length == 0) {
|
return await this.procFileEvent(true);
|
||||||
return;
|
|
||||||
}
|
|
||||||
return await runWithLock("batchSave", false, async () => {
|
|
||||||
const batchItems = JSON.parse(JSON.stringify(this.batchFileChange)) as string[];
|
|
||||||
this.batchFileChange = [];
|
|
||||||
const semaphore = Semaphore(3);
|
|
||||||
|
|
||||||
const batchProcesses = batchItems.map(e => (async (e) => {
|
|
||||||
const releaser = await semaphore.acquire(1, "batch");
|
|
||||||
try {
|
|
||||||
const f = this.app.vault.getAbstractFileByPath(normalizePath(e));
|
|
||||||
if (f && f instanceof TFile) {
|
|
||||||
await this.updateIntoDB(f);
|
|
||||||
Logger(`Batch save:${e}`);
|
|
||||||
}
|
|
||||||
} catch (ex) {
|
|
||||||
Logger(`Batch save error:${e}`, LOG_LEVEL.NOTICE);
|
|
||||||
Logger(ex, LOG_LEVEL.VERBOSE);
|
|
||||||
} finally {
|
|
||||||
releaser();
|
|
||||||
}
|
|
||||||
})(e))
|
|
||||||
await Promise.all(batchProcesses);
|
|
||||||
|
|
||||||
this.refreshStatusText();
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
batchFileChange: string[] = [];
|
|
||||||
|
|
||||||
async watchVaultChangeAsync(file: TFile, ...args: any[]) {
|
|
||||||
if (file instanceof TFile) {
|
|
||||||
if (recentlyTouched(file)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.updateIntoDB(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watchVaultDelete(file: TAbstractFile) {
|
|
||||||
if (!this.isTargetFile(file)) return;
|
|
||||||
// When save is delayed, it should be cancelled.
|
|
||||||
this.batchFileChange = this.batchFileChange.filter((e) => e != file.path);
|
|
||||||
if (this.settings.suspendFileWatching) return;
|
|
||||||
this.watchVaultDeleteAsync(file).then(() => { });
|
|
||||||
}
|
|
||||||
|
|
||||||
async watchVaultDeleteAsync(file: TAbstractFile) {
|
|
||||||
if (file instanceof TFile) {
|
|
||||||
await this.deleteFromDB(file);
|
|
||||||
} else if (file instanceof TFolder) {
|
|
||||||
await this.deleteFolderOnDB(file);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GetAllFilesRecursively(file: TAbstractFile): TFile[] {
|
GetAllFilesRecursively(file: TAbstractFile): TFile[] {
|
||||||
@@ -826,12 +916,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watchVaultRename(file: TAbstractFile, oldFile: any) {
|
|
||||||
if (!this.isTargetFile(file)) return;
|
|
||||||
if (this.settings.suspendFileWatching) return;
|
|
||||||
this.watchVaultRenameAsync(file, oldFile).then(() => { });
|
|
||||||
}
|
|
||||||
|
|
||||||
getFilePath(file: TAbstractFile): string {
|
getFilePath(file: TAbstractFile): string {
|
||||||
if (file instanceof TFolder) {
|
if (file instanceof TFolder) {
|
||||||
if (file.isRoot()) return "";
|
if (file.isRoot()) return "";
|
||||||
@@ -844,13 +928,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
return this.getFilePath(file.parent) + "/" + file.name;
|
return this.getFilePath(file.parent) + "/" + file.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
async watchVaultRenameAsync(file: TAbstractFile, oldFile: any) {
|
async watchVaultRenameAsync(file: TAbstractFile, oldFile: any, cache?: CacheData) {
|
||||||
Logger(`${oldFile} renamed to ${file.path}`, LOG_LEVEL.VERBOSE);
|
Logger(`${oldFile} renamed to ${file.path}`, LOG_LEVEL.VERBOSE);
|
||||||
try {
|
|
||||||
await this.applyBatchChange();
|
|
||||||
} catch (ex) {
|
|
||||||
Logger(ex);
|
|
||||||
}
|
|
||||||
if (file instanceof TFolder) {
|
if (file instanceof TFolder) {
|
||||||
const newFiles = this.GetAllFilesRecursively(file);
|
const newFiles = this.GetAllFilesRecursively(file);
|
||||||
// for guard edge cases. this won't happen and each file's event will be raise.
|
// for guard edge cases. this won't happen and each file's event will be raise.
|
||||||
@@ -871,7 +950,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
} else if (file instanceof TFile) {
|
} else if (file instanceof TFile) {
|
||||||
try {
|
try {
|
||||||
Logger(`file save ${file.path} into db`);
|
Logger(`file save ${file.path} into db`);
|
||||||
await this.updateIntoDB(file);
|
await this.updateIntoDB(file, false, cache);
|
||||||
Logger(`deleted ${oldFile} from db`);
|
Logger(`deleted ${oldFile} from db`);
|
||||||
await this.deleteFromDBbyPath(oldFile);
|
await this.deleteFromDBbyPath(oldFile);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
@@ -993,7 +1072,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
ctime: doc.ctime,
|
ctime: doc.ctime,
|
||||||
mtime: doc.mtime,
|
mtime: doc.mtime,
|
||||||
});
|
});
|
||||||
this.batchFileChange = this.batchFileChange.filter((e) => e != newFile.path);
|
// this.batchFileChange = this.batchFileChange.filter((e) => e != newFile.path);
|
||||||
Logger(msg + path);
|
Logger(msg + path);
|
||||||
touch(newFile);
|
touch(newFile);
|
||||||
this.app.vault.trigger("create", newFile);
|
this.app.vault.trigger("create", newFile);
|
||||||
@@ -1013,7 +1092,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
ctime: doc.ctime,
|
ctime: doc.ctime,
|
||||||
mtime: doc.mtime,
|
mtime: doc.mtime,
|
||||||
});
|
});
|
||||||
this.batchFileChange = this.batchFileChange.filter((e) => e != newFile.path);
|
// this.batchFileChange = this.batchFileChange.filter((e) => e != newFile.path);
|
||||||
Logger(msg + path);
|
Logger(msg + path);
|
||||||
touch(newFile);
|
touch(newFile);
|
||||||
this.app.vault.trigger("create", newFile);
|
this.app.vault.trigger("create", newFile);
|
||||||
@@ -1081,7 +1160,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
await this.ensureDirectory(path);
|
await this.ensureDirectory(path);
|
||||||
try {
|
try {
|
||||||
await this.app.vault.modifyBinary(file, bin, { ctime: doc.ctime, mtime: doc.mtime });
|
await this.app.vault.modifyBinary(file, bin, { ctime: doc.ctime, mtime: doc.mtime });
|
||||||
this.batchFileChange = this.batchFileChange.filter((e) => e != file.path);
|
// this.batchFileChange = this.batchFileChange.filter((e) => e != file.path);
|
||||||
Logger(msg + path);
|
Logger(msg + path);
|
||||||
const xf = this.app.vault.getAbstractFileByPath(file.path) as TFile;
|
const xf = this.app.vault.getAbstractFileByPath(file.path) as TFile;
|
||||||
touch(xf);
|
touch(xf);
|
||||||
@@ -1099,7 +1178,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
try {
|
try {
|
||||||
await this.app.vault.modify(file, doc.data, { ctime: doc.ctime, mtime: doc.mtime });
|
await this.app.vault.modify(file, doc.data, { ctime: doc.ctime, mtime: doc.mtime });
|
||||||
Logger(msg + path);
|
Logger(msg + path);
|
||||||
this.batchFileChange = this.batchFileChange.filter((e) => e != file.path);
|
// this.batchFileChange = this.batchFileChange.filter((e) => e != file.path);
|
||||||
const xf = this.app.vault.getAbstractFileByPath(file.path) as TFile;
|
const xf = this.app.vault.getAbstractFileByPath(file.path) as TFile;
|
||||||
touch(xf);
|
touch(xf);
|
||||||
this.app.vault.trigger("modify", xf);
|
this.app.vault.trigger("modify", xf);
|
||||||
@@ -1455,7 +1534,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.statusBar.title = this.localDatabase.syncStatus;
|
this.statusBar.title = this.localDatabase.syncStatus;
|
||||||
let waiting = "";
|
let waiting = "";
|
||||||
if (this.settings.batchSave) {
|
if (this.settings.batchSave) {
|
||||||
waiting = " " + this.batchFileChange.map((e) => "🛫").join("");
|
waiting = " " + this.watchedFileEventQueue.map((e) => "🛫").join("");
|
||||||
waiting = waiting.replace(/(🛫){10}/g, "🚀");
|
waiting = waiting.replace(/(🛫){10}/g, "🚀");
|
||||||
}
|
}
|
||||||
let queued = "";
|
let queued = "";
|
||||||
@@ -1528,17 +1607,23 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async initializeDatabase(showingNotice?: boolean) {
|
async initializeDatabase(showingNotice?: boolean) {
|
||||||
|
this.isReady = false;
|
||||||
if (await this.openDatabase()) {
|
if (await this.openDatabase()) {
|
||||||
if (this.localDatabase.isReady) {
|
if (this.localDatabase.isReady) {
|
||||||
await this.syncAllFiles(showingNotice);
|
await this.syncAllFiles(showingNotice);
|
||||||
}
|
}
|
||||||
|
this.isReady = true;
|
||||||
|
// run queued event once.
|
||||||
|
await this.procFileEvent(true);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
this.isReady = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async replicateAllToServer(showingNotice?: boolean) {
|
async replicateAllToServer(showingNotice?: boolean) {
|
||||||
|
if (!this.isReady) return false;
|
||||||
if (this.settings.autoSweepPlugins) {
|
if (this.settings.autoSweepPlugins) {
|
||||||
await this.sweepPlugin(showingNotice);
|
await this.sweepPlugin(showingNotice);
|
||||||
}
|
}
|
||||||
@@ -1627,9 +1712,14 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
});
|
});
|
||||||
if (!initialScan) {
|
if (!initialScan) {
|
||||||
await runAll("UPDATE STORAGE", onlyInDatabase, async (e) => {
|
await runAll("UPDATE STORAGE", onlyInDatabase, async (e) => {
|
||||||
Logger(`Check or pull from db:${e}`);
|
const w = await this.localDatabase.getDBEntryMeta(e);
|
||||||
await this.pullFile(e, filesStorage, false, null, false);
|
if (w) {
|
||||||
Logger(`Check or pull from db:${e} OK`);
|
Logger(`Check or pull from db:${e}`);
|
||||||
|
await this.pullFile(e, filesStorage, false, null, false);
|
||||||
|
Logger(`Check or pull from db:${e} OK`);
|
||||||
|
} else {
|
||||||
|
Logger(`entry not found, maybe deleted:${e}`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!initialScan) {
|
if (!initialScan) {
|
||||||
@@ -1701,23 +1791,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async renameFolder(folder: TFolder, oldFile: any) {
|
|
||||||
for (const v of folder.children) {
|
|
||||||
const entry = v as TFile & TFolder;
|
|
||||||
if (entry.children) {
|
|
||||||
await this.deleteFolderOnDB(entry);
|
|
||||||
if (this.settings.trashInsteadDelete) {
|
|
||||||
await this.app.vault.trash(entry, false);
|
|
||||||
} else {
|
|
||||||
await this.app.vault.delete(entry);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await this.deleteFromDB(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --> conflict resolving
|
// --> conflict resolving
|
||||||
async getConflictedDoc(path: string, rev: string): Promise<false | diff_result_leaf> {
|
async getConflictedDoc(path: string, rev: string): Promise<false | diff_result_leaf> {
|
||||||
try {
|
try {
|
||||||
@@ -1974,20 +2047,30 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateIntoDB(file: TFile, initialScan?: boolean) {
|
async updateIntoDB(file: TFile, initialScan?: boolean, cache?: CacheData) {
|
||||||
if (!this.isTargetFile(file)) return;
|
if (!this.isTargetFile(file)) return;
|
||||||
if (shouldBeIgnored(file.path)) {
|
if (shouldBeIgnored(file.path)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let content = "";
|
let content = "";
|
||||||
let datatype: "plain" | "newnote" = "newnote";
|
let datatype: "plain" | "newnote" = "newnote";
|
||||||
if (!isPlainText(file.name)) {
|
if (!cache) {
|
||||||
const contentBin = await this.app.vault.readBinary(file);
|
if (!isPlainText(file.name)) {
|
||||||
content = await arrayBufferToBase64(contentBin);
|
const contentBin = await this.app.vault.readBinary(file);
|
||||||
datatype = "newnote";
|
content = await arrayBufferToBase64(contentBin);
|
||||||
|
datatype = "newnote";
|
||||||
|
} else {
|
||||||
|
content = await this.app.vault.read(file);
|
||||||
|
datatype = "plain";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
content = await this.app.vault.read(file);
|
if (cache instanceof ArrayBuffer) {
|
||||||
datatype = "plain";
|
content = await arrayBufferToBase64(cache);
|
||||||
|
datatype = "newnote"
|
||||||
|
} else {
|
||||||
|
content = cache;
|
||||||
|
datatype = "plain";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const fullPath = path2id(file.path);
|
const fullPath = path2id(file.path);
|
||||||
const d: LoadedEntry = {
|
const d: LoadedEntry = {
|
||||||
|
|||||||
23
updates.md
23
updates.md
@@ -6,8 +6,12 @@ I appreciate for reviewing and giving me advice @Pouhon158!
|
|||||||
|
|
||||||
#### Minors
|
#### Minors
|
||||||
- 0.15.1 Missed the stylesheet.
|
- 0.15.1 Missed the stylesheet.
|
||||||
- 0.15.2 The wizard has been improved and documentated!
|
- 0.15.2 The wizard has been improved and documented!
|
||||||
- 0.15.3 Fixed the issue about locking/unlocking remote database while rebuilding in the wizard.
|
- 0.15.3 Fixed the issue about locking/unlocking remote database while rebuilding in the wizard.
|
||||||
|
- 0.15.4 Fixed issues about asynchronous processing (e.g., Conflict check or hidden file detection)
|
||||||
|
- 0.15.5 Add new features for setting Self-hosted LiveSync up more easier.
|
||||||
|
- 0.15.6 File tracking logic has been refined.
|
||||||
|
- 0.15.7 Fixed bug about renaming file.
|
||||||
|
|
||||||
### 0.14.1
|
### 0.14.1
|
||||||
- The target selecting filter was implemented.
|
- The target selecting filter was implemented.
|
||||||
@@ -33,20 +37,5 @@ I appreciate for reviewing and giving me advice @Pouhon158!
|
|||||||
- 0.14.6 Change Target to ES2018
|
- 0.14.6 Change Target to ES2018
|
||||||
- 0.14.7 Refactor and fix typos.
|
- 0.14.7 Refactor and fix typos.
|
||||||
- 0.14.8 Refactored again. There should be no change in behaviour, but please let me know if there is any.
|
- 0.14.8 Refactored again. There should be no change in behaviour, but please let me know if there is any.
|
||||||
### 0.13.0
|
|
||||||
|
|
||||||
- The metadata of the deleted files will be kept on the database by default. If you want to delete this as the previous version, please turn on `Delete metadata of deleted files.`. And, if you have upgraded from the older version, please ensure every device has been upgraded.
|
... To continue on to `updates_old.md`.
|
||||||
- Please turn on `Delete metadata of deleted files.` if you are using livesync-classroom or filesystem-livesync.
|
|
||||||
- We can see the history of deleted files.
|
|
||||||
- `Pick file to show` was renamed to `Pick a file to show.
|
|
||||||
- Files in the `Pick a file to show` are now ordered by their modified date descent.
|
|
||||||
- Update information became to be shown on the major upgrade.
|
|
||||||
|
|
||||||
#### Minors
|
|
||||||
- 0.13.1 Fixed on conflict resolution.
|
|
||||||
- 0.13.2 Fixed file deletion failures.
|
|
||||||
- 0.13.4
|
|
||||||
- Now, we can synchronise hidden files that conflicted on each devices.
|
|
||||||
- We can search for conflicting docs.
|
|
||||||
- Pending processes can now be run at any time.
|
|
||||||
- Performance improved on synchronising large numbers of files at once.
|
|
||||||
17
updates_old.md
Normal file
17
updates_old.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
### 0.13.0
|
||||||
|
|
||||||
|
- The metadata of the deleted files will be kept on the database by default. If you want to delete this as the previous version, please turn on `Delete metadata of deleted files.`. And, if you have upgraded from the older version, please ensure every device has been upgraded.
|
||||||
|
- Please turn on `Delete metadata of deleted files.` if you are using livesync-classroom or filesystem-livesync.
|
||||||
|
- We can see the history of deleted files.
|
||||||
|
- `Pick file to show` was renamed to `Pick a file to show.
|
||||||
|
- Files in the `Pick a file to show` are now ordered by their modified date descent.
|
||||||
|
- Update information became to be shown on the major upgrade.
|
||||||
|
|
||||||
|
#### Minors
|
||||||
|
- 0.13.1 Fixed on conflict resolution.
|
||||||
|
- 0.13.2 Fixed file deletion failures.
|
||||||
|
- 0.13.4
|
||||||
|
- Now, we can synchronise hidden files that conflicted on each devices.
|
||||||
|
- We can search for conflicting docs.
|
||||||
|
- Pending processes can now be run at any time.
|
||||||
|
- Performance improved on synchronising large numbers of files at once.
|
||||||
Reference in New Issue
Block a user