Compare commits

..

21 Commits

Author SHA1 Message Date
vorotamoroz
3a3f79bb99 bump 2023-03-17 17:50:53 +09:00
vorotamoroz
9efb6ed0c1 Fixed:
- Now periodic internal file scanning works well.
- The handler of Window-visibility-changed has been fixed.
- And minor fixes possibly included.
Refactored:
- Unused logic has been removed.
- Some utility functions have been moved into suitable files.
- Function names have been renamed.
2023-03-17 17:48:24 +09:00
vorotamoroz
6b7956ab67 bump 2023-03-14 19:03:28 +09:00
vorotamoroz
58196c2423 Fixed:
- Now `redflag3` can be run surely.
- Synchronisation can now be aborted.
2023-03-14 19:02:57 +09:00
vorotamoroz
3940260d42 bump 2023-03-02 12:56:59 +09:00
vorotamoroz
b16333c604 Implemented:
- `Resolve all conflicted files` has been implemented.
Fixed:
- Fixed a problem about reading chunks online when a file has more chunks than the concurrency limit.
Rollbacked:
- Logs are kept only for 100 lines, again.
2023-03-02 12:54:41 +09:00
vorotamoroz
7bf6d1f663 update dependencies 2023-03-02 12:51:46 +09:00
vorotamoroz
7046928068 bump 2023-03-01 12:59:48 +09:00
vorotamoroz
333fcbaaeb - Fixed:
- Requests of reading chunks online are now split into a reasonable(and configurable) size.
    - No longer error message will be shown on Linux devices with hidden file synchronisation.
  - Improved:
    - The interval of reading chunks online is now configurable.
    - Boot sequence has been speeded up, more.
  - Misc:
    - Messages on the boot sequence will now be more detailed. If you want to see them, please enable the verbose log.
    - Logs became be kept for 1000 lines while the verbose log is enabled.
2023-03-01 12:58:29 +09:00
vorotamoroz
009f92c307 bump 2023-02-28 17:25:46 +09:00
vorotamoroz
3e541bd061 Fixed:
- Some messages have been refined.
- Boot sequence has been speeded up.
- Opening the local database multiple times in a short duration has been suppressed.
2023-02-28 17:15:43 +09:00
vorotamoroz
52d08301cc bump 2023-02-27 17:57:37 +09:00
vorotamoroz
49d4c239f2 Improved:
- Now, the filename of the conflicted settings will be shown on the merging dialogue
- The plugin data can be resolved when conflicted.
- The semaphore status display has been changed to count only.
- Applying to the storage will be concurrent with a few files.
2023-02-27 17:57:05 +09:00
vorotamoroz
748d031b36 bump 2023-02-21 09:13:19 +09:00
vorotamoroz
dbe77718c8 Urgent:
- The modified document will be reflected in the storage now.
2023-02-21 09:12:14 +09:00
vorotamoroz
f334974cc3 bump 2023-02-20 17:58:14 +09:00
vorotamoroz
8f2ae437c6 Fixed:
- Now reading error will be reported.
2023-02-20 17:54:57 +09:00
vorotamoroz
a0efda9e71 bump 2023-02-17 17:37:15 +09:00
vorotamoroz
be3d61c1c7 - New feature:
- If any conflicted files have been left, they will be reported.
- Fixed:
  - Now the name of the conflicting file is shown on the conflict-resolving dialogue.
  - Hidden files are now able to be merged again.
  - No longer error caused at plug-in being loaded.
- Improved:
  - Caching chunks are now limited in total size of cached chunks.
2023-02-17 17:35:06 +09:00
vorotamoroz
b24c4ef55b bump 2023-02-13 15:17:23 +09:00
vorotamoroz
ff850b48ca - Improved:
- Now we can preserve the logs into the file.
    - Note: This option will be enabled automatically also when we flagging a red flag.
  - File names can now be made platform-appropriate.
- Refactored:
  - Some redundant implementations have been sorted out.
2023-02-13 15:17:09 +09:00
19 changed files with 2404 additions and 1706 deletions

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-livesync",
"name": "Self-hosted LiveSync",
"version": "0.17.22",
"version": "0.17.32",
"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.",
"author": "vorotamoroz",

1941
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
"version": "0.17.22",
"version": "0.17.32",
"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",
"type": "module",
@@ -16,31 +16,31 @@
"@types/diff-match-patch": "^1.0.32",
"@types/pouchdb": "^6.4.0",
"@types/pouchdb-browser": "^6.1.3",
"@typescript-eslint/eslint-plugin": "^5.44.0",
"@typescript-eslint/parser": "^5.44.0",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"builtin-modules": "^3.3.0",
"esbuild": "0.15.15",
"esbuild-svelte": "^0.7.3",
"eslint": "^8.28.0",
"eslint": "^8.35.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-import": "^2.27.5",
"events": "^3.3.0",
"obsidian": "^0.16.3",
"postcss": "^8.4.19",
"obsidian": "^1.1.1",
"postcss": "^8.4.21",
"postcss-load-config": "^4.0.1",
"pouchdb-adapter-http": "^8.0.0",
"pouchdb-adapter-idb": "^8.0.0",
"pouchdb-adapter-indexeddb": "^8.0.0",
"pouchdb-core": "^8.0.0",
"pouchdb-find": "^8.0.0",
"pouchdb-mapreduce": "^8.0.0",
"pouchdb-replication": "^8.0.0",
"pouchdb-utils": "file:src/lib/src/patches/pouchdb-utils",
"svelte": "^3.53.1",
"svelte-preprocess": "^4.10.7",
"pouchdb-adapter-http": "^8.0.1",
"pouchdb-adapter-idb": "^8.0.1",
"pouchdb-adapter-indexeddb": "^8.0.1",
"pouchdb-core": "^8.0.1",
"pouchdb-find": "^8.0.1",
"pouchdb-mapreduce": "^8.0.1",
"pouchdb-replication": "^8.0.1",
"pouchdb-utils": "^8.0.1",
"svelte": "^3.55.1",
"svelte-preprocess": "^5.0.1",
"transform-pouch": "^2.0.0",
"tslib": "^2.4.1",
"typescript": "^4.9.3"
"tslib": "^2.5.0",
"typescript": "^4.9.5"
},
"dependencies": {
"diff-match-patch": "^1.0.5",

View File

@@ -1,4 +1,4 @@
import { App, Modal } from "obsidian";
import { App, Modal } from "./deps";
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT } from "diff-match-patch";
import { diff_result } from "./lib/src/types";
import { escapeStringToHTML } from "./lib/src/strbin";
@@ -22,7 +22,7 @@ export class ConflictResolveModal extends Modal {
contentEl.empty();
contentEl.createEl("h2", { text: "This document has conflicted changes." });
contentEl.createEl("span", this.filename);
contentEl.createEl("span", { text: this.filename });
const div = contentEl.createDiv("");
div.addClass("op-scrollable");
let diff = "";

View File

@@ -1,7 +1,6 @@
import { TFile, Modal, App } from "obsidian";
import { path2id } from "./utils";
import { TFile, Modal, App } from "./deps";
import { isValidPath, path2id } from "./utils";
import { base64ToArrayBuffer, base64ToString, escapeStringToHTML } from "./lib/src/strbin";
import { isValidPath } from "./lib/src/path";
import ObsidianLiveSyncPlugin from "./main";
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch";
import { LoadedEntry, LOG_LEVEL } from "./lib/src/types";

View File

@@ -1,4 +1,4 @@
import { App, Modal } from "obsidian";
import { App, Modal } from "./deps";
import { LoadedEntry } from "./lib/src/types";
import JsonResolvePane from "./JsonResolvePane.svelte";

View File

@@ -3,7 +3,7 @@
import type { LoadedEntry } from "./lib/src/types";
import { base64ToString } from "./lib/src/strbin";
import { getDocData } from "./lib/src/utils";
import { mergeObject } from "./utils";
import { id2path, mergeObject } from "./utils";
export let docs: LoadedEntry[] = [];
export let callback: (keepRev: string, mergedStr?: string) => Promise<void> = async (_, __) => {
@@ -93,9 +93,11 @@
diffs = getJsonDiff(objA, selectedObj);
console.dir(selectedObj);
}
$: filename = id2path(docA?._id ?? "");
</script>
<h1>File Conflicted</h1>
<h1>Conflicted settings</h1>
<div><span>{filename}</span></div>
{#if !docA || !docB}
<div class="message">Just for a minute, please!</div>
<div class="buttons">

View File

@@ -1,4 +1,4 @@
import { requestUrl, RequestUrlParam, RequestUrlResponse } from "obsidian";
import { requestUrl, RequestUrlParam, RequestUrlResponse } from "./deps";
import { KeyValueDatabase, OpenKeyValueDatabase } from "./KeyValueDB.js";
import { LocalPouchDBBase } from "./lib/src/LocalPouchDBBase.js";
import { Logger } from "./lib/src/logger.js";

View File

@@ -1,4 +1,4 @@
import { App, Modal } from "obsidian";
import { App, Modal } from "./deps";
import { logMessageStore } from "./lib/src/stores";
import { escapeStringToHTML } from "./lib/src/strbin";
import ObsidianLiveSyncPlugin from "./main";

View File

@@ -1,4 +1,4 @@
import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl, TextAreaComponent, MarkdownRenderer, stringifyYaml } from "obsidian";
import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl, TextAreaComponent, MarkdownRenderer, stringifyYaml } from "./deps";
import { DEFAULT_SETTINGS, LOG_LEVEL, ObsidianLiveSyncSettings, ConfigPassphraseStore, RemoteDBSettings } from "./lib/src/types";
import { path2id, id2path } from "./utils";
import { delay } from "./lib/src/utils";
@@ -28,6 +28,7 @@ const requestToCouchDB = async (baseUri: string, username: string, password: str
};
export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
plugin: ObsidianLiveSyncPlugin;
selectedScreen = "";
constructor(app: App, plugin: ObsidianLiveSyncPlugin) {
super(app, plugin);
@@ -93,6 +94,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
element.addClass("selected");
(element.querySelector("input[type=radio]") as HTMLInputElement).checked = true;
});
this.selectedScreen = screen;
};
menuTabs.forEach((element) => {
const e = element.querySelector(".sls-setting-tab");
@@ -453,6 +455,9 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.syncAfterMerge = false;
this.plugin.settings.syncInternalFiles = false;
this.plugin.settings.usePluginSync = false;
Logger("Hidden files and plugin synchronization have been temporarily disabled. Please enable them after the fetching, if you need them.", LOG_LEVEL.NOTICE)
await this.plugin.saveSettings();
applyDisplayEnabled();
@@ -1291,8 +1296,71 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
})
return toggle;
}
);
);
new Setting(containerSyncSettingEl)
.setName("A number of hashes to be cached")
.setDesc("")
.addText((text) => {
text.setPlaceholder("")
.setValue(this.plugin.settings.hashCacheMaxCount + "")
.onChange(async (value) => {
let v = Number(value);
if (isNaN(v) || v < 10) {
v = 10;
}
this.plugin.settings.hashCacheMaxCount = v;
await this.plugin.saveSettings();
});
text.inputEl.setAttribute("type", "number");
});
new Setting(containerSyncSettingEl)
.setName("The total length of hashes to be cached")
.setDesc("(Mega chars)")
.addText((text) => {
text.setPlaceholder("")
.setValue(this.plugin.settings.hashCacheMaxAmount + "")
.onChange(async (value) => {
let v = Number(value);
if (isNaN(v) || v < 1) {
v = 1;
}
this.plugin.settings.hashCacheMaxAmount = v;
await this.plugin.saveSettings();
});
text.inputEl.setAttribute("type", "number");
});
new Setting(containerSyncSettingEl)
.setName("The maximum number of reading chunks online concurrently")
.setDesc("")
.addText((text) => {
text.setPlaceholder("")
.setValue(this.plugin.settings.concurrencyOfReadChunksOnline + "")
.onChange(async (value) => {
let v = Number(value);
if (isNaN(v) || v < 10) {
v = 10;
}
this.plugin.settings.concurrencyOfReadChunksOnline = v;
await this.plugin.saveSettings();
});
text.inputEl.setAttribute("type", "number");
});
new Setting(containerSyncSettingEl)
.setName("The minimum interval for reading chunks online")
.setDesc("")
.addText((text) => {
text.setPlaceholder("")
.setValue(this.plugin.settings.minimumIntervalOfReadChunksOnline + "")
.onChange(async (value) => {
let v = Number(value);
if (isNaN(v) || v < 10) {
v = 10;
}
this.plugin.settings.minimumIntervalOfReadChunksOnline = v;
await this.plugin.saveSettings();
});
text.inputEl.setAttribute("type", "number");
});
addScreenElement("30", containerSyncSettingEl);
const containerMiscellaneousEl = containerEl.createDiv();
containerMiscellaneousEl.createEl("h3", { text: "Miscellaneous" });
@@ -1326,29 +1394,51 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
Logger("Select any preset.", LOG_LEVEL.NOTICE);
return;
}
this.plugin.settings.batchSave = false;
this.plugin.settings.liveSync = false;
this.plugin.settings.periodicReplication = false;
this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.syncAfterMerge = false;
const presetAllDisabled = {
batchSave: false,
liveSync: false,
periodicReplication: false,
syncOnSave: false,
syncOnStart: false,
syncOnFileOpen: false,
syncAfterMerge: false,
} as Partial<ObsidianLiveSyncSettings>;
const presetLiveSync = {
...presetAllDisabled,
liveSync: true
} as Partial<ObsidianLiveSyncSettings>;
const presetPeriodic = {
...presetAllDisabled,
batchSave: true,
periodicReplication: true,
syncOnSave: false,
syncOnStart: true,
syncOnFileOpen: true,
syncAfterMerge: true,
} as Partial<ObsidianLiveSyncSettings>;
if (currentPreset == "LIVESYNC") {
this.plugin.settings.liveSync = true;
this.plugin.settings = {
...this.plugin.settings,
...presetLiveSync
}
Logger("Synchronization setting configured as LiveSync.", LOG_LEVEL.NOTICE);
} else if (currentPreset == "PERIODIC") {
this.plugin.settings.batchSave = true;
this.plugin.settings.periodicReplication = true;
this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = true;
this.plugin.settings.syncOnFileOpen = true;
this.plugin.settings.syncAfterMerge = true;
this.plugin.settings = {
...this.plugin.settings,
...presetPeriodic
}
Logger("Synchronization setting configured as Periodic sync with batch database update.", LOG_LEVEL.NOTICE);
} else {
Logger("All synchronization disabled.", LOG_LEVEL.NOTICE);
this.plugin.settings = {
...this.plugin.settings,
...presetAllDisabled
}
}
this.plugin.saveSettings();
await this.plugin.realizeSettingSyncMode();
this.display();
if (inWizard) {
// @ts-ignore
this.plugin.app.setting.close()
@@ -1366,8 +1456,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
// @ts-ignore
this.plugin.app.commands.executeCommandById("obsidian-livesync:livesync-copysetupuri")
}
})
);
@@ -1538,6 +1626,15 @@ ${stringifyYaml(pluginConfig)}`;
await this.plugin.saveSettings();
})
);
new Setting(containerHatchEl)
.setName("Write logs into the file")
.setDesc("Warning! This will have a serious impact on performance. And the logs will not be synchronised under the default name. Please be careful with logs; they often contain your confidential information.")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.writeLogToTheFile).onChange(async (value) => {
this.plugin.settings.writeLogToTheFile = value;
await this.plugin.saveSettings();
})
);
new Setting(containerHatchEl)
.setName("Discard local database to reset or uninstall Self-hosted LiveSync")
@@ -1694,18 +1791,22 @@ ${stringifyYaml(pluginConfig)}`;
}
applyDisplayEnabled();
addScreenElement("70", containerCorruptedDataEl);
if (lastVersion != this.plugin.settings.lastReadUpdates) {
if (JSON.stringify(this.plugin.settings) != JSON.stringify(DEFAULT_SETTINGS)) {
changeDisplay("100");
if (this.selectedScreen == "") {
if (lastVersion != this.plugin.settings.lastReadUpdates) {
if (JSON.stringify(this.plugin.settings) != JSON.stringify(DEFAULT_SETTINGS)) {
changeDisplay("100");
} else {
changeDisplay("110")
}
} else {
changeDisplay("110")
if (isAnySyncEnabled()) {
changeDisplay("0");
} else {
changeDisplay("110")
}
}
} else {
if (isAnySyncEnabled()) {
changeDisplay("0");
} else {
changeDisplay("110")
}
changeDisplay(this.selectedScreen);
}
}
}

172
src/StorageEventManager.ts Normal file
View File

@@ -0,0 +1,172 @@
import { Plugin_2, TAbstractFile, TFile, TFolder } from "./deps";
import { isPlainText, shouldBeIgnored } from "./lib/src/path";
import { getGlobalStore } from "./lib/src/store";
import { ObsidianLiveSyncSettings } from "./lib/src/types";
import { FileEventItem, FileEventType, FileInfo, InternalFileInfo, queueItem } from "./types";
import { recentlyTouched } from "./utils";
export abstract class StorageEventManager {
abstract fetchEvent(): FileEventItem | false;
abstract cancelRelativeEvent(item: FileEventItem): void;
abstract getQueueLength(): number;
}
type LiveSyncForStorageEventManager = Plugin_2 &
{
settings: ObsidianLiveSyncSettings
} & {
isTargetFile: (file: string | TAbstractFile) => boolean,
procFileEvent: (applyBatch?: boolean) => Promise<boolean>
};
export class StorageEventManagerObsidian extends StorageEventManager {
plugin: LiveSyncForStorageEventManager;
queuedFilesStore = getGlobalStore("queuedFiles", { queuedItems: [] as queueItem[], fileEventItems: [] as FileEventItem[] });
watchedFileEventQueue = [] as FileEventItem[];
constructor(plugin: LiveSyncForStorageEventManager) {
super();
this.plugin = plugin;
this.watchVaultChange = this.watchVaultChange.bind(this);
this.watchVaultCreate = this.watchVaultCreate.bind(this);
this.watchVaultDelete = this.watchVaultDelete.bind(this);
this.watchVaultRename = this.watchVaultRename.bind(this);
this.watchVaultRawEvents = this.watchVaultRawEvents.bind(this);
plugin.registerEvent(app.vault.on("modify", this.watchVaultChange));
plugin.registerEvent(app.vault.on("delete", this.watchVaultDelete));
plugin.registerEvent(app.vault.on("rename", this.watchVaultRename));
plugin.registerEvent(app.vault.on("create", this.watchVaultCreate));
//@ts-ignore : Internal API
plugin.registerEvent(app.vault.on("raw", this.watchVaultRawEvents));
}
watchVaultCreate(file: TAbstractFile, ctx?: any) {
this.appendWatchEvent([{ type: "CREATE", file }], ctx);
}
watchVaultChange(file: TAbstractFile, ctx?: any) {
this.appendWatchEvent([{ type: "CHANGED", file }], ctx);
}
watchVaultDelete(file: TAbstractFile, ctx?: any) {
this.appendWatchEvent([{ type: "DELETE", file }], ctx);
}
watchVaultRename(file: TAbstractFile, oldFile: string, ctx?: any) {
if (file instanceof TFile) {
this.appendWatchEvent([
{ type: "CREATE", file },
{ type: "DELETE", file: { path: oldFile, mtime: file.stat.mtime, ctime: file.stat.ctime, size: file.stat.size, deleted: true } }
], ctx);
}
}
// Watch raw events (Internal API)
watchVaultRawEvents(path: string) {
if (!this.plugin.settings.syncInternalFiles) return;
if (!this.plugin.settings.watchInternalFileChanges) return;
if (!path.startsWith(app.vault.configDir)) return;
const ignorePatterns = this.plugin.settings.syncInternalFilesIgnorePatterns
.replace(/\n| /g, "")
.split(",").filter(e => e).map(e => new RegExp(e, "i"));
if (ignorePatterns.some(e => path.match(e))) return;
this.appendWatchEvent(
[{
type: "INTERNAL",
file: { path, mtime: 0, ctime: 0, size: 0 }
}], null);
}
// Cache file and waiting to can be proceed.
async appendWatchEvent(params: { type: FileEventType, file: TAbstractFile | InternalFileInfo, oldPath?: string }[], ctx?: any) {
let forcePerform = false;
for (const param of params) {
if (shouldBeIgnored(param.file.path)) {
continue;
}
const atomicKey = [0, 0, 0, 0, 0, 0].map(e => `${Math.floor(Math.random() * 100000)}`).join("-");
const type = param.type;
const file = param.file;
const oldPath = param.oldPath;
if (file instanceof TFolder) continue;
if (!this.plugin.isTargetFile(file.path)) continue;
if (this.plugin.settings.suspendFileWatching) continue;
let cache: null | string | ArrayBuffer;
// new file or something changed, cache the changes.
if (file instanceof TFile && (type == "CREATE" || type == "CHANGED")) {
if (recentlyTouched(file)) {
continue;
}
if (!isPlainText(file.name)) {
cache = await app.vault.readBinary(file);
} else {
// cache = await this.app.vault.read(file);
cache = await app.vault.cachedRead(file);
if (!cache) cache = await app.vault.read(file);
}
}
if (type == "DELETE" || type == "RENAME") {
forcePerform = true;
}
if (this.plugin.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;
L1:
while (i >= 0) {
i--;
if (i < 0) break L1;
if (this.watchedFileEventQueue[i].args.file.path != file.path) {
continue L1;
}
if (this.watchedFileEventQueue[i].type != type) break L1;
this.watchedFileEventQueue.remove(this.watchedFileEventQueue[i]);
//this.queuedFilesStore.set({ queuedItems: this.queuedFiles, fileEventItems: this.watchedFileEventQueue });
this.queuedFilesStore.apply((value) => ({ ...value, fileEventItems: this.watchedFileEventQueue }));
}
}
const fileInfo = file instanceof TFile ? {
ctime: file.stat.ctime,
mtime: file.stat.mtime,
file: file,
path: file.path,
size: file.stat.size
} as FileInfo : file as InternalFileInfo;
this.watchedFileEventQueue.push({
type,
args: {
file: fileInfo,
oldPath,
cache,
ctx
},
key: atomicKey
})
}
// this.queuedFilesStore.set({ queuedItems: this.queuedFiles, fileEventItems: this.watchedFileEventQueue });
this.queuedFilesStore.apply((value) => ({ ...value, fileEventItems: this.watchedFileEventQueue }));
this.plugin.procFileEvent(forcePerform);
}
fetchEvent(): FileEventItem | false {
if (this.watchedFileEventQueue.length == 0) return false;
const item = this.watchedFileEventQueue.shift();
this.queuedFilesStore.apply((value) => ({ ...value, fileEventItems: this.watchedFileEventQueue }));
return item;
}
cancelRelativeEvent(item: FileEventItem) {
this.watchedFileEventQueue = [...this.watchedFileEventQueue].filter(e => e.key != item.key);
this.queuedFilesStore.apply((value) => ({ ...value, fileEventItems: this.watchedFileEventQueue }));
}
getQueueLength() {
return this.watchedFileEventQueue.length;
}
}

4
src/deps.ts Normal file
View File

@@ -0,0 +1,4 @@
export {
addIcon, App, DataWriteOptions, debounce, Editor, FuzzySuggestModal, MarkdownRenderer, MarkdownView, Modal, normalizePath, Notice, Platform, Plugin, PluginManifest,
PluginSettingTab, Plugin_2, requestUrl, RequestUrlParam, RequestUrlResponse, sanitizeHTMLToDom, Setting, stringifyYaml, TAbstractFile, TextAreaComponent, TFile, TFolder
} from "obsidian";

View File

@@ -1,4 +1,4 @@
import { App, FuzzySuggestModal, Modal, Setting } from "obsidian";
import { App, FuzzySuggestModal, Modal, Setting } from "./deps";
import ObsidianLiveSyncPlugin from "./main";
//@ts-ignore

Submodule src/lib updated: 85061f0368...a929ee40cc

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
import { PluginManifest, TFile } from "obsidian";
import { PluginManifest, TFile } from "./deps";
import { DatabaseEntry, EntryBody } from "./lib/src/types";
export interface PluginDataEntry extends DatabaseEntry {
@@ -46,4 +46,28 @@ export type queueItem = {
timeout?: number;
done?: boolean;
warned?: boolean;
};
};
export type CacheData = string | ArrayBuffer;
export type FileEventType = "CREATE" | "DELETE" | "CHANGED" | "RENAME" | "INTERNAL";
export type FileEventArgs = {
file: FileInfo | InternalFileInfo;
cache?: CacheData;
oldPath?: string;
ctx?: any;
}
export type FileEventItem = {
type: FileEventType,
args: FileEventArgs,
key: string,
}
export const CHeader = "h:";
export const PSCHeader = "ps:";
export const PSCHeaderEnd = "ps;";
export const ICHeader = "i:";
export const ICHeaderEnd = "i;";
export const ICHeaderLength = ICHeader.length;
export const FileWatchEventQueueMax = 10;
export const configURIBase = "obsidian://setuplivesync?settings=";

View File

@@ -1,6 +1,10 @@
import { normalizePath } from "obsidian";
import { DataWriteOptions, normalizePath, TFile, Platform, TAbstractFile, App, Plugin_2 } from "./deps";
import { path2id_base, id2path_base, isValidFilenameInLinux, isValidFilenameInDarwin, isValidFilenameInWidows, isValidFilenameInAndroid } from "./lib/src/path";
import { path2id_base, id2path_base } from "./lib/src/path";
import { Logger } from "./lib/src/logger";
import { LOG_LEVEL } from "./lib/src/types";
import { CHeader, ICHeader, ICHeaderLength, PSCHeader } from "./types";
import { InputStringDialog, PopoverSelectString } from "./dialogs";
// For backward compatibility, using the path for determining id.
// Only CouchDB unacceptable ID (that starts with an underscore) has been prefixed with "/".
@@ -13,40 +17,44 @@ export function id2path(filename: string): string {
return id2path_base(normalizePath(filename));
}
const triggers: { [key: string]: ReturnType<typeof setTimeout> } = {};
export function setTrigger(key: string, timeout: number, proc: (() => Promise<any> | void)) {
clearTrigger(key);
triggers[key] = setTimeout(async () => {
delete triggers[key];
const tasks: { [key: string]: ReturnType<typeof setTimeout> } = {};
export function scheduleTask(key: string, timeout: number, proc: (() => Promise<any> | void)) {
cancelTask(key);
tasks[key] = setTimeout(async () => {
delete tasks[key];
await proc();
}, timeout);
}
export function clearTrigger(key: string) {
if (key in triggers) {
clearTimeout(triggers[key]);
export function cancelTask(key: string) {
if (key in tasks) {
clearTimeout(tasks[key]);
delete tasks[key];
}
}
export function clearAllTriggers() {
for (const v in triggers) {
clearTimeout(triggers[v]);
export function cancelAllTasks() {
for (const v in tasks) {
clearTimeout(tasks[v]);
delete tasks[v];
}
}
const intervals: { [key: string]: ReturnType<typeof setInterval> } = {};
export function setPeriodic(key: string, timeout: number, proc: (() => Promise<any> | void)) {
clearPeriodic(key);
export function setPeriodicTask(key: string, timeout: number, proc: (() => Promise<any> | void)) {
cancelPeriodicTask(key);
intervals[key] = setInterval(async () => {
delete intervals[key];
await proc();
}, timeout);
}
export function clearPeriodic(key: string) {
export function cancelPeriodicTask(key: string) {
if (key in intervals) {
clearInterval(intervals[key]);
delete intervals[key];
}
}
export function clearAllPeriodic() {
export function cancelAllPeriodicTask() {
for (const v in intervals) {
clearInterval(intervals[v]);
delete intervals[v];
}
}
@@ -259,4 +267,138 @@ export function flattenObject(obj: Record<string | number | symbol, any>, path:
ret.push(...p);
}
return ret;
}
}
export function modifyFile(file: TFile, data: string | ArrayBuffer, options?: DataWriteOptions) {
if (typeof (data) === "string") {
return app.vault.modify(file, data, options);
} else {
return app.vault.modifyBinary(file, data, options);
}
}
export function createFile(path: string, data: string | ArrayBuffer, options?: DataWriteOptions): Promise<TFile> {
if (typeof (data) === "string") {
return app.vault.create(path, data, options);
} else {
return app.vault.createBinary(path, data, options);
}
}
export function isValidPath(filename: string) {
if (Platform.isDesktop) {
// if(Platform.isMacOS) return isValidFilenameInDarwin(filename);
if (process.platform == "darwin") return isValidFilenameInDarwin(filename);
if (process.platform == "linux") return isValidFilenameInLinux(filename);
return isValidFilenameInWidows(filename);
}
if (Platform.isAndroidApp) return isValidFilenameInAndroid(filename);
if (Platform.isIosApp) return isValidFilenameInDarwin(filename);
//Fallback
Logger("Could not determine platform for checking filename", LOG_LEVEL.VERBOSE);
return isValidFilenameInWidows(filename);
}
let touchedFiles: string[] = [];
export function getAbstractFileByPath(path: string): TAbstractFile | null {
// Hidden API but so useful.
// @ts-ignore
if ("getAbstractFileByPathInsensitive" in app.vault && (app.vault.adapter?.insensitive ?? false)) {
// @ts-ignore
return app.vault.getAbstractFileByPathInsensitive(path);
} else {
return app.vault.getAbstractFileByPath(path);
}
}
export function trimPrefix(target: string, prefix: string) {
return target.startsWith(prefix) ? target.substring(prefix.length) : target;
}
export function touch(file: TFile | string) {
const f = file instanceof TFile ? file : getAbstractFileByPath(file) as TFile;
const key = `${f.path}-${f.stat.mtime}-${f.stat.size}`;
touchedFiles.unshift(key);
touchedFiles = touchedFiles.slice(0, 100);
}
export function recentlyTouched(file: TFile) {
const key = `${file.path}-${file.stat.mtime}-${file.stat.size}`;
if (touchedFiles.indexOf(key) == -1) return false;
return true;
}
export function clearTouched() {
touchedFiles = [];
}
/**
* returns is internal chunk of file
* @param str ID
* @returns
*/
export function isInternalMetadata(str: string): boolean {
return str.startsWith(ICHeader);
}
export function id2filenameInternalMetadata(str: string): string {
return str.substring(ICHeaderLength);
}
export function filename2idInternalMetadata(str: string): string {
return ICHeader + str;
}
// const CHeaderLength = CHeader.length;
export function isChunk(str: string): boolean {
return str.startsWith(CHeader);
}
export function isPluginMetadata(str: string): boolean {
return str.startsWith(PSCHeader);
}
export const askYesNo = (app: App, message: string): Promise<"yes" | "no"> => {
return new Promise((res) => {
const popover = new PopoverSelectString(app, message, null, null, (result) => res(result as "yes" | "no"));
popover.open();
});
};
export const askSelectString = (app: App, message: string, items: string[]): Promise<string> => {
const getItemsFun = () => items;
return new Promise((res) => {
const popover = new PopoverSelectString(app, message, "", getItemsFun, (result) => res(result));
popover.open();
});
};
export const askString = (app: App, title: string, key: string, placeholder: string): Promise<string | false> => {
return new Promise((res) => {
const dialog = new InputStringDialog(app, title, key, placeholder, (result) => res(result));
dialog.open();
});
};
export class PeriodicProcessor {
_process: () => Promise<any>;
_timer?: number;
_plugin: Plugin_2;
constructor(plugin: Plugin_2, process: () => Promise<any>) {
this._plugin = plugin;
this._process = process;
}
async process() {
try {
await this._process();
} catch (ex) {
Logger(ex);
}
}
enable(interval: number) {
this.disable();
if (interval == 0) return;
this._timer = window.setInterval(() => this._process().then(() => { }), interval);
this._plugin.registerInterval(this._timer);
}
disable() {
if (this._timer) clearInterval(this._timer);
}
}

View File

@@ -10,41 +10,53 @@
- Chunk ID numbering rules
#### Minors
- __0.17.1 to 0.17.15 has been moved into `update_old.md`__
- 0.17.16:
- __0.17.1 to 0.17.25 has been moved into `update_old.md`__
- 0.17.26
- Fixed(Urgent):
- The modified document will be reflected in the storage now.
- 0.17.27
- Improved:
- Plugins and their settings no longer need scanning if changes are monitored.
- Now synchronising plugins and their settings are performed parallelly and faster.
- We can place `redflag2.md` to rebuild the database automatically while the boot sequence.
- Experimental:
- We can use a new adapter on PouchDB. This will make us smoother.
- Note: Not compatible with the older version.
- Now, the filename of the conflicted settings will be shown on the merging dialogue
- The plugin data can be resolved when conflicted.
- The semaphore status display has been changed to count only.
- Applying to the storage will be concurrent with a few files.
- 0.17.28
-Fixed:
- Some messages have been refined.
- Boot sequence has been speeded up.
- Opening the local database multiple times in a short duration has been suppressed.
- Older migration logic.
- Note: If you have used 0.10.0 or lower and have not upgraded, you will need to run 0.17.27 or earlier once or reinstall Obsidian.
- 0.17.29
- Fixed:
- The default batch size is smaller again.
- Plugins and their setting can be synchronised again.
- Hidden files and plugins are correctly scanned while rebuilding.
- Files with the name started `_` are also being performed conflict-checking.
- 0.17.17
- Fixed: Now we can merge JSON files even if we failed to compare items like null.
- 0.17.18
- Fixed: Fixed lack of error handling.
- 0.17.19
- Fixed: Error reporting has been ensured.
- 0.17.20
- Improved: Changes of hidden files will be notified to Obsidian.
- 0.17.21
- Fixed: Skip patterns now handle capital letters.
- Improved
- New configuration to avoid exceeding throttle capacity.
- We have been grateful to @karasevm!
- The conflicted `data.json` is no longer merged automatically.
- This behaviour is not configurable, unlike the `Use newer file if conflicted` of normal files.
- 0.17.22
- Requests of reading chunks online are now split into a reasonable(and configurable) size.
- No longer error message will be shown on Linux devices with hidden file synchronisation.
- Improved:
- The interval of reading chunks online is now configurable.
- Boot sequence has been speeded up, more.
- Misc:
- Messages on the boot sequence will now be more detailed. If you want to see them, please enable the verbose log.
- Logs became be kept for 1000 lines while the verbose log is enabled.
- 0.17.30
- Implemented:
- `Resolve all conflicted files` has been implemented.
- Fixed:
- Now hidden files will not be synchronised while we are not configured.
- Some processes could start without waiting for synchronisation to complete, but now they will wait for.
- Improved
- Now, by placing `redflag3.md`, we can discard the local database and fetch again.
- Fixed a problem about reading chunks online when a file has more chunks than the concurrency limit.
- Rollbacked:
- Logs are kept only for 100 lines, again.
- 0.17.31
- Fixed:
- Now `redflag3` can be run surely.
- Synchronisation can now be aborted.
- Note: The synchronisation flow has been rewritten drastically. Please do not haste to inform me if you have noticed anything.
- 0.17.32
- Fixed:
- Now periodic internal file scanning works well.
- The handler of Window-visibility-changed has been fixed.
- And minor fixes possibly included.
- Refactored:
- Unused logic has been removed.
- Some utility functions have been moved into suitable files.
- Function names have been renamed.
... To continue on to `updates_old.md`.

View File

@@ -68,7 +68,60 @@
- Hidden files have been synchronised again.
- Rename of files has been fixed again.
And, minor changes have been included.
- 0.17.16:
- Improved:
- Plugins and their settings no longer need scanning if changes are monitored.
- Now synchronising plugins and their settings are performed parallelly and faster.
- We can place `redflag2.md` to rebuild the database automatically while the boot sequence.
- Experimental:
- We can use a new adapter on PouchDB. This will make us smoother.
- Note: Not compatible with the older version.
- Fixed:
- The default batch size is smaller again.
- Plugins and their setting can be synchronised again.
- Hidden files and plugins are correctly scanned while rebuilding.
- Files with the name started `_` are also being performed conflict-checking.
- 0.17.17
- Fixed: Now we can merge JSON files even if we failed to compare items like null.
- 0.17.18
- Fixed: Fixed lack of error handling.
- 0.17.19
- Fixed: Error reporting has been ensured.
- 0.17.20
- Improved: Changes of hidden files will be notified to Obsidian.
- 0.17.21
- Fixed: Skip patterns now handle capital letters.
- Improved
- New configuration to avoid exceeding throttle capacity.
- We have been grateful to @karasevm!
- The conflicted `data.json` is no longer merged automatically.
- This behaviour is not configurable, unlike the `Use newer file if conflicted` of normal files.
- 0.17.22
- Fixed:
- Now hidden files will not be synchronised while we are not configured.
- Some processes could start without waiting for synchronisation to complete, but now they will wait for.
- Improved
- Now, by placing `redflag3.md`, we can discard the local database and fetch again.
- The document has been updated! Thanks to @hilsonp!
- 0.17.23
- Improved:
- Now we can preserve the logs into the file.
- Note: This option will be enabled automatically also when we flagging a red flag.
- File names can now be made platform-appropriate.
- Refactored:
- Some redundant implementations have been sorted out.
- 0.17.24
- New feature:
- If any conflicted files have been left, they will be reported.
- Fixed:
- Now the name of the conflicting file is shown on the conflict-resolving dialogue.
- Hidden files are now able to be merged again.
- No longer error caused at plug-in being loaded.
- Improved:
- Caching chunks are now limited in total size of cached chunks.
- 0.17.25
- Fixed:
- Now reading error will be reported.
### 0.16.0
- Now hidden files need not be scanned. Changes will be detected automatically.
- If you want it to back to its previous behaviour, please disable `Monitor changes to internal files`.