mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-20 22:31:44 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3940260d42 | ||
|
|
b16333c604 | ||
|
|
7bf6d1f663 | ||
|
|
7046928068 | ||
|
|
333fcbaaeb | ||
|
|
009f92c307 | ||
|
|
3e541bd061 | ||
|
|
52d08301cc | ||
|
|
49d4c239f2 |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-livesync",
|
"id": "obsidian-livesync",
|
||||||
"name": "Self-hosted LiveSync",
|
"name": "Self-hosted LiveSync",
|
||||||
"version": "0.17.26",
|
"version": "0.17.30",
|
||||||
"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",
|
||||||
|
|||||||
1941
package-lock.json
generated
1941
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
38
package.json
38
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.17.26",
|
"version": "0.17.30",
|
||||||
"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",
|
||||||
@@ -16,31 +16,31 @@
|
|||||||
"@types/diff-match-patch": "^1.0.32",
|
"@types/diff-match-patch": "^1.0.32",
|
||||||
"@types/pouchdb": "^6.4.0",
|
"@types/pouchdb": "^6.4.0",
|
||||||
"@types/pouchdb-browser": "^6.1.3",
|
"@types/pouchdb-browser": "^6.1.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
"@typescript-eslint/eslint-plugin": "^5.54.0",
|
||||||
"@typescript-eslint/parser": "^5.44.0",
|
"@typescript-eslint/parser": "^5.54.0",
|
||||||
"builtin-modules": "^3.3.0",
|
"builtin-modules": "^3.3.0",
|
||||||
"esbuild": "0.15.15",
|
"esbuild": "0.15.15",
|
||||||
"esbuild-svelte": "^0.7.3",
|
"esbuild-svelte": "^0.7.3",
|
||||||
"eslint": "^8.28.0",
|
"eslint": "^8.35.0",
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.27.5",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"obsidian": "^0.16.3",
|
"obsidian": "^1.1.1",
|
||||||
"postcss": "^8.4.19",
|
"postcss": "^8.4.21",
|
||||||
"postcss-load-config": "^4.0.1",
|
"postcss-load-config": "^4.0.1",
|
||||||
"pouchdb-adapter-http": "^8.0.0",
|
"pouchdb-adapter-http": "^8.0.1",
|
||||||
"pouchdb-adapter-idb": "^8.0.0",
|
"pouchdb-adapter-idb": "^8.0.1",
|
||||||
"pouchdb-adapter-indexeddb": "^8.0.0",
|
"pouchdb-adapter-indexeddb": "^8.0.1",
|
||||||
"pouchdb-core": "^8.0.0",
|
"pouchdb-core": "^8.0.1",
|
||||||
"pouchdb-find": "^8.0.0",
|
"pouchdb-find": "^8.0.1",
|
||||||
"pouchdb-mapreduce": "^8.0.0",
|
"pouchdb-mapreduce": "^8.0.1",
|
||||||
"pouchdb-replication": "^8.0.0",
|
"pouchdb-replication": "^8.0.1",
|
||||||
"pouchdb-utils": "file:src/lib/src/patches/pouchdb-utils",
|
"pouchdb-utils": "^8.0.1",
|
||||||
"svelte": "^3.53.1",
|
"svelte": "^3.55.1",
|
||||||
"svelte-preprocess": "^4.10.7",
|
"svelte-preprocess": "^5.0.1",
|
||||||
"transform-pouch": "^2.0.0",
|
"transform-pouch": "^2.0.0",
|
||||||
"tslib": "^2.4.1",
|
"tslib": "^2.5.0",
|
||||||
"typescript": "^4.9.3"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"diff-match-patch": "^1.0.5",
|
"diff-match-patch": "^1.0.5",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import type { LoadedEntry } from "./lib/src/types";
|
import type { LoadedEntry } from "./lib/src/types";
|
||||||
import { base64ToString } from "./lib/src/strbin";
|
import { base64ToString } from "./lib/src/strbin";
|
||||||
import { getDocData } from "./lib/src/utils";
|
import { getDocData } from "./lib/src/utils";
|
||||||
import { mergeObject } from "./utils";
|
import { id2path, mergeObject } from "./utils";
|
||||||
|
|
||||||
export let docs: LoadedEntry[] = [];
|
export let docs: LoadedEntry[] = [];
|
||||||
export let callback: (keepRev: string, mergedStr?: string) => Promise<void> = async (_, __) => {
|
export let callback: (keepRev: string, mergedStr?: string) => Promise<void> = async (_, __) => {
|
||||||
@@ -93,9 +93,11 @@
|
|||||||
diffs = getJsonDiff(objA, selectedObj);
|
diffs = getJsonDiff(objA, selectedObj);
|
||||||
console.dir(selectedObj);
|
console.dir(selectedObj);
|
||||||
}
|
}
|
||||||
|
$: filename = id2path(docA?._id ?? "");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1>File Conflicted</h1>
|
<h1>Conflicted settings</h1>
|
||||||
|
<div><span>{filename}</span></div>
|
||||||
{#if !docA || !docB}
|
{#if !docA || !docB}
|
||||||
<div class="message">Just for a minute, please!</div>
|
<div class="message">Just for a minute, please!</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
|||||||
@@ -453,6 +453,9 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
this.plugin.settings.syncOnStart = false;
|
this.plugin.settings.syncOnStart = false;
|
||||||
this.plugin.settings.syncOnFileOpen = false;
|
this.plugin.settings.syncOnFileOpen = false;
|
||||||
this.plugin.settings.syncAfterMerge = 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();
|
await this.plugin.saveSettings();
|
||||||
|
|
||||||
applyDisplayEnabled();
|
applyDisplayEnabled();
|
||||||
@@ -1324,7 +1327,38 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
text.inputEl.setAttribute("type", "number");
|
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);
|
addScreenElement("30", containerSyncSettingEl);
|
||||||
const containerMiscellaneousEl = containerEl.createDiv();
|
const containerMiscellaneousEl = containerEl.createDiv();
|
||||||
containerMiscellaneousEl.createEl("h3", { text: "Miscellaneous" });
|
containerMiscellaneousEl.createEl("h3", { text: "Miscellaneous" });
|
||||||
|
|||||||
2
src/lib
2
src/lib
Submodule src/lib updated: fbb3fcd8b4...8985fa74e9
203
src/main.ts
203
src/main.ts
@@ -64,7 +64,6 @@ function filename2idInternalMetadata(str: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CHeader = "h:";
|
const CHeader = "h:";
|
||||||
const CHeaderEnd = "h;";
|
|
||||||
// const CHeaderLength = CHeader.length;
|
// const CHeaderLength = CHeader.length;
|
||||||
function isChunk(str: string): boolean {
|
function isChunk(str: string): boolean {
|
||||||
return str.startsWith(CHeader);
|
return str.startsWith(CHeader);
|
||||||
@@ -217,44 +216,38 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
const notesList = notes.map(e => e.path);
|
const notesList = notes.map(e => e.path);
|
||||||
if (notesList.length == 0) {
|
if (notesList.length == 0) {
|
||||||
Logger("There are no conflicted documents", LOG_LEVEL.NOTICE);
|
Logger("There are no conflicted documents", LOG_LEVEL.NOTICE);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
const target = await askSelectString(this.app, "File to view History", notesList);
|
const target = await askSelectString(this.app, "File to view History", notesList);
|
||||||
if (target) {
|
if (target) {
|
||||||
if (isInternalMetadata(target)) {
|
await this.resolveConflicted(target);
|
||||||
//NOP
|
return true;
|
||||||
await this.resolveConflictOnInternalFile(target);
|
}
|
||||||
} else {
|
return false;
|
||||||
await this.showIfConflicted(target);
|
}
|
||||||
}
|
async resolveConflicted(target: string) {
|
||||||
|
if (isInternalMetadata(target)) {
|
||||||
|
await this.resolveConflictOnInternalFile(target);
|
||||||
|
} else if (isPluginMetadata(target)) {
|
||||||
|
await this.resolveConflictByNewerEntry(target);
|
||||||
|
} else {
|
||||||
|
await this.showIfConflicted(target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async collectDeletedFiles() {
|
async collectDeletedFiles() {
|
||||||
const pageLimit = 1000;
|
|
||||||
let nextKey = "";
|
|
||||||
const limitDays = this.settings.automaticallyDeleteMetadataOfDeletedFiles;
|
const limitDays = this.settings.automaticallyDeleteMetadataOfDeletedFiles;
|
||||||
if (limitDays <= 0) return;
|
if (limitDays <= 0) return;
|
||||||
Logger(`Checking expired file history`);
|
Logger(`Checking expired file history`);
|
||||||
const limit = Date.now() - (86400 * 1000 * limitDays);
|
const limit = Date.now() - (86400 * 1000 * limitDays);
|
||||||
const notes: { path: string, mtime: number, ttl: number, doc: PouchDB.Core.ExistingDocument<EntryDoc & PouchDB.Core.AllDocsMeta> }[] = [];
|
const notes: { path: string, mtime: number, ttl: number, doc: PouchDB.Core.ExistingDocument<EntryDoc & PouchDB.Core.AllDocsMeta> }[] = [];
|
||||||
do {
|
for await (const doc of this.localDatabase.findAllDocs({ conflicts: true })) {
|
||||||
const docs = await this.localDatabase.localDatabase.allDocs({ limit: pageLimit, startkey: nextKey, conflicts: true, include_docs: true });
|
if (doc.type == "newnote" || doc.type == "plain") {
|
||||||
nextKey = "";
|
if (doc.deleted && (doc.mtime - limit) < 0) {
|
||||||
for (const row of docs.rows) {
|
notes.push({ path: id2path(doc._id), mtime: doc.mtime, ttl: (doc.mtime - limit) / 1000 / 86400, doc: doc });
|
||||||
const doc = row.doc;
|
|
||||||
nextKey = `${row.id}\u{10ffff}`;
|
|
||||||
if (doc.type == "newnote" || doc.type == "plain") {
|
|
||||||
if (doc.deleted && (doc.mtime - limit) < 0) {
|
|
||||||
notes.push({ path: id2path(doc._id), mtime: doc.mtime, ttl: (doc.mtime - limit) / 1000 / 86400, doc: doc });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isChunk(nextKey)) {
|
|
||||||
// skip the chunk zone.
|
|
||||||
nextKey = CHeaderEnd;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (nextKey != "");
|
}
|
||||||
if (notes.length == 0) {
|
if (notes.length == 0) {
|
||||||
Logger("There are no old documents");
|
Logger("There are no old documents");
|
||||||
Logger(`Checking expired file history done`);
|
Logger(`Checking expired file history done`);
|
||||||
@@ -327,7 +320,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
if (this.settings.suspendFileWatching) {
|
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);
|
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);
|
||||||
}
|
}
|
||||||
const isInitialized = await this.initializeDatabase();
|
const isInitialized = await this.initializeDatabase(false, false);
|
||||||
if (!isInitialized) {
|
if (!isInitialized) {
|
||||||
//TODO:stop all sync.
|
//TODO:stop all sync.
|
||||||
return false;
|
return false;
|
||||||
@@ -431,7 +424,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.settings = newSettingW;
|
this.settings = newSettingW;
|
||||||
this.usedPassphrase = "";
|
this.usedPassphrase = "";
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
await this.resetLocalOldDatabase();
|
|
||||||
await this.resetLocalDatabase();
|
await this.resetLocalDatabase();
|
||||||
await this.localDatabase.initializeDatabase();
|
await this.localDatabase.initializeDatabase();
|
||||||
await this.markRemoteResolved();
|
await this.markRemoteResolved();
|
||||||
@@ -444,7 +436,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.settings = newSettingW;
|
this.settings = newSettingW;
|
||||||
this.usedPassphrase = "";
|
this.usedPassphrase = "";
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
await this.resetLocalOldDatabase();
|
|
||||||
await this.resetLocalDatabase();
|
await this.resetLocalDatabase();
|
||||||
await this.localDatabase.initializeDatabase();
|
await this.localDatabase.initializeDatabase();
|
||||||
await this.initializeDatabase(true);
|
await this.initializeDatabase(true);
|
||||||
@@ -482,7 +473,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.usedPassphrase = "";
|
this.usedPassphrase = "";
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
if (keepLocalDB == "no") {
|
if (keepLocalDB == "no") {
|
||||||
this.resetLocalOldDatabase();
|
|
||||||
this.resetLocalDatabase();
|
this.resetLocalDatabase();
|
||||||
this.localDatabase.initializeDatabase();
|
this.localDatabase.initializeDatabase();
|
||||||
const rebuild = await askYesNo(this.app, "Rebuild the database?");
|
const rebuild = await askYesNo(this.app, "Rebuild the database?");
|
||||||
@@ -531,6 +521,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
const lsKey = "obsidian-live-sync-ver" + this.getVaultName();
|
const lsKey = "obsidian-live-sync-ver" + this.getVaultName();
|
||||||
const last_version = localStorage.getItem(lsKey);
|
const last_version = localStorage.getItem(lsKey);
|
||||||
await this.loadSettings();
|
await this.loadSettings();
|
||||||
|
|
||||||
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
|
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
|
||||||
if (lastVersion > this.settings.lastReadUpdates) {
|
if (lastVersion > this.settings.lastReadUpdates) {
|
||||||
Logger("Self-hosted LiveSync has undergone a major upgrade. Please open the setting dialog, and check the information pane.", LOG_LEVEL.NOTICE);
|
Logger("Self-hosted LiveSync has undergone a major upgrade. Please open the setting dialog, and check the information pane.", LOG_LEVEL.NOTICE);
|
||||||
@@ -606,7 +597,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.addSettingTab(new ObsidianLiveSyncSettingTab(this.app, this));
|
this.addSettingTab(new ObsidianLiveSyncSettingTab(this.app, this));
|
||||||
|
|
||||||
this.app.workspace.onLayoutReady(this.onLayoutReady.bind(this));
|
this.app.workspace.onLayoutReady(this.onLayoutReady.bind(this));
|
||||||
this.registerObsidianProtocolHandler("setuplivesync", async (conf: any) => await this.setupWizard(conf.settings));
|
this.registerObsidianProtocolHandler("setuplivesync", async (conf: any) => await this.setupWizard(conf.settings));
|
||||||
|
|
||||||
@@ -726,6 +716,13 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.pickFileForResolve();
|
this.pickFileForResolve();
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
this.addCommand({
|
||||||
|
id: "livesync-all-conflictcheck",
|
||||||
|
name: "Resolve all conflicted files",
|
||||||
|
callback: async () => {
|
||||||
|
while (await this.pickFileForResolve());
|
||||||
|
},
|
||||||
|
})
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-runbatch",
|
id: "livesync-runbatch",
|
||||||
name: "Run pended batch processes",
|
name: "Run pended batch processes",
|
||||||
@@ -782,7 +779,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
await this.localDatabase.close();
|
await this.localDatabase.close();
|
||||||
}
|
}
|
||||||
const vaultName = this.getVaultName();
|
const vaultName = this.getVaultName();
|
||||||
Logger("Open Database...");
|
Logger("Waiting for ready...");
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const isMobile = this.app.isMobile;
|
const isMobile = this.app.isMobile;
|
||||||
this.localDatabase = new LocalPouchDB(this.settings, vaultName, isMobile);
|
this.localDatabase = new LocalPouchDB(this.settings, vaultName, isMobile);
|
||||||
@@ -1315,7 +1312,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
this.app.vault.adapter.append(normalizePath(logDate), vaultName + ":" + newMessage + "\n");
|
this.app.vault.adapter.append(normalizePath(logDate), vaultName + ":" + newMessage + "\n");
|
||||||
}
|
}
|
||||||
logMessageStore.apply(e => [...e, newMessage].slice(-100));
|
logMessageStore.apply(e => [...e, newMessage].slice(-100));
|
||||||
this.setStatusBarText(null, messageContent.substring(0, 30));
|
this.setStatusBarText(null, messageContent);
|
||||||
|
|
||||||
if (level >= LOG_LEVEL.NOTICE) {
|
if (level >= LOG_LEVEL.NOTICE) {
|
||||||
if (!key) key = messageContent;
|
if (!key) key = messageContent;
|
||||||
@@ -1457,33 +1454,40 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
|
|
||||||
|
|
||||||
queuedEntries: EntryBody[] = [];
|
queuedEntries: EntryBody[] = [];
|
||||||
|
dbChangeProcRunning = false;
|
||||||
handleDBChanged(change: EntryBody) {
|
handleDBChanged(change: EntryBody) {
|
||||||
// If queued same file, cancel previous one.
|
|
||||||
this.queuedEntries.remove(this.queuedEntries.find(e => e._id == change._id));
|
|
||||||
// If the file is opened, we have to apply immediately
|
// If the file is opened, we have to apply immediately
|
||||||
const af = app.workspace.getActiveFile();
|
const af = app.workspace.getActiveFile();
|
||||||
if (af && af.path == id2path(change._id)) {
|
if (af && af.path == id2path(change._id)) {
|
||||||
|
this.queuedEntries = this.queuedEntries.filter(e => e._id != change._id);
|
||||||
return this.handleDBChangedAsync(change);
|
return this.handleDBChangedAsync(change);
|
||||||
}
|
}
|
||||||
this.queuedEntries.push(change);
|
this.queuedEntries.push(change);
|
||||||
if (this.queuedEntries.length > 50) {
|
this.execDBchanged();
|
||||||
clearTrigger("dbchanged");
|
|
||||||
this.execDBchanged();
|
|
||||||
}
|
|
||||||
setTrigger("dbchanged", 500, () => this.execDBchanged());
|
|
||||||
}
|
}
|
||||||
async execDBchanged() {
|
async execDBchanged() {
|
||||||
await runWithLock("dbchanged", false, async () => {
|
if (this.dbChangeProcRunning) return false;
|
||||||
const w = [...this.queuedEntries];
|
this.dbChangeProcRunning = true;
|
||||||
this.queuedEntries = [];
|
const semaphore = Semaphore(4);
|
||||||
Logger(`Applying ${w.length} files`);
|
try {
|
||||||
for (const entry of w) {
|
do {
|
||||||
Logger(`Applying ${entry._id} (${entry._rev}) change...`, LOG_LEVEL.VERBOSE);
|
const entry = this.queuedEntries.shift();
|
||||||
await this.handleDBChangedAsync(entry);
|
// If the same file is to be manipulated, leave it to the last process.
|
||||||
Logger(`Applied ${entry._id} (${entry._rev}) change...`);
|
if (this.queuedEntries.some(e => e._id == entry._id)) continue;
|
||||||
}
|
try {
|
||||||
|
const releaser = await semaphore.acquire(1);
|
||||||
|
runWithLock(`dbchanged-${entry._id}`, false, async () => {
|
||||||
|
Logger(`Applying ${entry._id} (${entry._rev}) change...`, LOG_LEVEL.VERBOSE);
|
||||||
|
await this.handleDBChangedAsync(entry);
|
||||||
|
Logger(`Applied ${entry._id} (${entry._rev}) change...`);
|
||||||
|
}).finally(() => { releaser(); });
|
||||||
|
} catch (ex) {
|
||||||
|
Logger(`Failed to apply the change of ${entry._id} (${entry._rev})`);
|
||||||
|
}
|
||||||
|
} while (this.queuedEntries.length > 0);
|
||||||
|
} finally {
|
||||||
|
this.dbChangeProcRunning = false;
|
||||||
}
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
async handleDBChangedAsync(change: EntryBody) {
|
async handleDBChangedAsync(change: EntryBody) {
|
||||||
|
|
||||||
@@ -1576,9 +1580,10 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
const filename = id2path(id2filenameInternalMetadata(queue.entry._id));
|
const filename = id2path(id2filenameInternalMetadata(queue.entry._id));
|
||||||
// await this.syncInternalFilesAndDatabase("pull", false, false, [filename])
|
// await this.syncInternalFilesAndDatabase("pull", false, false, [filename])
|
||||||
this.procInternalFile(filename);
|
this.procInternalFile(filename);
|
||||||
}
|
} else if (isValidPath(id2path(queue.entry._id))) {
|
||||||
if (isValidPath(id2path(queue.entry._id))) {
|
|
||||||
this.handleDBChanged(queue.entry);
|
this.handleDBChanged(queue.entry);
|
||||||
|
} else {
|
||||||
|
Logger(`Skipped: ${queue.entry._id}`, LOG_LEVEL.VERBOSE);
|
||||||
}
|
}
|
||||||
} else if (now > queue.timeout) {
|
} else if (now > queue.timeout) {
|
||||||
if (!queue.warned) Logger(`Timed out: ${queue.entry._id} could not collect ${queue.missingChildren.length} chunks. plugin keeps watching, but you have to check the file after the replication.`, LOG_LEVEL.NOTICE);
|
if (!queue.warned) Logger(`Timed out: ${queue.entry._id} could not collect ${queue.missingChildren.length} chunks. plugin keeps watching, but you have to check the file after the replication.`, LOG_LEVEL.NOTICE);
|
||||||
@@ -1843,17 +1848,23 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
const processes = e.count;
|
const processes = e.count;
|
||||||
const processesDisp = processes == 0 ? "" : ` ⏳${processes}`;
|
const processesDisp = processes == 0 ? "" : ` ⏳${processes}`;
|
||||||
const message = `Sync: ${w} ↑${sent}${pushLast} ↓${arrived}${pullLast}${waiting}${processesDisp}${queued}`;
|
const message = `Sync: ${w} ↑${sent}${pushLast} ↓${arrived}${pullLast}${waiting}${processesDisp}${queued}`;
|
||||||
// const locks = getLocks();
|
function getProcKind(proc: string) {
|
||||||
|
const p = proc.indexOf("-");
|
||||||
|
if (p == -1) {
|
||||||
|
return proc;
|
||||||
|
}
|
||||||
|
return proc.substring(0, p);
|
||||||
|
}
|
||||||
const pendingTask = e.pending.length
|
const pendingTask = e.pending.length
|
||||||
? "\nPending: " +
|
? "\nPending: " +
|
||||||
Object.entries(e.pending.reduce((p, c) => ({ ...p, [c]: (p[c] ?? 0) + 1 }), {} as { [key: string]: number }))
|
Object.entries(e.pending.reduce((p, c) => ({ ...p, [getProcKind(c)]: (p[getProcKind(c)] ?? 0) + 1 }), {} as { [key: string]: number }))
|
||||||
.map((e) => `${e[0]}${e[1] == 1 ? "" : `(${e[1]})`}`)
|
.map((e) => `${e[0]}${e[1] == 1 ? "" : `(${e[1]})`}`)
|
||||||
.join(", ")
|
.join(", ")
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const runningTask = e.running.length
|
const runningTask = e.running.length
|
||||||
? "\nRunning: " +
|
? "\nRunning: " +
|
||||||
Object.entries(e.running.reduce((p, c) => ({ ...p, [c]: (p[c] ?? 0) + 1 }), {} as { [key: string]: number }))
|
Object.entries(e.running.reduce((p, c) => ({ ...p, [getProcKind(c)]: (p[getProcKind(c)] ?? 0) + 1 }), {} as { [key: string]: number }))
|
||||||
.map((e) => `${e[0]}${e[1] == 1 ? "" : `(${e[1]})`}`)
|
.map((e) => `${e[0]}${e[1] == 1 ? "" : `(${e[1]})`}`)
|
||||||
.join(", ")
|
.join(", ")
|
||||||
: "";
|
: "";
|
||||||
@@ -1909,9 +1920,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
await this.localDatabase.openReplication(this.settings, false, showMessage, this.parseReplicationResult);
|
await this.localDatabase.openReplication(this.settings, false, showMessage, this.parseReplicationResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
async initializeDatabase(showingNotice?: boolean) {
|
async initializeDatabase(showingNotice?: boolean, reopenDatabase = true) {
|
||||||
this.isReady = false;
|
this.isReady = false;
|
||||||
if (await this.openDatabase()) {
|
if ((!reopenDatabase) || await this.openDatabase()) {
|
||||||
if (this.localDatabase.isReady) {
|
if (this.localDatabase.isReady) {
|
||||||
await this.syncAllFiles(showingNotice);
|
await this.syncAllFiles(showingNotice);
|
||||||
}
|
}
|
||||||
@@ -1973,18 +1984,23 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
Logger("Initializing", LOG_LEVEL.NOTICE, "syncAll");
|
Logger("Initializing", LOG_LEVEL.NOTICE, "syncAll");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger("Initialize and checking database files");
|
||||||
|
Logger("Checking deleted files");
|
||||||
await this.collectDeletedFiles();
|
await this.collectDeletedFiles();
|
||||||
|
|
||||||
|
Logger("Collecting local files on the storage", LOG_LEVEL.VERBOSE);
|
||||||
const filesStorage = this.app.vault.getFiles().filter(e => this.isTargetFile(e));
|
const filesStorage = this.app.vault.getFiles().filter(e => this.isTargetFile(e));
|
||||||
const filesStorageName = filesStorage.map((e) => e.path);
|
const filesStorageName = filesStorage.map((e) => e.path);
|
||||||
const wf = await this.localDatabase.localDatabase.allDocs();
|
Logger("Collecting local files on the DB", LOG_LEVEL.VERBOSE);
|
||||||
const filesDatabase = wf.rows.filter((e) =>
|
const filesDatabase = [] as string[]
|
||||||
!isChunk(e.id) &&
|
for await (const docId of this.localDatabase.findAllDocNames()) {
|
||||||
!isPluginMetadata(e.id) &&
|
const path = id2path(docId);
|
||||||
e.id != "obsydian_livesync_version" &&
|
if (isValidPath(docId) && this.isTargetFile(path)) {
|
||||||
e.id != "_design/replicate"
|
filesDatabase.push(path);
|
||||||
)
|
}
|
||||||
.filter(e => isValidPath(e.id)).map((e) => id2path(e.id)).filter(e => this.isTargetFile(e));
|
}
|
||||||
|
|
||||||
|
Logger("Opening the key-value database", LOG_LEVEL.VERBOSE);
|
||||||
const isInitialized = await (this.localDatabase.kvDB.get<boolean>("initialized")) || false;
|
const isInitialized = await (this.localDatabase.kvDB.get<boolean>("initialized")) || false;
|
||||||
// Make chunk bigger if it is the initial scan. There must be non-active docs.
|
// Make chunk bigger if it is the initial scan. There must be non-active docs.
|
||||||
if (filesDatabase.length == 0 && !isInitialized) {
|
if (filesDatabase.length == 0 && !isInitialized) {
|
||||||
@@ -1997,33 +2013,18 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
const onlyInStorageNames = onlyInStorage.map((e) => e.path);
|
const onlyInStorageNames = onlyInStorage.map((e) => e.path);
|
||||||
|
|
||||||
const syncFiles = filesStorage.filter((e) => onlyInStorageNames.indexOf(e.path) == -1);
|
const syncFiles = filesStorage.filter((e) => onlyInStorageNames.indexOf(e.path) == -1);
|
||||||
Logger("Initialize and checking database files");
|
|
||||||
Logger("Updating database by new files");
|
Logger("Updating database by new files");
|
||||||
this.setStatusBarText(`UPDATE DATABASE`);
|
this.setStatusBarText(`UPDATE DATABASE`);
|
||||||
|
|
||||||
const runAll = async<T>(procedureName: string, objects: T[], callback: (arg: T) => Promise<void>) => {
|
const runAll = async<T>(procedureName: string, objects: T[], callback: (arg: T) => Promise<void>) => {
|
||||||
// const count = objects.length;
|
|
||||||
Logger(procedureName);
|
Logger(procedureName);
|
||||||
// let i = 0;
|
|
||||||
const semaphore = Semaphore(25);
|
const semaphore = Semaphore(25);
|
||||||
|
|
||||||
// Logger(`${procedureName} exec.`);
|
|
||||||
if (!this.localDatabase.isReady) throw Error("Database is not ready!");
|
if (!this.localDatabase.isReady) throw Error("Database is not ready!");
|
||||||
const processes = objects.map(e => (async (v) => {
|
const processes = objects.map(e => (async (v) => {
|
||||||
const releaser = await semaphore.acquire(1, procedureName);
|
const releaser = await semaphore.acquire(1, procedureName);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await callback(v);
|
await callback(v);
|
||||||
// i++;
|
|
||||||
// if (i % 50 == 0) {
|
|
||||||
// const notify = `${procedureName} : ${i}/${count}`;
|
|
||||||
// if (showingNotice) {
|
|
||||||
// Logger(notify, LOG_LEVEL.NOTICE, "syncAll");
|
|
||||||
// } else {
|
|
||||||
// Logger(notify);
|
|
||||||
// }
|
|
||||||
// this.setStatusBarText(notify);
|
|
||||||
// }
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`Error while ${procedureName}`, LOG_LEVEL.NOTICE);
|
Logger(`Error while ${procedureName}`, LOG_LEVEL.NOTICE);
|
||||||
Logger(ex);
|
Logger(ex);
|
||||||
@@ -2063,7 +2064,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
const syncFilesX = syncFiles.splice(0, 100);
|
const syncFilesX = syncFiles.splice(0, 100);
|
||||||
const docs = await this.localDatabase.localDatabase.allDocs({ keys: syncFilesX.map(e => path2id(e.path)), include_docs: true })
|
const docs = await this.localDatabase.localDatabase.allDocs({ keys: syncFilesX.map(e => path2id(e.path)), include_docs: true })
|
||||||
const syncFilesToSync = syncFilesX.map((e) => ({ file: e, doc: docs.rows.find(ee => ee.id == path2id(e.path)).doc as LoadedEntry }));
|
const syncFilesToSync = syncFilesX.map((e) => ({ file: e, doc: docs.rows.find(ee => ee.id == path2id(e.path)).doc as LoadedEntry }));
|
||||||
|
|
||||||
await runAll(`CHECK FILE STATUS:${syncFiles.length}/${docsCount}`, syncFilesToSync, async (e) => {
|
await runAll(`CHECK FILE STATUS:${syncFiles.length}/${docsCount}`, syncFilesToSync, async (e) => {
|
||||||
caches = await this.syncFileBetweenDBandStorage(e.file, e.doc, initialScan, caches);
|
caches = await this.syncFileBetweenDBandStorage(e.file, e.doc, initialScan, caches);
|
||||||
});
|
});
|
||||||
@@ -2670,6 +2670,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
const dK = `${file.path}-diff`;
|
const dK = `${file.path}-diff`;
|
||||||
const isLastDiff = dK in caches ? caches[dK] : { storageMtime: 0, docMtime: 0 };
|
const isLastDiff = dK in caches ? caches[dK] : { storageMtime: 0, docMtime: 0 };
|
||||||
if (isLastDiff.docMtime == docMtime && isLastDiff.storageMtime == storageMtime) {
|
if (isLastDiff.docMtime == docMtime && isLastDiff.storageMtime == storageMtime) {
|
||||||
|
Logger("STORAGE .. DB :" + file.path, LOG_LEVEL.VERBOSE);
|
||||||
caches[dK] = { storageMtime, docMtime };
|
caches[dK] = { storageMtime, docMtime };
|
||||||
return caches;
|
return caches;
|
||||||
}
|
}
|
||||||
@@ -2692,11 +2693,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
caches[dK] = { storageMtime, docMtime };
|
caches[dK] = { storageMtime, docMtime };
|
||||||
return caches;
|
return caches;
|
||||||
} else {
|
|
||||||
// Logger("EVEN :" + file.path, LOG_LEVEL.VERBOSE);
|
|
||||||
// Logger(`${storageMtime} = ${docMtime}`, LOG_LEVEL.VERBOSE);
|
|
||||||
//eq.case
|
|
||||||
}
|
}
|
||||||
|
Logger("STORAGE == DB :" + file.path + "", LOG_LEVEL.VERBOSE);
|
||||||
caches[dK] = { storageMtime, docMtime };
|
caches[dK] = { storageMtime, docMtime };
|
||||||
return caches;
|
return caches;
|
||||||
|
|
||||||
@@ -2755,7 +2753,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
};
|
};
|
||||||
//upsert should locked
|
//upsert should locked
|
||||||
const msg = `DB <- STORAGE (${datatype}) `;
|
const msg = `DB <- STORAGE (${datatype}) `;
|
||||||
const isNotChanged = await runWithLock("file:" + fullPath, false, async () => {
|
const isNotChanged = await runWithLock("file-" + fullPath, false, async () => {
|
||||||
if (recentlyTouched(file)) {
|
if (recentlyTouched(file)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2811,11 +2809,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
async resetLocalDatabase() {
|
async resetLocalDatabase() {
|
||||||
clearTouched();
|
clearTouched();
|
||||||
await this.localDatabase.resetDatabase();
|
await this.localDatabase.resetDatabase();
|
||||||
await this.localDatabase.resetLocalOldDatabase();
|
|
||||||
}
|
|
||||||
async resetLocalOldDatabase() {
|
|
||||||
clearTouched();
|
|
||||||
await this.localDatabase.resetLocalOldDatabase();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async tryResetRemoteDatabase() {
|
async tryResetRemoteDatabase() {
|
||||||
@@ -3285,16 +3278,36 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
|
|
||||||
async resolveConflictOnInternalFiles() {
|
async resolveConflictOnInternalFiles() {
|
||||||
// Scan all conflicted internal files
|
// Scan all conflicted internal files
|
||||||
const docs = await this.localDatabase.localDatabase.allDocs({ startkey: ICHeader, endkey: ICHeaderEnd, conflicts: true, include_docs: true });
|
const conflicted = this.localDatabase.findEntries(ICHeader, ICHeaderEnd, { conflicts: true });
|
||||||
for (const row of docs.rows) {
|
for await (const doc of conflicted) {
|
||||||
const doc = row.doc;
|
|
||||||
if (!("_conflicts" in doc)) continue;
|
if (!("_conflicts" in doc)) continue;
|
||||||
if (isInternalMetadata(row.id)) {
|
if (isInternalMetadata(doc._id)) {
|
||||||
await this.resolveConflictOnInternalFile(row.id);
|
await this.resolveConflictOnInternalFile(doc._id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async resolveConflictByNewerEntry(id: string) {
|
||||||
|
const doc = await this.localDatabase.localDatabase.get(id, { conflicts: true });
|
||||||
|
// If there is no conflict, return with false.
|
||||||
|
if (!("_conflicts" in doc)) return false;
|
||||||
|
if (doc._conflicts.length == 0) return false;
|
||||||
|
Logger(`Hidden file conflicted:${id2filenameInternalMetadata(id)}`);
|
||||||
|
const conflicts = doc._conflicts.sort((a, b) => Number(a.split("-")[0]) - Number(b.split("-")[0]));
|
||||||
|
const revA = doc._rev;
|
||||||
|
const revB = conflicts[0];
|
||||||
|
const revBDoc = await this.localDatabase.localDatabase.get(id, { rev: revB });
|
||||||
|
// determine which revision should been deleted.
|
||||||
|
// simply check modified time
|
||||||
|
const mtimeA = ("mtime" in doc && doc.mtime) || 0;
|
||||||
|
const mtimeB = ("mtime" in revBDoc && revBDoc.mtime) || 0;
|
||||||
|
const delRev = mtimeA < mtimeB ? revA : revB;
|
||||||
|
// delete older one.
|
||||||
|
await this.localDatabase.localDatabase.remove(id, delRev);
|
||||||
|
Logger(`Older one has been deleted:${id2filenameInternalMetadata(id)}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
async resolveConflictOnInternalFile(id: string): Promise<boolean> {
|
async resolveConflictOnInternalFile(id: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
// Retrieve data
|
// Retrieve data
|
||||||
|
|||||||
88
updates.md
88
updates.md
@@ -10,64 +10,38 @@
|
|||||||
- Chunk ID numbering rules
|
- Chunk ID numbering rules
|
||||||
|
|
||||||
#### Minors
|
#### Minors
|
||||||
- __0.17.1 to 0.17.15 has been moved into `update_old.md`__
|
- __0.17.1 to 0.17.25 has been moved into `update_old.md`__
|
||||||
|
|
||||||
- 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.17.26
|
- 0.17.26
|
||||||
- Fixed(Urgent):
|
- Fixed(Urgent):
|
||||||
- The modified document will be reflected in the storage now.
|
- The modified document will be reflected in the storage now.
|
||||||
|
- 0.17.27
|
||||||
|
- 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.
|
||||||
|
- 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:
|
||||||
|
- 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:
|
||||||
|
- 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.
|
||||||
... To continue on to `updates_old.md`.
|
... To continue on to `updates_old.md`.
|
||||||
@@ -68,7 +68,60 @@
|
|||||||
- Hidden files have been synchronised again.
|
- Hidden files have been synchronised again.
|
||||||
- Rename of files has been fixed again.
|
- Rename of files has been fixed again.
|
||||||
And, minor changes have been included.
|
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
|
### 0.16.0
|
||||||
- Now hidden files need not be scanned. Changes will be detected automatically.
|
- 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`.
|
- If you want it to back to its previous behaviour, please disable `Monitor changes to internal files`.
|
||||||
|
|||||||
Reference in New Issue
Block a user