mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-13 11:01:16 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
748d031b36 | ||
|
|
dbe77718c8 | ||
|
|
f334974cc3 | ||
|
|
8f2ae437c6 | ||
|
|
a0efda9e71 | ||
|
|
be3d61c1c7 |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-livesync",
|
"id": "obsidian-livesync",
|
||||||
"name": "Self-hosted LiveSync",
|
"name": "Self-hosted LiveSync",
|
||||||
"version": "0.17.23",
|
"version": "0.17.26",
|
||||||
"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.17.23",
|
"version": "0.17.26",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.17.23",
|
"version": "0.17.26",
|
||||||
"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.17.23",
|
"version": "0.17.26",
|
||||||
"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",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export class ConflictResolveModal extends Modal {
|
|||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
|
|
||||||
contentEl.createEl("h2", { text: "This document has conflicted changes." });
|
contentEl.createEl("h2", { text: "This document has conflicted changes." });
|
||||||
contentEl.createEl("span", this.filename);
|
contentEl.createEl("span", { text: this.filename });
|
||||||
const div = contentEl.createDiv("");
|
const div = contentEl.createDiv("");
|
||||||
div.addClass("op-scrollable");
|
div.addClass("op-scrollable");
|
||||||
let diff = "";
|
let diff = "";
|
||||||
|
|||||||
@@ -1292,6 +1292,38 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
return toggle;
|
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");
|
||||||
|
});
|
||||||
|
|
||||||
addScreenElement("30", containerSyncSettingEl);
|
addScreenElement("30", containerSyncSettingEl);
|
||||||
const containerMiscellaneousEl = containerEl.createDiv();
|
const containerMiscellaneousEl = containerEl.createDiv();
|
||||||
|
|||||||
2
src/lib
2
src/lib
Submodule src/lib updated: 6c8d0b0c32...fbb3fcd8b4
106
src/main.ts
106
src/main.ts
@@ -138,6 +138,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
deviceAndVaultName: string;
|
deviceAndVaultName: string;
|
||||||
isMobile = false;
|
isMobile = false;
|
||||||
isReady = false;
|
isReady = false;
|
||||||
|
packageVersion = "";
|
||||||
|
manifestVersion = "";
|
||||||
|
|
||||||
watchedFileEventQueue = [] as FileEventItem[];
|
watchedFileEventQueue = [] as FileEventItem[];
|
||||||
|
|
||||||
@@ -194,26 +196,10 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fileHistory() {
|
async fileHistory() {
|
||||||
const pageLimit = 1000;
|
|
||||||
let nextKey = "";
|
|
||||||
const notes: { path: string, mtime: number }[] = [];
|
const notes: { path: string, mtime: number }[] = [];
|
||||||
do {
|
for await (const doc of this.localDatabase.findAllDocs()) {
|
||||||
const docs = await this.localDatabase.localDatabase.allDocs({ limit: pageLimit, startkey: nextKey, include_docs: true });
|
notes.push({ path: id2path(doc._id), mtime: doc.mtime });
|
||||||
nextKey = "";
|
}
|
||||||
for (const row of docs.rows) {
|
|
||||||
const doc = row.doc;
|
|
||||||
nextKey = `${row.id}\u{10ffff}`;
|
|
||||||
if (!("type" in doc)) continue;
|
|
||||||
if (doc.type == "newnote" || doc.type == "plain") {
|
|
||||||
notes.push({ path: id2path(doc._id), mtime: doc.mtime });
|
|
||||||
}
|
|
||||||
if (isChunk(nextKey)) {
|
|
||||||
// skip the chunk zone.
|
|
||||||
nextKey = CHeaderEnd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (nextKey != "");
|
|
||||||
|
|
||||||
notes.sort((a, b) => b.mtime - a.mtime);
|
notes.sort((a, b) => b.mtime - a.mtime);
|
||||||
const notesList = notes.map(e => e.path);
|
const notesList = notes.map(e => e.path);
|
||||||
const target = await askSelectString(this.app, "File to view History", notesList);
|
const target = await askSelectString(this.app, "File to view History", notesList);
|
||||||
@@ -222,31 +208,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async pickFileForResolve() {
|
async pickFileForResolve() {
|
||||||
const pageLimit = 1000;
|
|
||||||
let nextKey = "";
|
|
||||||
const notes: { path: string, mtime: number }[] = [];
|
const notes: { path: string, mtime: number }[] = [];
|
||||||
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 (!("_conflicts" in doc)) continue;
|
||||||
nextKey = "";
|
notes.push({ path: id2path(doc._id), mtime: doc.mtime });
|
||||||
for (const row of docs.rows) {
|
}
|
||||||
const doc = row.doc;
|
|
||||||
nextKey = `${row.id}\u{10ffff}`;
|
|
||||||
if (isChunk(nextKey)) {
|
|
||||||
// skip the chunk zone.
|
|
||||||
nextKey = CHeaderEnd;
|
|
||||||
}
|
|
||||||
if (!("_conflicts" in doc)) continue;
|
|
||||||
if (isInternalMetadata(row.id)) continue;
|
|
||||||
// We have to check also deleted files.
|
|
||||||
// if (doc._deleted) continue;
|
|
||||||
// if ("deleted" in doc && doc.deleted) continue;
|
|
||||||
if (doc.type == "newnote" || doc.type == "plain") {
|
|
||||||
// const docId = doc._id.startsWith("i:") ? doc._id.substring("i:".length) : doc._id;
|
|
||||||
notes.push({ path: id2path(doc._id), mtime: doc.mtime });
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
} while (nextKey != "");
|
|
||||||
notes.sort((a, b) => b.mtime - a.mtime);
|
notes.sort((a, b) => b.mtime - a.mtime);
|
||||||
const notesList = notes.map(e => e.path);
|
const notesList = notes.map(e => e.path);
|
||||||
if (notesList.length == 0) {
|
if (notesList.length == 0) {
|
||||||
@@ -257,6 +223,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
if (target) {
|
if (target) {
|
||||||
if (isInternalMetadata(target)) {
|
if (isInternalMetadata(target)) {
|
||||||
//NOP
|
//NOP
|
||||||
|
await this.resolveConflictOnInternalFile(target);
|
||||||
} else {
|
} else {
|
||||||
await this.showIfConflicted(target);
|
await this.showIfConflicted(target);
|
||||||
}
|
}
|
||||||
@@ -371,12 +338,33 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
if (this.settings.syncOnStart) {
|
if (this.settings.syncOnStart) {
|
||||||
this.localDatabase.openReplication(this.settings, false, false, this.parseReplicationResult);
|
this.localDatabase.openReplication(this.settings, false, false, this.parseReplicationResult);
|
||||||
}
|
}
|
||||||
|
this.scanStat();
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger("Error while loading Self-hosted LiveSync", LOG_LEVEL.NOTICE);
|
Logger("Error while loading Self-hosted LiveSync", LOG_LEVEL.NOTICE);
|
||||||
Logger(ex, LOG_LEVEL.VERBOSE);
|
Logger(ex, LOG_LEVEL.VERBOSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan status
|
||||||
|
*/
|
||||||
|
async scanStat() {
|
||||||
|
const notes: { path: string, mtime: number }[] = [];
|
||||||
|
Logger(`Additional safety scan..`, LOG_LEVEL.VERBOSE);
|
||||||
|
for await (const doc of this.localDatabase.findAllDocs({ conflicts: true })) {
|
||||||
|
if (!("_conflicts" in doc)) continue;
|
||||||
|
notes.push({ path: id2path(doc._id), mtime: doc.mtime });
|
||||||
|
}
|
||||||
|
if (notes.length > 0) {
|
||||||
|
Logger(`Some files have been left conflicted! Please resolve them by "Pick a file to resolve conflict". The list is written in the log.`, LOG_LEVEL.NOTICE);
|
||||||
|
for (const note of notes) {
|
||||||
|
Logger(`Conflicted: ${note.path}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger(`There are no conflicted files`, LOG_LEVEL.VERBOSE);
|
||||||
|
}
|
||||||
|
Logger(`Additional safety scan done`, LOG_LEVEL.VERBOSE);
|
||||||
|
}
|
||||||
async command_copySetupURI() {
|
async command_copySetupURI() {
|
||||||
const encryptingPassphrase = await askString(this.app, "Encrypt your settings", "The passphrase to encrypt the setup URI", "");
|
const encryptingPassphrase = await askString(this.app, "Encrypt your settings", "The passphrase to encrypt the setup URI", "");
|
||||||
if (encryptingPassphrase === false) return;
|
if (encryptingPassphrase === false) return;
|
||||||
@@ -536,6 +524,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const packageVersion: string = PACKAGE_VERSION || "0.0.0";
|
const packageVersion: string = PACKAGE_VERSION || "0.0.0";
|
||||||
|
|
||||||
|
this.manifestVersion = manifestVersion;
|
||||||
|
this.packageVersion = packageVersion;
|
||||||
|
|
||||||
Logger(`Self-hosted LiveSync v${manifestVersion} ${packageVersion} `);
|
Logger(`Self-hosted LiveSync v${manifestVersion} ${packageVersion} `);
|
||||||
const lsKey = "obsidian-live-sync-ver" + this.getVaultName();
|
const lsKey = "obsidian-live-sync-ver" + this.getVaultName();
|
||||||
@@ -1315,7 +1305,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
const newMessage = timestamp + "->" + messageContent;
|
const newMessage = timestamp + "->" + messageContent;
|
||||||
|
|
||||||
console.log(vaultName + ":" + newMessage);
|
console.log(vaultName + ":" + newMessage);
|
||||||
if (this.settings.writeLogToTheFile) {
|
if (this.settings?.writeLogToTheFile) {
|
||||||
const time = now.toISOString().split("T")[0];
|
const time = now.toISOString().split("T")[0];
|
||||||
const logDate = `${PREFIXMD_LOGFILE}${time}.md`;
|
const logDate = `${PREFIXMD_LOGFILE}${time}.md`;
|
||||||
const file = this.app.vault.getAbstractFileByPath(normalizePath(logDate));
|
const file = this.app.vault.getAbstractFileByPath(normalizePath(logDate));
|
||||||
@@ -1409,7 +1399,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const localMtime = ~~(file?.stat?.mtime || 0 / 1000);
|
const localMtime = ~~((file?.stat?.mtime || 0) / 1000);
|
||||||
const docMtime = ~~(docEntry.mtime / 1000);
|
const docMtime = ~~(docEntry.mtime / 1000);
|
||||||
|
|
||||||
const doc = await this.localDatabase.getDBEntry(pathSrc, { rev: docEntry._rev });
|
const doc = await this.localDatabase.getDBEntry(pathSrc, { rev: docEntry._rev });
|
||||||
@@ -2724,7 +2714,13 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
Logger(`Reading : ${file.path}`, LOG_LEVEL.VERBOSE);
|
Logger(`Reading : ${file.path}`, LOG_LEVEL.VERBOSE);
|
||||||
const contentBin = await this.app.vault.readBinary(file);
|
const contentBin = await this.app.vault.readBinary(file);
|
||||||
Logger(`Processing: ${file.path}`, LOG_LEVEL.VERBOSE);
|
Logger(`Processing: ${file.path}`, LOG_LEVEL.VERBOSE);
|
||||||
content = await arrayBufferToBase64(contentBin);
|
try {
|
||||||
|
content = await arrayBufferToBase64(contentBin);
|
||||||
|
} catch (ex) {
|
||||||
|
Logger(`The file ${file.path} could not be encoded`);
|
||||||
|
Logger(ex, LOG_LEVEL.VERBOSE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
datatype = "newnote";
|
datatype = "newnote";
|
||||||
} else {
|
} else {
|
||||||
content = await this.app.vault.read(file);
|
content = await this.app.vault.read(file);
|
||||||
@@ -2732,7 +2728,14 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (cache instanceof ArrayBuffer) {
|
if (cache instanceof ArrayBuffer) {
|
||||||
content = await arrayBufferToBase64(cache);
|
Logger(`Processing: ${file.path}`, LOG_LEVEL.VERBOSE);
|
||||||
|
try {
|
||||||
|
content = await arrayBufferToBase64(cache);
|
||||||
|
} catch (ex) {
|
||||||
|
Logger(`The file ${file.path} could not be encoded`);
|
||||||
|
Logger(ex, LOG_LEVEL.VERBOSE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
datatype = "newnote"
|
datatype = "newnote"
|
||||||
} else {
|
} else {
|
||||||
content = cache;
|
content = cache;
|
||||||
@@ -3081,7 +3084,14 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
async storeInternalFileToDatabase(file: InternalFileInfo, forceWrite = false) {
|
async storeInternalFileToDatabase(file: InternalFileInfo, forceWrite = false) {
|
||||||
const id = filename2idInternalMetadata(path2id(file.path));
|
const id = filename2idInternalMetadata(path2id(file.path));
|
||||||
const contentBin = await this.app.vault.adapter.readBinary(file.path);
|
const contentBin = await this.app.vault.adapter.readBinary(file.path);
|
||||||
const content = await arrayBufferToBase64(contentBin);
|
let content: string[];
|
||||||
|
try {
|
||||||
|
content = await arrayBufferToBase64(contentBin);
|
||||||
|
} catch (ex) {
|
||||||
|
Logger(`The file ${file.path} could not be encoded`);
|
||||||
|
Logger(ex, LOG_LEVEL.VERBOSE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const mtime = file.mtime;
|
const mtime = file.mtime;
|
||||||
return await runWithLock("file-" + id, false, async () => {
|
return await runWithLock("file-" + id, false, async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
15
updates.md
15
updates.md
@@ -54,5 +54,20 @@
|
|||||||
- File names can now be made platform-appropriate.
|
- File names can now be made platform-appropriate.
|
||||||
- Refactored:
|
- Refactored:
|
||||||
- Some redundant implementations have been sorted out.
|
- 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
|
||||||
|
- Fixed(Urgent):
|
||||||
|
- The modified document will be reflected in the storage now.
|
||||||
|
|
||||||
... To continue on to `updates_old.md`.
|
... To continue on to `updates_old.md`.
|
||||||
Reference in New Issue
Block a user