mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2025-12-21 13:41:29 +00:00
New feature:
- Bootup sequence prevention implemented. Touched the docs up:
This commit is contained in:
@@ -81,6 +81,7 @@ Synchronization status is shown in statusbar.
|
|||||||
- When synchronized, files are compared by their modified times and overwritten by the newer ones once. Then plugin checks the conflicts and if a merge is needed, the dialog will open.
|
- When synchronized, files are compared by their modified times and overwritten by the newer ones once. Then plugin checks the conflicts and if a merge is needed, the dialog will open.
|
||||||
- Rarely, the file in the database would be broken. The plugin will not write storage when it looks broken, so some old files must be on your device. If you edit the file, it will be cured. But if the file does not exist on any device, can not rescue it. So you can delete these items from the setting dialog.
|
- Rarely, the file in the database would be broken. The plugin will not write storage when it looks broken, so some old files must be on your device. If you edit the file, it will be cured. But if the file does not exist on any device, can not rescue it. So you can delete these items from the setting dialog.
|
||||||
- If your database looks corrupted, try "Drop History". Usually, It is the easiest way.
|
- If your database looks corrupted, try "Drop History". Usually, It is the easiest way.
|
||||||
|
- To stop the bootup sequence for fixing problems on databases, you can put `redflag.md` on top of your vault.
|
||||||
- Q: Database is growing, how can I shrink it up?
|
- Q: Database is growing, how can I shrink it up?
|
||||||
A: each of the docs is saved with their old 100 revisions to detect and resolve confliction. Picture yourself that one device has been off the line for a while, and joined again. The device has to check his note and remote saved note. If exists in revision histories of remote notes even though the device's note is a little different from the latest one, it could be merged safely. Even if that is not in revision histories, we only have to check differences after the revision that both devices commonly have. This is like The git's conflict resolving method. So, We have to make the database again like an enlarged git repo if you want to solve the root of the problem.
|
A: each of the docs is saved with their old 100 revisions to detect and resolve confliction. Picture yourself that one device has been off the line for a while, and joined again. The device has to check his note and remote saved note. If exists in revision histories of remote notes even though the device's note is a little different from the latest one, it could be merged safely. Even if that is not in revision histories, we only have to check differences after the revision that both devices commonly have. This is like The git's conflict resolving method. So, We have to make the database again like an enlarged git repo if you want to solve the root of the problem.
|
||||||
- And more technical Information are in the [Technical Information](docs/tech_info.md)
|
- And more technical Information are in the [Technical Information](docs/tech_info.md)
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ Self-hosted LiveSync用にWebClipperも作りました。Chrome Web Storeから
|
|||||||
- ファイルは同期された後、タイムスタンプを比較して新しければいったん新しい方で上書きされます。その後、衝突が発生したかによって、マージが行われます。
|
- ファイルは同期された後、タイムスタンプを比較して新しければいったん新しい方で上書きされます。その後、衝突が発生したかによって、マージが行われます。
|
||||||
- まれにファイルが破損することがあります。破損したファイルに関してはディスクへの反映を試みないため、実際には使用しているデバイスには少し古いファイルが残っていることが多いです。そのファイルを再度更新してもらうと、データベースが更新されて問題なくなるケースがあります。ファイルがどの端末にも存在しない場合は、設定画面から、削除できます。
|
- まれにファイルが破損することがあります。破損したファイルに関してはディスクへの反映を試みないため、実際には使用しているデバイスには少し古いファイルが残っていることが多いです。そのファイルを再度更新してもらうと、データベースが更新されて問題なくなるケースがあります。ファイルがどの端末にも存在しない場合は、設定画面から、削除できます。
|
||||||
- データベースが変。そういうときは、いったんデータベースをDrop Historyのapply and sendで再初期化してみてください。だいたい直ります。
|
- データベースが変。そういうときは、いったんデータベースをDrop Historyのapply and sendで再初期化してみてください。だいたい直ります。
|
||||||
|
- データベースの復旧中に再起動した場合など、うまくローカルデータベースを修正できない際には、Vaultのトップに`redflag.md`というファイルを置いてください。起動時のシーケンスがスキップされます。
|
||||||
- データベースが大きくなってきてるんだけど、小さくできる?→各ノートは、それぞれの古い100リビジョンとともに保存されています。例えば、しばらくオフラインだったあるデバイスが、久しぶりに同期したと想定してみてください。そのとき、そのデバイスは最新とは少し異なるリビジョンを持ってるはずです。その場合でも、リモートのリビジョン履歴にリモートのものが存在した場合、安全にマージできます。もしリビジョン履歴に存在しなかった場合、確認しなければいけない差分も、対象を存在して持っている共通のリビジョン以降のみに絞れます。ちょうどGitのような方法で、衝突を解決している形になるのです。そのため、肥大化したリポジトリの解消と同様に、本質的にデータベースを小さくしたい場合は、データベースの作り直しが必要です。
|
- データベースが大きくなってきてるんだけど、小さくできる?→各ノートは、それぞれの古い100リビジョンとともに保存されています。例えば、しばらくオフラインだったあるデバイスが、久しぶりに同期したと想定してみてください。そのとき、そのデバイスは最新とは少し異なるリビジョンを持ってるはずです。その場合でも、リモートのリビジョン履歴にリモートのものが存在した場合、安全にマージできます。もしリビジョン履歴に存在しなかった場合、確認しなければいけない差分も、対象を存在して持っている共通のリビジョン以降のみに絞れます。ちょうどGitのような方法で、衝突を解決している形になるのです。そのため、肥大化したリポジトリの解消と同様に、本質的にデータベースを小さくしたい場合は、データベースの作り直しが必要です。
|
||||||
- その他の技術的なお話は、[技術的な内容](docs/tech_info_ja.md)に書いてあります。
|
- その他の技術的なお話は、[技術的な内容](docs/tech_info_ja.md)に書いてあります。
|
||||||
|
|
||||||
|
|||||||
@@ -212,8 +212,11 @@ The remote database indicates that has been unlocked Pattern 1.
|
|||||||
When you mark all devices as resolved, you can unlock the database.
|
When you mark all devices as resolved, you can unlock the database.
|
||||||
But, there's no problem even if you leave it as it is.
|
But, there's no problem even if you leave it as it is.
|
||||||
|
|
||||||
### Reread all files
|
### Verify and repair all files
|
||||||
Reread all files in the vault, and update them into the database if there's diff or could not read from the database.
|
read all files in the vault, and update them into the database if there's diff or could not read from the database.
|
||||||
|
|
||||||
|
### Sanity check
|
||||||
|
Make sure that all the files on the local database have all chunks.
|
||||||
|
|
||||||
### Drop history
|
### Drop history
|
||||||
Drop all histories on the local database and the remote database, and initialize When synchronization time has been prolonged to the new device or new vault, or database size became to be much larger. Try this.
|
Drop all histories on the local database and the remote database, and initialize When synchronization time has been prolonged to the new device or new vault, or database size became to be much larger. Try this.
|
||||||
|
|||||||
@@ -142,12 +142,34 @@ Self-hosted LiveSyncは通常、フォルダ内のファイルがすべて削除
|
|||||||
### Use newer file if conflicted (beta)
|
### Use newer file if conflicted (beta)
|
||||||
競合が発生したとき、常に新しいファイルを使用して競合を自動的に解決します。
|
競合が発生したとき、常に新しいファイルを使用して競合を自動的に解決します。
|
||||||
|
|
||||||
|
### Advanced settings
|
||||||
|
Self-hosted LiveSyncはPouchDBを使用し、リモートと[このプロトコル](https://docs.couchdb.org/en/stable/replication/protocol.html)で同期しています。
|
||||||
|
そのため、全てのノートなどはデータベースが許容するペイロードサイズやドキュメントサイズに併せてチャンクに分割されています。
|
||||||
|
|
||||||
|
しかしながら、それだけでは不十分なケースがあり、[Replicate Changes](https://docs.couchdb.org/en/stable/replication/protocol.html#replicate-changes)の[2.4.2.5.2. Upload Batch of Changed Documents](https://docs.couchdb.org/en/stable/replication/protocol.html#upload-batch-of-changed-documents)を参照すると、このリクエストは巨大になる可能性がありました。
|
||||||
|
|
||||||
|
残念ながら、このサイズを呼び出しごとに自動的に調整する方法はありません。
|
||||||
|
そのため、設定を変更できるように機能追加いたしました。
|
||||||
|
|
||||||
|
備考:もし小さな値を設定した場合、リクエスト数は増えます。
|
||||||
|
もしサーバから遠い場合、トータルのスループットは遅くなり、転送量は増えます。
|
||||||
|
|
||||||
|
### Batch size
|
||||||
|
一度に処理するChange feedの数です。デフォルトは250です。
|
||||||
|
|
||||||
|
### Batch limit
|
||||||
|
一度に処理するBatchの数です。デフォルトは40です。
|
||||||
|
|
||||||
## Miscellaneous
|
## Miscellaneous
|
||||||
その他の設定です
|
その他の設定です
|
||||||
### Show status inside editor
|
### Show status inside editor
|
||||||
同期の情報をエディター内に表示します。
|
同期の情報をエディター内に表示します。
|
||||||
モバイルで便利です。
|
モバイルで便利です。
|
||||||
|
|
||||||
|
### Check integrity on saving
|
||||||
|
保存時にデータが全て保存できたかチェックを行います。
|
||||||
|
|
||||||
|
|
||||||
## Hatch
|
## Hatch
|
||||||
ここから先は、困ったときに開ける蓋の中身です。注意して使用してください。
|
ここから先は、困ったときに開ける蓋の中身です。注意して使用してください。
|
||||||
|
|
||||||
@@ -166,9 +188,12 @@ Self-hosted LiveSyncは通常、フォルダ内のファイルがすべて削除
|
|||||||
ご使用のすべてのデバイスでロックを解除した場合は、データベースのロックを解除することができます。
|
ご使用のすべてのデバイスでロックを解除した場合は、データベースのロックを解除することができます。
|
||||||
ただし、このまま放置しても問題はありません。
|
ただし、このまま放置しても問題はありません。
|
||||||
|
|
||||||
### Reread all files
|
### Verify and repair all files
|
||||||
Vault内のファイルを全て読み込み直し、もし差分があったり、データベースから正常に読み込めなかったものに関して、データベースに反映します。
|
Vault内のファイルを全て読み込み直し、もし差分があったり、データベースから正常に読み込めなかったものに関して、データベースに反映します。
|
||||||
|
|
||||||
|
### Sanity check
|
||||||
|
ローカルデータベースに保存されている全てのファイルが正しくチャンクを持っていることを確認します。
|
||||||
|
|
||||||
### Drop history
|
### Drop history
|
||||||
データベースに記録されている履歴を削除し、データベースを初期化します。
|
データベースに記録されている履歴を削除し、データベースを初期化します。
|
||||||
新しい端末や新しいVaultへの同期にやたらと時間がかかったり、データベースサイズが肥大化したりしてきた際に使用してください。
|
新しい端末や新しいVaultへの同期にやたらと時間がかかったり、データベースサイズが肥大化したりしてきた際に使用してください。
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-livesync",
|
"id": "obsidian-livesync",
|
||||||
"name": "Self-hosted LiveSync",
|
"name": "Self-hosted LiveSync",
|
||||||
"version": "0.3.1",
|
"version": "0.4.0",
|
||||||
"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.3.1",
|
"version": "0.4.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.3.1",
|
"version": "0.4.0",
|
||||||
"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.3.1",
|
"version": "0.4.0",
|
||||||
"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",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -635,6 +635,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
c.addClass("op-warn");
|
c.addClass("op-warn");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const hatchWarn = containerHatchEl.createEl("div", { text: `To stop the bootup sequence for fixing problems on databases, you can put redflag.md on top of your vault (Rebooting obsidian is required).` });
|
||||||
|
hatchWarn.addClass("op-warn");
|
||||||
const dropHistory = async (sendToServer: boolean) => {
|
const dropHistory = async (sendToServer: boolean) => {
|
||||||
this.plugin.settings.liveSync = false;
|
this.plugin.settings.liveSync = false;
|
||||||
this.plugin.settings.periodicReplication = false;
|
this.plugin.settings.periodicReplication = false;
|
||||||
@@ -763,6 +765,10 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
containerHatchEl.createEl("div", {
|
||||||
|
text: sanitizeHTMLToDom(`Advanced buttons<br>
|
||||||
|
These buttons could break your database easily.`),
|
||||||
|
});
|
||||||
new Setting(containerHatchEl)
|
new Setting(containerHatchEl)
|
||||||
.setName("Reset remote database")
|
.setName("Reset remote database")
|
||||||
.setDesc("Reset remote database, this affects only database. If you replicate again, remote database will restored by local database.")
|
.setDesc("Reset remote database, this affects only database. If you replicate again, remote database will restored by local database.")
|
||||||
|
|||||||
58
src/main.ts
58
src/main.ts
@@ -1,8 +1,24 @@
|
|||||||
import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, PluginManifest } from "obsidian";
|
import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, PluginManifest } from "obsidian";
|
||||||
import { diff_match_patch } from "diff-match-patch";
|
import { diff_match_patch } from "diff-match-patch";
|
||||||
|
|
||||||
import { EntryDoc, LoadedEntry, ObsidianLiveSyncSettings, diff_check_result, diff_result_leaf, EntryBody, PluginDataEntry, LOG_LEVEL, VER, PERIODIC_PLUGIN_SWEEP, DEFAULT_SETTINGS, PluginList, DevicePluginList, diff_result } from "./types";
|
import {
|
||||||
import { base64ToString, arrayBufferToBase64, base64ToArrayBuffer, isValidPath, versionNumberString2Number, id2path, path2id, runWithLock } from "./utils";
|
EntryDoc,
|
||||||
|
LoadedEntry,
|
||||||
|
ObsidianLiveSyncSettings,
|
||||||
|
diff_check_result,
|
||||||
|
diff_result_leaf,
|
||||||
|
EntryBody,
|
||||||
|
PluginDataEntry,
|
||||||
|
LOG_LEVEL,
|
||||||
|
VER,
|
||||||
|
PERIODIC_PLUGIN_SWEEP,
|
||||||
|
DEFAULT_SETTINGS,
|
||||||
|
PluginList,
|
||||||
|
DevicePluginList,
|
||||||
|
diff_result,
|
||||||
|
FLAGMD_REDFLAG,
|
||||||
|
} from "./types";
|
||||||
|
import { base64ToString, arrayBufferToBase64, base64ToArrayBuffer, isValidPath, versionNumberString2Number, id2path, path2id, runWithLock, shouldBeIgnored } from "./utils";
|
||||||
import { Logger, setLogger } from "./logger";
|
import { Logger, setLogger } from "./logger";
|
||||||
import { LocalPouchDB } from "./LocalPouchDB";
|
import { LocalPouchDB } from "./LocalPouchDB";
|
||||||
import { LogDisplayModal } from "./LogDisplayModal";
|
import { LogDisplayModal } from "./LogDisplayModal";
|
||||||
@@ -22,6 +38,13 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.registerInterval(timer);
|
this.registerInterval(timer);
|
||||||
return timer;
|
return timer;
|
||||||
}
|
}
|
||||||
|
isRedFlagRaised(): boolean {
|
||||||
|
const redflag = this.app.vault.getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG));
|
||||||
|
if (redflag != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
async onload() {
|
async onload() {
|
||||||
setLogger(this.addLog.bind(this)); // Logger moved to global.
|
setLogger(this.addLog.bind(this)); // Logger moved to global.
|
||||||
Logger("loading plugin");
|
Logger("loading plugin");
|
||||||
@@ -90,7 +113,27 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
|
|
||||||
this.app.workspace.onLayoutReady(async () => {
|
this.app.workspace.onLayoutReady(async () => {
|
||||||
try {
|
try {
|
||||||
await this.initializeDatabase();
|
if (this.isRedFlagRaised()) {
|
||||||
|
this.settings.batchSave = false;
|
||||||
|
this.settings.liveSync = false;
|
||||||
|
this.settings.periodicReplication = false;
|
||||||
|
this.settings.syncOnSave = false;
|
||||||
|
this.settings.syncOnStart = false;
|
||||||
|
this.settings.syncOnFileOpen = false;
|
||||||
|
this.settings.autoSweepPlugins = false;
|
||||||
|
this.settings.usePluginSync = false;
|
||||||
|
this.settings.suspendFileWatching = true;
|
||||||
|
await this.saveSettings();
|
||||||
|
await this.openDatabase();
|
||||||
|
const warningMessage = "The red flag is raised! The whole initialize steps are skipped, and any file changes are not captured.";
|
||||||
|
Logger(warningMessage, LOG_LEVEL.NOTICE);
|
||||||
|
this.setStatusBarText(warningMessage);
|
||||||
|
} else {
|
||||||
|
if (this.settings.suspendFileWatching) {
|
||||||
|
Logger("'Suspend file watching' turned on. Are you sure this is what you intended? Every modification on the vault will be ignored.", LOG_LEVEL.NOTICE);
|
||||||
|
}
|
||||||
|
await this.initializeDatabase();
|
||||||
|
}
|
||||||
await this.realizeSettingSyncMode();
|
await this.realizeSettingSyncMode();
|
||||||
this.registerWatchEvents();
|
this.registerWatchEvents();
|
||||||
if (this.settings.syncOnStart) {
|
if (this.settings.syncOnStart) {
|
||||||
@@ -470,6 +513,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
|
|
||||||
async doc2storage_create(docEntry: EntryBody, force?: boolean) {
|
async doc2storage_create(docEntry: EntryBody, force?: boolean) {
|
||||||
const pathSrc = id2path(docEntry._id);
|
const pathSrc = id2path(docEntry._id);
|
||||||
|
if (shouldBeIgnored(pathSrc)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const doc = await this.localDatabase.getDBEntry(pathSrc, { rev: docEntry._rev });
|
const doc = await this.localDatabase.getDBEntry(pathSrc, { rev: docEntry._rev });
|
||||||
if (doc === false) return;
|
if (doc === false) return;
|
||||||
const path = id2path(doc._id);
|
const path = id2path(doc._id);
|
||||||
@@ -527,6 +573,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
async doc2storate_modify(docEntry: EntryBody, file: TFile, force?: boolean) {
|
async doc2storate_modify(docEntry: EntryBody, file: TFile, force?: boolean) {
|
||||||
const pathSrc = id2path(docEntry._id);
|
const pathSrc = id2path(docEntry._id);
|
||||||
|
if (shouldBeIgnored(pathSrc)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (docEntry._deleted) {
|
if (docEntry._deleted) {
|
||||||
//basically pass.
|
//basically pass.
|
||||||
//but if there're no docs left, delete file.
|
//but if there're no docs left, delete file.
|
||||||
@@ -1141,6 +1190,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateIntoDB(file: TFile) {
|
async updateIntoDB(file: TFile) {
|
||||||
|
if (shouldBeIgnored(file.path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
await this.localDatabase.waitForGCComplete();
|
await this.localDatabase.waitForGCComplete();
|
||||||
let content = "";
|
let content = "";
|
||||||
let datatype: "plain" | "newnote" = "newnote";
|
let datatype: "plain" | "newnote" = "newnote";
|
||||||
|
|||||||
@@ -226,3 +226,5 @@ export interface PluginList {
|
|||||||
export interface DevicePluginList {
|
export interface DevicePluginList {
|
||||||
[key: string]: PluginDataEntry;
|
[key: string]: PluginDataEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const FLAGMD_REDFLAG = "redflag.md";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { normalizePath } from "obsidian";
|
import { normalizePath } from "obsidian";
|
||||||
import { Logger } from "./logger";
|
import { Logger } from "./logger";
|
||||||
import { LOG_LEVEL } from "./types";
|
import { FLAGMD_REDFLAG, LOG_LEVEL } from "./types";
|
||||||
|
|
||||||
export function arrayBufferToBase64(buffer: ArrayBuffer): Promise<string> {
|
export function arrayBufferToBase64(buffer: ArrayBuffer): Promise<string> {
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
@@ -79,6 +79,13 @@ export function isValidPath(filename: string): boolean {
|
|||||||
return sx == filename;
|
return sx == filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shouldBeIgnored(filename: string): boolean {
|
||||||
|
if (filename == FLAGMD_REDFLAG) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
export function versionNumberString2Number(version: string): number {
|
export function versionNumberString2Number(version: string): number {
|
||||||
return version // "1.23.45"
|
return version // "1.23.45"
|
||||||
.split(".") // 1 23 45
|
.split(".") // 1 23 45
|
||||||
|
|||||||
Reference in New Issue
Block a user