Compare commits

...

8 Commits

Author SHA1 Message Date
vorotamoroz
983d9248ed bump 2023-10-13 04:23:59 +01:00
vorotamoroz
7240e84328 - New feature:
- We can launch Customization sync from the Ribbon if we enable it.
- Fixed:
  - Setup URI is now back to the previous spec; be encrypted by V1.
  - The Settings dialogue is now registered at the beginning of the start-up process.
- Improved:
  - Enumerating documents has been faster.
2023-10-13 04:22:24 +01:00
vorotamoroz
0d55ae2532 Merge pull request #298 from LiamSwayne/patch-1
Grammar fix
2023-10-04 13:07:42 +09:00
Liam Swayne
dbd284f5dd grammar fix 2023-10-03 22:26:30 -04:00
vorotamoroz
c000a02f4a bump 2023-10-02 10:39:54 +01:00
vorotamoroz
79754f48d6 New feature:
- We can delete all data of customization sync from the `Delete all customization sync data` on the `Hatch` pane.
Fixed:
- Prevent keep restarting on iOS by yielding microtasks.
2023-10-02 10:38:54 +01:00
vorotamoroz
dd7a40630b bump 2023-10-02 09:54:56 +01:00
vorotamoroz
14406f8213 - Fixed:
- No more UI freezing and keep restarting on iOS.
  - Diff of Non-markdown documents are now shown correctly.
- Improved:
  - Performance has been a bit improved.
  - Customization sync has gotten faster.
    - However, We lost forward compatibility again (only for this feature). Please update all devices.
- Misc
  - Terser configuration has been more aggressive.
2023-10-02 09:53:41 +01:00
13 changed files with 233 additions and 97 deletions

View File

@@ -66,7 +66,7 @@ Synchronization status is shown in statusbar.
- ↓ Downloaded chunks and metadata
- ⏳ Number of pending processes
- 🧩 Number of files waiting for their chunks.
If you have deleted or renamed files, please wait until ⏳ icon disappeared.
If you have deleted or renamed files, please wait until ⏳ icon disappears.
## Hints

View File

@@ -48,7 +48,9 @@ const terserOpt = {
lhs_constants: true,
hoist_props: true,
side_effects: true,
// if_return: true,
if_return: true,
ecma: 2018,
unused: true,
},
// mangle: {
// // mangle options

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-livesync",
"name": "Self-hosted LiveSync",
"version": "0.20.0",
"version": "0.20.3",
"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",

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "obsidian-livesync",
"version": "0.20.0",
"version": "0.20.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "obsidian-livesync",
"version": "0.20.0",
"version": "0.20.3",
"license": "MIT",
"dependencies": {
"diff-match-patch": "^1.0.5",

View File

@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
"version": "0.20.0",
"version": "0.20.3",
"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",

View File

@@ -16,22 +16,108 @@ import { PluginDialogModal } from "./dialogs";
import { JsonResolveModal } from "./JsonResolveModal";
import { pipeGeneratorToGenerator, processAllGeneratorTasksWithConcurrencyLimit } from './lib/src/task';
const d = "\u200b";
const d2 = "\n";
function serialize(data: PluginDataEx): string {
// To improve performance, make JSON manually.
// For higher performance, create custom plug-in data strings.
// Self-hosted LiveSync uses `\n` to split chunks. Therefore, grouping together those with similar entropy would work nicely.
return `{"category":"${data.category}","name":"${data.name}","term":${JSON.stringify(data.term)}
${data.version ? `,"version":"${data.version}"` : ""},
"mtime":${data.mtime},
"files":[
${data.files.map(file => `{"filename":"${file.filename}"${file.displayName ? `,"displayName":"${file.displayName}"` : ""}${file.version ? `,"version":"${file.version}"` : ""},
"mtime":${file.mtime},"size":${file.size}
,"data":[${file.data.map(e => `"${e}"`).join(",")
}]}`).join(",")
}]}`
let ret = "";
ret += ":";
ret += data.category + d + data.name + d + data.term + d2;
ret += (data.version ?? "") + d2;
ret += data.mtime + d2;
for (const file of data.files) {
ret += file.filename + d + (file.displayName ?? "") + d + (file.version ?? "") + d2;
ret += file.mtime + d + file.size + d2;
for (const data of file.data ?? []) {
ret += data + d
}
ret += d2;
}
return ret;
}
function fetchToken(source: string, from: number): [next: number, token: string] {
const limitIdx = source.indexOf(d2, from);
const limit = limitIdx == -1 ? source.length : limitIdx;
const delimiterIdx = source.indexOf(d, from);
const delimiter = delimiterIdx == -1 ? source.length : delimiterIdx;
const tokenEnd = Math.min(limit, delimiter);
let next = tokenEnd;
if (limit < delimiter) {
next = tokenEnd;
} else {
next = tokenEnd + 1
}
return [next, source.substring(from, tokenEnd)];
}
function getTokenizer(source: string) {
const t = {
pos: 1,
next() {
const [next, token] = fetchToken(source, this.pos);
this.pos = next;
return token;
},
nextLine() {
const nextPos = source.indexOf(d2, this.pos);
if (nextPos == -1) {
this.pos = source.length;
} else {
this.pos = nextPos + 1;
}
}
}
return t;
}
function deserialize2(str: string): PluginDataEx {
const tokens = getTokenizer(str);
const ret = {} as PluginDataEx;
const category = tokens.next();
const name = tokens.next();
const term = tokens.next();
tokens.nextLine();
const version = tokens.next();
tokens.nextLine();
const mtime = Number(tokens.next());
tokens.nextLine();
const result: PluginDataEx = Object.assign(ret,
{ category, name, term, version, mtime, files: [] as PluginDataExFile[] })
let filename = "";
do {
filename = tokens.next();
if (!filename) break;
const displayName = tokens.next();
const version = tokens.next();
tokens.nextLine();
const mtime = Number(tokens.next());
const size = Number(tokens.next());
tokens.nextLine();
const data = [] as string[];
let piece = "";
do {
piece = tokens.next();
if (piece == "") break;
data.push(piece);
} while (piece != "");
result.files.push(
{
filename,
displayName,
version,
mtime,
size,
data
}
)
} while (filename);
return result;
}
function deserialize<T>(str: string, def: T) {
try {
if (str[0] == ":") return deserialize2(str);
return JSON.parse(str) as T;
} catch (ex) {
try {

View File

@@ -60,7 +60,7 @@ export class SetupLiveSync extends LiveSyncCommands {
delete setting[k];
}
}
const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(setting), encryptingPassphrase, false, false));
const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(setting), encryptingPassphrase, false, true));
const uri = `${configURIBase}${encryptedSetting}`;
await navigator.clipboard.writeText(uri);
Logger("Setup URI copied to clipboard", LOG_LEVEL_NOTICE);
@@ -70,7 +70,7 @@ export class SetupLiveSync extends LiveSyncCommands {
if (encryptingPassphrase === false)
return;
const setting = { ...this.settings, configPassphraseStore: "", encryptedCouchDBConnection: "", encryptedPassphrase: "" };
const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(setting), encryptingPassphrase, false, false));
const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(setting), encryptingPassphrase, false, true));
const uri = `${configURIBase}${encryptedSetting}`;
await navigator.clipboard.writeText(uri);
Logger("Setup URI copied to clipboard", LOG_LEVEL_NOTICE);

View File

@@ -99,7 +99,7 @@ export class DocumentHistoryModal extends Modal {
const w2 = await db.getDBEntry(this.file, { rev: oldRev }, false, false, true);
if (w2 != false) {
const dmp = new diff_match_patch();
const w2data = w2.datatype == "plain" ? getDocData(w2.data) : readString(new Uint8Array(decodeBinary(w.data)));
const w2data = w2.datatype == "plain" ? getDocData(w2.data) : readString(new Uint8Array(decodeBinary(w2.data)));
const diff = dmp.diff_main(w2data, w1data);
dmp.diff_cleanupSemantic(diff);
for (const v of diff) {

View File

@@ -1657,6 +1657,22 @@ ${stringifyYaml(pluginConfig)}`;
}
Logger(`Converting finished`, LOG_LEVEL_NOTICE);
}));
new Setting(containerHatchEl)
.setName("Delete all customization sync data")
.addButton((button) =>
button
.setButtonText("Delete")
.setDisabled(false)
.setWarning()
.onClick(async () => {
Logger(`Deleting customization sync data`, LOG_LEVEL_NOTICE);
const entriesToDelete = (await this.plugin.localDatabase.allDocsRaw({ startkey: "ix:", endkey: "ix:\u{10ffff}", include_docs: true }));
const newData = entriesToDelete.rows.map(e => ({ ...e.doc, _deleted: true }));
const r = await this.plugin.localDatabase.bulkDocsRaw(newData as any[]);
// Do not care about the result.
Logger(`${r.length} items have been removed, to confirm how many items are left, please perform it again.`, LOG_LEVEL_NOTICE);
}))
new Setting(containerHatchEl)
.setName("Suspend file watching")
.setDesc("Stop watching for file change.")

View File

@@ -274,7 +274,7 @@
{/if}
{:else}
<span class="spacer" />
<span class="message even">All devices are even</span>
<span class="message even">All the same or non-existent</span>
<button disabled />
<button disabled />
{/if}

Submodule src/lib updated: 4a955add9c...b2788a8d98

View File

@@ -515,58 +515,7 @@ Note: We can always able to read V1 format. It will be progressively converted.
return ret == CHOICE_V1;
}
async onload() {
logStore.subscribe(e => this.addLog(e.message, e.level, e.key));
Logger("loading plugin");
//@ts-ignore
const manifestVersion: string = MANIFEST_VERSION || "0.0.0";
//@ts-ignore
const packageVersion: string = PACKAGE_VERSION || "0.0.0";
this.manifestVersion = manifestVersion;
this.packageVersion = packageVersion;
Logger(`Self-hosted LiveSync v${manifestVersion} ${packageVersion} `);
const lsKey = "obsidian-live-sync-ver" + this.getVaultName();
const last_version = localStorage.getItem(lsKey);
await this.loadSettings();
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
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);
}
//@ts-ignore
if (this.app.isMobile) {
this.isMobile = true;
this.settings.disableRequestURI = true;
}
if (last_version && Number(last_version) < VER) {
this.settings.liveSync = false;
this.settings.syncOnSave = false;
this.settings.syncOnEditorSave = false;
this.settings.syncOnStart = false;
this.settings.syncOnFileOpen = false;
this.settings.syncAfterMerge = false;
this.settings.periodicReplication = false;
this.settings.versionUpFlash = "Self-hosted LiveSync has been upgraded and some behaviors have changed incompatibly. All automatic synchronization is now disabled temporary. Ensure that other devices are also upgraded, and enable synchronization again.";
this.saveSettings();
}
localStorage.setItem(lsKey, `${VER}`);
await this.openDatabase();
this.watchWorkspaceOpen = debounce(this.watchWorkspaceOpen.bind(this), 1000, false);
this.watchWindowVisibility = debounce(this.watchWindowVisibility.bind(this), 1000, false);
this.watchOnline = debounce(this.watchOnline.bind(this), 500, false);
this.parseReplicationResult = this.parseReplicationResult.bind(this);
this.loadQueuedFiles = this.loadQueuedFiles.bind(this);
this.triggerRealizeSettingSyncMode = debounce(this.triggerRealizeSettingSyncMode.bind(this), 1000);
this.statusBar = this.addStatusBarItem();
this.statusBar.addClass("syncstatusbar");
addUIs() {
addIcon(
"replicate",
`<g transform="matrix(1.15 0 0 1.15 -8.31 -9.52)" fill="currentColor" fill-rule="evenodd">
@@ -583,14 +532,23 @@ Note: We can always able to read V1 format. It will be progressively converted.
<path d="m106 346v44h70v-44zm45 16h-20v-8h20z"/>
</g>`
);
await Promise.all(this.addOns.map(e => e.onload()));
addIcon(
"custom-sync",
`<g transform="rotate(-90 75 218)" fill="currentColor" fill-rule="evenodd">
<path d="m272 166-9.38 9.38 9.38 9.38 9.38-9.38c1.96-1.93 5.11-1.9 7.03 0.058 1.91 1.94 1.91 5.04 0 6.98l-9.38 9.38 5.86 5.86-11.7 11.7c-8.34 8.35-21.4 9.68-31.3 3.19l-3.84 3.98c-8.45 8.7-20.1 13.6-32.2 13.6h-5.55v-9.95h5.55c9.43-0.0182 18.5-3.84 25-10.6l3.95-4.09c-6.54-9.86-5.23-23 3.14-31.3l11.7-11.7 5.86 5.86 9.38-9.38c1.96-1.93 5.11-1.9 7.03 0.0564 1.91 1.93 1.91 5.04 2e-3 6.98z"/>
</g>`
);
this.addRibbonIcon("replicate", "Replicate", async () => {
await this.replicate(true);
});
}).addClass("livesync-ribbon-replicate");
this.addRibbonIcon("view-log", "Show log", () => {
this.showView(VIEW_TYPE_LOG);
});
}).addClass("livesync-ribbon-showlog");
this.addRibbonIcon("custom-sync", "Show Customization sync", () => {
this.addOnConfigSync.showPluginSyncModal();
}).addClass("livesync-ribbon-showcustom");
this.addCommand({
id: "view-log",
name: "Show log",
@@ -598,8 +556,6 @@ Note: We can always able to read V1 format. It will be progressively converted.
this.showView(VIEW_TYPE_LOG);
}
});
this.addSettingTab(new ObsidianLiveSyncSettingTab(this.app, this));
this.app.workspace.onLayoutReady(this.onLayoutReady.bind(this));
this.addCommand({
id: "livesync-replicate",
@@ -672,8 +628,6 @@ Note: We can always able to read V1 format. It will be progressively converted.
}
})
this.triggerRealizeSettingSyncMode = debounce(this.triggerRealizeSettingSyncMode.bind(this), 1000);
this.addCommand({
id: "livesync-filehistory",
name: "Pick a file to show history",
@@ -709,6 +663,13 @@ Note: We can always able to read V1 format. It will be progressively converted.
this.replicator.terminateSync();
},
})
this.addCommand({
id: "livesync-global-history",
name: "Show vault history",
callback: () => {
this.showGlobalHistory()
}
})
this.registerView(
VIEW_TYPE_GLOBAL_HISTORY,
@@ -718,13 +679,66 @@ Note: We can always able to read V1 format. It will be progressively converted.
VIEW_TYPE_LOG,
(leaf) => new LogPaneView(leaf, this)
);
this.addCommand({
id: "livesync-global-history",
name: "Show vault history",
callback: () => {
this.showGlobalHistory()
}
})
}
async onload() {
logStore.subscribe(e => this.addLog(e.message, e.level, e.key));
Logger("loading plugin");
this.addSettingTab(new ObsidianLiveSyncSettingTab(this.app, this));
this.addUIs();
//@ts-ignore
const manifestVersion: string = MANIFEST_VERSION || "0.0.0";
//@ts-ignore
const packageVersion: string = PACKAGE_VERSION || "0.0.0";
this.manifestVersion = manifestVersion;
this.packageVersion = packageVersion;
Logger(`Self-hosted LiveSync v${manifestVersion} ${packageVersion} `);
const lsKey = "obsidian-live-sync-ver" + this.getVaultName();
const last_version = localStorage.getItem(lsKey);
await this.loadSettings();
this.statusBar = this.addStatusBarItem();
this.statusBar.addClass("syncstatusbar");
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
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);
}
//@ts-ignore
if (this.app.isMobile) {
this.isMobile = true;
this.settings.disableRequestURI = true;
}
if (last_version && Number(last_version) < VER) {
this.settings.liveSync = false;
this.settings.syncOnSave = false;
this.settings.syncOnEditorSave = false;
this.settings.syncOnStart = false;
this.settings.syncOnFileOpen = false;
this.settings.syncAfterMerge = false;
this.settings.periodicReplication = false;
this.settings.versionUpFlash = "Self-hosted LiveSync has been upgraded and some behaviors have changed incompatibly. All automatic synchronization is now disabled temporary. Ensure that other devices are also upgraded, and enable synchronization again.";
this.saveSettings();
}
localStorage.setItem(lsKey, `${VER}`);
await this.openDatabase();
this.watchWorkspaceOpen = debounce(this.watchWorkspaceOpen.bind(this), 1000, false);
this.watchWindowVisibility = debounce(this.watchWindowVisibility.bind(this), 1000, false);
this.watchOnline = debounce(this.watchOnline.bind(this), 500, false);
this.parseReplicationResult = this.parseReplicationResult.bind(this);
this.loadQueuedFiles = this.loadQueuedFiles.bind(this);
this.triggerRealizeSettingSyncMode = debounce(this.triggerRealizeSettingSyncMode.bind(this), 1000);
await Promise.all(this.addOns.map(e => e.onload()));
this.app.workspace.onLayoutReady(this.onLayoutReady.bind(this));
this.triggerRealizeSettingSyncMode = debounce(this.triggerRealizeSettingSyncMode.bind(this), 1000);
}
async showView(viewType: string) {
const leaves = this.app.workspace.getLeavesOfType(viewType);
@@ -1609,6 +1623,9 @@ Note: We can always able to read V1 format. It will be progressively converted.
this.replicator.openReplication(this.settings, true, false);
}
const q = activeDocument.querySelector(`.livesync-ribbon-showcustom`);
q?.toggleClass("sls-hidden", !this.settings.usePluginSync);
this.periodicSyncProcessor.enable(this.settings.periodicReplication ? this.settings.periodicReplicationInterval * 1000 : 0);

View File

@@ -15,7 +15,31 @@ This format change gives us the ability to detect some `marks` in the binary fil
Now only a few chunks are transferred, even if we add a comment to the PDF or put new files into the ZIP archives.
#### Version history
- 0.20.3
- New feature:
- We can launch Customization sync from the Ribbon if we enabled it.
- Fixed:
- Setup URI is now back to the previous spec; be encrypted by V1.
- It may avoid the trouble with iOS 17.
- The Settings dialogue is now registered at the beginning of the start-up process.
- We can change the configuration even though LiveSync could not be launched in normal.
- Improved:
- Enumerating documents has been faster.
- 0.20.2
- New feature:
- We can delete all data of customization sync from the `Delete all customization sync data` on the `Hatch` pane.
- Fixed:
- Prevent keep restarting on iOS by yielding microtasks.
- 0.20.1
- Fixed:
- No more UI freezing and keep restarting on iOS.
- Diff of Non-markdown documents are now shown correctly.
- Improved:
- Performance has been a bit improved.
- Customization sync has gotten faster.
- However, We lost forward compatibility again (only for this feature). Please update all devices.
- Misc
- Terser configuration has been more aggressive.
- 0.20.0
- Improved:
- A New binary file handling implemented
@@ -27,12 +51,3 @@ Now only a few chunks are transferred, even if we add a comment to the PDF or pu
- Some Lint warnings have been suppressed.
... To continue on to `updates_old.md`.
- Improved:
- A New binary file handling implemented
- A new encrypted format has been implemented
- Now the chunk sizes will be adjusted for efficient sync
- Fixed:
- levels of exception in some logs have been fixed
- Tidied:
- Some Lint warnings have been suppressed.