mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-03-01 07:28:47 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccb3dd52de | ||
|
|
3e5f4c8946 | ||
|
|
54e64c59a9 | ||
|
|
f2b667d75e | ||
|
|
9b1588a65b |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-livesync",
|
||||
"name": "Self-hosted LiveSync",
|
||||
"version": "0.24.7",
|
||||
"version": "0.24.9",
|
||||
"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",
|
||||
|
||||
923
package-lock.json
generated
923
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.24.7",
|
||||
"version": "0.24.9",
|
||||
"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",
|
||||
@@ -32,17 +32,17 @@
|
||||
"@types/pouchdb-mapreduce": "^6.1.10",
|
||||
"@types/pouchdb-replication": "^6.4.7",
|
||||
"@types/transform-pouch": "^1.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^8.4.0",
|
||||
"@typescript-eslint/parser": "^8.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.23.0",
|
||||
"@typescript-eslint/parser": "^8.23.0",
|
||||
"builtin-modules": "^4.0.0",
|
||||
"esbuild": "0.23.1",
|
||||
"esbuild-svelte": "^0.8.1",
|
||||
"esbuild": "0.24.2",
|
||||
"esbuild-svelte": "^0.9.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-plugin-import": "^2.30.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"events": "^3.3.0",
|
||||
"obsidian": "^1.6.6",
|
||||
"postcss": "^8.4.45",
|
||||
"obsidian": "^1.7.2",
|
||||
"postcss": "^8.5.1",
|
||||
"postcss-load-config": "^6.0.1",
|
||||
"pouchdb-adapter-http": "^9.0.0",
|
||||
"pouchdb-adapter-idb": "^9.0.0",
|
||||
@@ -54,13 +54,13 @@
|
||||
"pouchdb-merge": "^9.0.0",
|
||||
"pouchdb-replication": "^9.0.0",
|
||||
"pouchdb-utils": "^9.0.0",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier": "^3.4.2",
|
||||
"svelte": "^4.2.19",
|
||||
"svelte-preprocess": "^6.0.2",
|
||||
"terser": "^5.31.6",
|
||||
"terser": "^5.37.0",
|
||||
"transform-pouch": "^2.0.0",
|
||||
"tslib": "^2.7.0",
|
||||
"typescript": "^5.5.4"
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.645.0",
|
||||
@@ -70,10 +70,10 @@
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"esbuild-plugin-inline-worker": "^0.1.1",
|
||||
"fflate": "^0.8.2",
|
||||
"idb": "^8.0.0",
|
||||
"idb": "^8.0.2",
|
||||
"minimatch": "^10.0.1",
|
||||
"octagonal-wheels": "^0.1.21",
|
||||
"svelte-check": "^4.0.4",
|
||||
"octagonal-wheels": "^0.1.22",
|
||||
"svelte-check": "^4.1.4",
|
||||
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
265
src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts
Normal file
265
src/features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
import { sizeToHumanReadable } from "octagonal-wheels/number";
|
||||
import { LOG_LEVEL_NOTICE, type MetaEntry } from "../../lib/src/common/types";
|
||||
import { getNoFromRev } from "../../lib/src/pouchdb/LiveSyncLocalDB";
|
||||
import type { IObsidianModule } from "../../modules/AbstractObsidianModule";
|
||||
import { LiveSyncCommands } from "../LiveSyncCommands";
|
||||
|
||||
export class LocalDatabaseMaintenance extends LiveSyncCommands implements IObsidianModule {
|
||||
$everyOnload(): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
onunload(): void {
|
||||
// NO OP.
|
||||
}
|
||||
onload(): void | Promise<void> {
|
||||
// NO OP.
|
||||
}
|
||||
async allChunks(includeDeleted: boolean = false) {
|
||||
const p = this._progress("", LOG_LEVEL_NOTICE);
|
||||
p.log("Retrieving chunks informations..");
|
||||
try {
|
||||
const ret = await this.localDatabase.allChunks(includeDeleted);
|
||||
return ret;
|
||||
} finally {
|
||||
p.done();
|
||||
}
|
||||
}
|
||||
get database() {
|
||||
return this.localDatabase.localDatabase;
|
||||
}
|
||||
clearHash() {
|
||||
this.localDatabase.hashCaches.clear();
|
||||
}
|
||||
|
||||
async confirm(title: string, message: string, affirmative = "Yes", negative = "No") {
|
||||
return (
|
||||
(await this.plugin.confirm.askSelectStringDialogue(message, [affirmative, negative], {
|
||||
title,
|
||||
defaultAction: affirmative,
|
||||
})) === affirmative
|
||||
);
|
||||
}
|
||||
isAvailable() {
|
||||
if (!this.settings.doNotUseFixedRevisionForChunks) {
|
||||
this._notice("Please enable 'Compute revisions for chunks' in settings to use Garbage Collection.");
|
||||
return false;
|
||||
}
|
||||
if (this.settings.readChunksOnline) {
|
||||
this._notice("Please disable 'Read chunks online' in settings to use Garbage Collection.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Resurrect deleted chunks that are still used in the database.
|
||||
*/
|
||||
async resurrectChunks() {
|
||||
if (!this.isAvailable()) return;
|
||||
const { used, existing } = await this.allChunks(true);
|
||||
const excessiveDeletions = [...existing]
|
||||
.filter(([key, e]) => e._deleted)
|
||||
.filter(([key, e]) => used.has(e._id))
|
||||
.map(([key, e]) => e);
|
||||
const completelyLostChunks = [] as string[];
|
||||
// Data lost chunks : chunks that are deleted and data is purged.
|
||||
const dataLostChunks = [...existing]
|
||||
.filter(([key, e]) => e._deleted && e.data === "")
|
||||
.map(([key, e]) => e)
|
||||
.filter((e) => used.has(e._id));
|
||||
for (const e of dataLostChunks) {
|
||||
// Retrieve the data from the previous revision.
|
||||
const doc = await this.database.get(e._id, { rev: e._rev, revs: true, revs_info: true, conflicts: true });
|
||||
const history = doc._revs_info || [];
|
||||
// Chunks are immutable. So, we can resurrect the chunk by copying the data from any of previous revisions.
|
||||
let resurrected = null as null | string;
|
||||
const availableRevs = history
|
||||
.filter((e) => e.status == "available")
|
||||
.map((e) => e.rev)
|
||||
.sort((a, b) => getNoFromRev(a) - getNoFromRev(b));
|
||||
for (const rev of availableRevs) {
|
||||
const revDoc = await this.database.get(e._id, { rev: rev });
|
||||
if (revDoc.type == "leaf" && revDoc.data !== "") {
|
||||
// Found the data.
|
||||
resurrected = revDoc.data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If the data is not found, we cannot resurrect the chunk, add it to the excessiveDeletions.
|
||||
if (resurrected !== null) {
|
||||
excessiveDeletions.push({ ...e, data: resurrected, _deleted: false });
|
||||
} else {
|
||||
completelyLostChunks.push(e._id);
|
||||
}
|
||||
}
|
||||
// Chunks to be resurrected.
|
||||
const resurrectChunks = excessiveDeletions.filter((e) => e.data !== "").map((e) => ({ ...e, _deleted: false }));
|
||||
|
||||
if (resurrectChunks.length == 0) {
|
||||
this._notice("No chunks are found to be resurrected.");
|
||||
return;
|
||||
}
|
||||
const message = `We have following chunks that are deleted but still used in the database.
|
||||
|
||||
- Completely lost chunks: ${completelyLostChunks.length}
|
||||
- Resurrectable chunks: ${resurrectChunks.length}
|
||||
|
||||
Do you want to resurrect these chunks?`;
|
||||
if (await this.confirm("Resurrect Chunks", message, "Resurrect", "Cancel")) {
|
||||
const result = await this.database.bulkDocs(resurrectChunks);
|
||||
this.clearHash();
|
||||
const resurrectedChunks = result.filter((e) => "ok" in e).map((e) => e.id);
|
||||
this._notice(`Resurrected chunks: ${resurrectedChunks.length} / ${resurrectChunks.length}`);
|
||||
} else {
|
||||
this._notice("Resurrect operation is cancelled.");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Commit deletion of files that are marked as deleted.
|
||||
* This method makes the deletion permanent, and the files will not be recovered.
|
||||
* After this, chunks that are used in the deleted files become ready for compaction.
|
||||
*/
|
||||
async commitFileDeletion() {
|
||||
if (!this.isAvailable()) return;
|
||||
const p = this._progress("", LOG_LEVEL_NOTICE);
|
||||
p.log("Searching for deleted files..");
|
||||
const docs = await this.database.allDocs<MetaEntry>({ include_docs: true });
|
||||
const deletedDocs = docs.rows.filter(
|
||||
(e) => (e.doc?.type == "newnote" || e.doc?.type == "plain") && e.doc?.deleted
|
||||
);
|
||||
if (deletedDocs.length == 0) {
|
||||
p.done("No deleted files found.");
|
||||
return;
|
||||
}
|
||||
p.log(`Found ${deletedDocs.length} deleted files.`);
|
||||
|
||||
const message = `We have following files that are marked as deleted.
|
||||
|
||||
- Deleted files: ${deletedDocs.length}
|
||||
|
||||
Are you sure to delete these files permanently?
|
||||
|
||||
Note: **Make sure to synchronise all devices before deletion.**
|
||||
|
||||
> [!Note]
|
||||
> This operation affects the database permanently. Deleted files will not be recovered after this operation.
|
||||
> And, the chunks that are used in the deleted files will be ready for compaction.`;
|
||||
|
||||
const deletingDocs = deletedDocs.map((e) => ({ ...e.doc, _deleted: true }) as MetaEntry);
|
||||
|
||||
if (await this.confirm("Delete Files", message, "Delete", "Cancel")) {
|
||||
const result = await this.database.bulkDocs(deletingDocs);
|
||||
this.clearHash();
|
||||
p.done(`Deleted ${result.filter((e) => "ok" in e).length} / ${deletedDocs.length} files.`);
|
||||
} else {
|
||||
p.done("Deletion operation is cancelled.");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Commit deletion of chunks that are not used in the database.
|
||||
* This method makes the deletion permanent, and the chunks will not be recovered if the database run compaction.
|
||||
* After this, the database can shrink the database size by compaction.
|
||||
* It is recommended to compact the database after this operation (History should be kept once before compaction).
|
||||
*/
|
||||
async commitChunkDeletion() {
|
||||
if (!this.isAvailable()) return;
|
||||
const { existing } = await this.allChunks(true);
|
||||
const deletedChunks = [...existing].filter(([key, e]) => e._deleted && e.data !== "").map(([key, e]) => e);
|
||||
const deletedNotVacantChunks = deletedChunks.map((e) => ({ ...e, data: "", _deleted: true }));
|
||||
const size = deletedChunks.reduce((acc, e) => acc + e.data.length, 0);
|
||||
const humanSize = sizeToHumanReadable(size);
|
||||
const message = `We have following chunks that are marked as deleted.
|
||||
|
||||
- Deleted chunks: ${deletedNotVacantChunks.length} (${humanSize})
|
||||
|
||||
Are you sure to delete these chunks permanently?
|
||||
|
||||
Note: **Make sure to synchronise all devices before deletion.**
|
||||
|
||||
> [!Note]
|
||||
> This operation finally reduces the capacity of the remote.`;
|
||||
|
||||
if (deletedNotVacantChunks.length == 0) {
|
||||
this._notice("No deleted chunks found.");
|
||||
return;
|
||||
}
|
||||
if (await this.confirm("Delete Chunks", message, "Delete", "Cancel")) {
|
||||
const result = await this.database.bulkDocs(deletedNotVacantChunks);
|
||||
this.clearHash();
|
||||
this._notice(
|
||||
`Deleted chunks: ${result.filter((e) => "ok" in e).length} / ${deletedNotVacantChunks.length}`
|
||||
);
|
||||
} else {
|
||||
this._notice("Deletion operation is cancelled.");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Compact the database.
|
||||
* This method removes all deleted chunks that are not used in the database.
|
||||
* Make sure all devices are synchronized before running this method.
|
||||
*/
|
||||
async markUnusedChunks() {
|
||||
if (!this.isAvailable()) return;
|
||||
const { used, existing } = await this.allChunks();
|
||||
const existChunks = [...existing];
|
||||
const unusedChunks = existChunks.filter(([key, e]) => !used.has(e._id)).map(([key, e]) => e);
|
||||
const deleteChunks = unusedChunks.map((e) => ({
|
||||
...e,
|
||||
_deleted: true,
|
||||
}));
|
||||
const size = deleteChunks.reduce((acc, e) => acc + e.data.length, 0);
|
||||
const humanSize = sizeToHumanReadable(size);
|
||||
if (deleteChunks.length == 0) {
|
||||
this._notice("No unused chunks found.");
|
||||
return;
|
||||
}
|
||||
const message = `We have following chunks that are not used from any files.
|
||||
|
||||
- Chunks: ${deleteChunks.length} (${humanSize})
|
||||
|
||||
Are you sure to mark these chunks to be deleted?
|
||||
|
||||
Note: **Make sure to synchronise all devices before deletion.**
|
||||
|
||||
> [!Note]
|
||||
> This operation will not reduces the capacity of the remote until permanent deletion.`;
|
||||
|
||||
if (await this.confirm("Mark unused chunks", message, "Mark", "Cancel")) {
|
||||
const result = await this.database.bulkDocs(deleteChunks);
|
||||
this.clearHash();
|
||||
this._notice(`Marked chunks: ${result.filter((e) => "ok" in e).length} / ${deleteChunks.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
async removeUnusedChunks() {
|
||||
const { used, existing } = await this.allChunks();
|
||||
const existChunks = [...existing];
|
||||
const unusedChunks = existChunks.filter(([key, e]) => !used.has(e._id)).map(([key, e]) => e);
|
||||
const deleteChunks = unusedChunks.map((e) => ({
|
||||
...e,
|
||||
data: "",
|
||||
_deleted: true,
|
||||
}));
|
||||
const size = unusedChunks.reduce((acc, e) => acc + e.data.length, 0);
|
||||
const humanSize = sizeToHumanReadable(size);
|
||||
if (deleteChunks.length == 0) {
|
||||
this._notice("No unused chunks found.");
|
||||
return;
|
||||
}
|
||||
const message = `We have following chunks that are not used from any files.
|
||||
|
||||
- Chunks: ${deleteChunks.length} (${humanSize})
|
||||
|
||||
Are you sure to delete these chunks?
|
||||
|
||||
Note: **Make sure to synchronise all devices before deletion.**
|
||||
|
||||
> [!Note]
|
||||
> Chunks referenced from deleted files are not deleted. Please run "Commit File Deletion" before this operation.`;
|
||||
|
||||
if (await this.confirm("Mark unused chunks", message, "Mark", "Cancel")) {
|
||||
const result = await this.database.bulkDocs(deleteChunks);
|
||||
this._notice(`Deleted chunks: ${result.filter((e) => "ok" in e).length} / ${deleteChunks.length}`);
|
||||
this.clearHash();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
src/lib
2
src/lib
Submodule src/lib updated: 89e825ef3b...90ace6de16
@@ -81,6 +81,7 @@ import { ModuleRebuilder } from "./modules/core/ModuleRebuilder.ts";
|
||||
import { ModuleReplicateTest } from "./modules/extras/ModuleReplicateTest.ts";
|
||||
import { ModuleLiveSyncMain } from "./modules/main/ModuleLiveSyncMain.ts";
|
||||
import { ModuleExtraSyncObsidian } from "./modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts";
|
||||
import { LocalDatabaseMaintenance } from "./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts";
|
||||
|
||||
function throwShouldBeOverridden(): never {
|
||||
throw new Error("This function should be overridden by the module.");
|
||||
@@ -117,7 +118,7 @@ export default class ObsidianLiveSyncPlugin
|
||||
}
|
||||
|
||||
// Keep order to display the dialogue in order.
|
||||
addOns = [new ConfigSync(this), new HiddenFileSync(this)] as LiveSyncCommands[];
|
||||
addOns = [new ConfigSync(this), new HiddenFileSync(this), new LocalDatabaseMaintenance(this)] as LiveSyncCommands[];
|
||||
|
||||
modules = [
|
||||
new ModuleLiveSyncMain(this),
|
||||
|
||||
@@ -205,13 +205,10 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
||||
): Promise<boolean> {
|
||||
const file = typeof info === "string" ? this.storage.getFileStub(info) : info;
|
||||
const mode = file == null ? "create" : "modify";
|
||||
|
||||
const docEntry =
|
||||
typeof entryInfo === "string"
|
||||
? await this.db.fetchEntryMeta(entryInfo, undefined, true)
|
||||
: await this.db.fetchEntryMeta(entryInfo.path, undefined, true);
|
||||
const pathFromEntryInfo = typeof entryInfo === "string" ? entryInfo : getPath(entryInfo);
|
||||
const docEntry = await this.db.fetchEntryMeta(pathFromEntryInfo, undefined, true);
|
||||
if (!docEntry) {
|
||||
this._log(`File ${entryInfo} is not exist on the database`, LOG_LEVEL_VERBOSE);
|
||||
this._log(`File ${pathFromEntryInfo} is not exist on the database`, LOG_LEVEL_VERBOSE);
|
||||
return false;
|
||||
}
|
||||
const path = getPath(docEntry);
|
||||
@@ -275,7 +272,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
||||
}
|
||||
// 2. if not, the content should be checked.
|
||||
|
||||
if (shouldApplied) {
|
||||
if (!shouldApplied) {
|
||||
const readFile = await this.readFileFromStub(existDoc);
|
||||
if (await isDocContentSame(docData, readFile.body)) {
|
||||
// The content is same. So, we do not need to update the file.
|
||||
|
||||
@@ -231,11 +231,6 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
||||
const { file, doc } = e;
|
||||
if (!this.core.$$isFileSizeExceeded(file.stat.size) && !this.core.$$isFileSizeExceeded(doc.size)) {
|
||||
await this.syncFileBetweenDBandStorage(file, doc);
|
||||
// fireAndForget(() => this.checkAndApplySettingFromMarkdown(getPath(doc), true));
|
||||
// eventHub.emitEvent("event-file-changed", {
|
||||
// file: getPath(doc),
|
||||
// automated: true,
|
||||
// });
|
||||
} else {
|
||||
this._log(
|
||||
`SYNC DATABASE AND STORAGE: ${getPath(doc)} has been skipped due to file size exceeding the limit`,
|
||||
|
||||
@@ -77,6 +77,7 @@ import { JournalSyncMinio } from "../../../lib/src/replication/journal/objectsto
|
||||
import { ICHeader, ICXHeader, PSCHeader } from "../../../common/types.ts";
|
||||
import { HiddenFileSync } from "../../../features/HiddenFileSync/CmdHiddenFileSync.ts";
|
||||
import { EVENT_REQUEST_SHOW_HISTORY } from "../../../common/obsidianEvents.ts";
|
||||
import { LocalDatabaseMaintenance } from "../../../features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts";
|
||||
|
||||
export type OnUpdateResult = {
|
||||
visibility?: boolean;
|
||||
@@ -2880,6 +2881,7 @@ ${stringifyYaml(pluginConfig)}`;
|
||||
})
|
||||
)
|
||||
.addOnUpdate(onlyOnCouchDB);
|
||||
|
||||
new Setting(paneEl)
|
||||
.setName("Reset journal received history")
|
||||
.setDesc(
|
||||
@@ -2923,7 +2925,53 @@ ${stringifyYaml(pluginConfig)}`;
|
||||
)
|
||||
.addOnUpdate(onlyOnMinIO);
|
||||
});
|
||||
void addPanel(paneEl, "Garbage Collection (Beta)", (e) => e, onlyOnCouchDB).then((paneEl) => {
|
||||
new Setting(paneEl)
|
||||
.setName("Remove all orphaned chunks")
|
||||
.setDesc("Remove all orphaned chunks from the local database.")
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText("Remove")
|
||||
.setWarning()
|
||||
.setDisabled(false)
|
||||
.onClick(async () => {
|
||||
await this.plugin
|
||||
.getAddOn<LocalDatabaseMaintenance>(LocalDatabaseMaintenance.name)
|
||||
?.removeUnusedChunks();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(paneEl)
|
||||
.setName("Resurrect deleted chunks")
|
||||
.setDesc(
|
||||
"If you have deleted chunks before fully synchronised and missed some chunks, you possibly can resurrect them."
|
||||
)
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText("Try resurrect")
|
||||
.setWarning()
|
||||
.setDisabled(false)
|
||||
.onClick(async () => {
|
||||
await this.plugin
|
||||
.getAddOn<LocalDatabaseMaintenance>(LocalDatabaseMaintenance.name)
|
||||
?.resurrectChunks();
|
||||
})
|
||||
);
|
||||
new Setting(paneEl)
|
||||
.setName("Commit File Deletion")
|
||||
.setDesc("Completely delete all deleted documents from the local database.")
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText("Delete")
|
||||
.setWarning()
|
||||
.setDisabled(false)
|
||||
.onClick(async () => {
|
||||
await this.plugin
|
||||
.getAddOn<LocalDatabaseMaintenance>(LocalDatabaseMaintenance.name)
|
||||
?.commitFileDeletion();
|
||||
})
|
||||
);
|
||||
});
|
||||
void addPanel(paneEl, "Rebuilding Operations (Local)").then((paneEl) => {
|
||||
new Setting(paneEl)
|
||||
.setName("Fetch from remote")
|
||||
@@ -3076,33 +3124,6 @@ ${stringifyYaml(pluginConfig)}`;
|
||||
.addOnUpdate(onlyOnMinIO);
|
||||
});
|
||||
|
||||
void addPanel(paneEl, "Deprecated").then((paneEl) => {
|
||||
new Setting(paneEl)
|
||||
.setClass("sls-setting-obsolete")
|
||||
.setName("Run database cleanup")
|
||||
.setDesc(
|
||||
"Attempt to shrink the database by deleting unused chunks. This may not work consistently. Use the 'Rebuild everything' under Total Overhaul."
|
||||
)
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText("DryRun")
|
||||
.setDisabled(false)
|
||||
.onClick(async () => {
|
||||
await this.dryRunGC();
|
||||
})
|
||||
)
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText("Perform cleaning")
|
||||
.setDisabled(false)
|
||||
.setWarning()
|
||||
.onClick(async () => {
|
||||
this.closeSetting();
|
||||
await this.dbGC();
|
||||
})
|
||||
)
|
||||
.addOnUpdate(onlyOnCouchDB);
|
||||
});
|
||||
void addPanel(paneEl, "Reset").then((paneEl) => {
|
||||
new Setting(paneEl)
|
||||
.setName("Delete local database to reset or uninstall Self-hosted LiveSync")
|
||||
|
||||
152
updates.md
152
updates.md
@@ -14,6 +14,44 @@ Thank you, and I hope your troubles will be resolved!
|
||||
|
||||
---
|
||||
|
||||
## 0.24.9
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the issue which the filename is shown as `undefined`.
|
||||
- Fixed the issue where files transferred at short intervals were not reflected.
|
||||
|
||||
### Improved
|
||||
|
||||
- Add more translations: `ja-JP` (Japanese) by @kohki-shikata (Thank you so much)!
|
||||
|
||||
### Internal
|
||||
|
||||
- Some files have been prettified.
|
||||
|
||||
## 0.24.8
|
||||
|
||||
### Fixed
|
||||
|
||||
- Some parallel-processing tasks are now performed more safely.
|
||||
- Some error messages has been fixed.
|
||||
|
||||
### Improved
|
||||
|
||||
- Synchronisation is now more efficient and faster.
|
||||
- Saving chunks is a bit more robust.
|
||||
|
||||
### New Feature
|
||||
|
||||
- We can remove orphaned chunks again, now!
|
||||
- Without rebuilding the database!
|
||||
- Note: Please synchronise devices completely before removing orphaned chunks.
|
||||
- Note2: Deleted files are using chunks, if you want to remove them, please commit the deletion first. (`Commit File Deletion`)
|
||||
- Note3: If you lost some chunks, do not worry. They will be resurrected if not so much time has passed. Try `Resurrect deleted chunks`.
|
||||
- Note4: This feature is still beta. Please report any issues you encounter.
|
||||
- Note5: Please disable `On demand chunk fetching`, and enable `Compute revisions for each chunk` before using this feature.
|
||||
- These settings is going to be default in the future.
|
||||
|
||||
## 0.24.7
|
||||
|
||||
### Fixed (Security)
|
||||
@@ -53,118 +91,4 @@ Thank you, and I hope your troubles will be resolved!
|
||||
- The status line and the log summary are now displayed more smoothly and efficiently.
|
||||
- This improvement has also been applied to the logs displayed in the log pane.
|
||||
|
||||
## 0.24.4
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed so many inefficient and buggy modules inherited from the past.
|
||||
|
||||
### Improved
|
||||
|
||||
- Tasks are now executed in an efficient asynchronous library.
|
||||
- On-demand chunk fetching is now more efficient and keeps the interval between requests.
|
||||
- This will reduce the load on the server and the network.
|
||||
- And, safe for the Cloudant.
|
||||
|
||||
## 0.24.3
|
||||
|
||||
### Improved
|
||||
|
||||
- Many messages have been improved for better understanding as thanks to the fine works of @Volkor3-16! Thank you so much!
|
||||
- Documentations also have been updated to reflect the changes in the messages.
|
||||
- Now the style of In-Editor Status has been solid for some Android devices.
|
||||
|
||||
## 0.24.2
|
||||
|
||||
### Rewritten
|
||||
|
||||
- Hidden File Sync is now respects the file changes on the storage. Not simply comparing modified times.
|
||||
- This makes hidden file sync more robust and reliable.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `Scan hidden files before replication` is now configurable again.
|
||||
- Some unexpected errors are now handled more gracefully.
|
||||
- Meaningless event passing during boot sequence is now prevented.
|
||||
- Error handling for non-existing files has been fixed.
|
||||
- Hidden files will not be batched to avoid the potential error.
|
||||
- This behaviour had been causing the error in the previous versions in specific situations.
|
||||
- The log which checking automatic conflict resolution is now in verbose level.
|
||||
- Replication log (skipping non-targetting files) shows the correct information.
|
||||
- The dialogue that asking enabling optional feature during `Rebuild Everything` now prevents to show the `overwrite` option.
|
||||
- The rebuilding device is the first, meaningless.
|
||||
- Files with different modified time but identical content are no longer processed repeatedly.
|
||||
- Some unexpected errors which caused after terminating plug-in are now avoided.
|
||||
-
|
||||
|
||||
### Improved
|
||||
|
||||
- JSON files are now more transferred efficiently.
|
||||
- Now the JSON files are transferred in more fine chunks, which makes the transfer more efficient.
|
||||
|
||||
## 0.24.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Vault History can show the correct information of match-or-not for each file and database even if it is a binary file.
|
||||
- `Sync settings via markdown` is now hidden during the setup wizard.
|
||||
- Verify and Fix will ignore the hidden files if the hidden file sync is disabled.
|
||||
|
||||
#### New feature
|
||||
|
||||
- Now we can fetch the tweaks from the remote database while the setting dialogue and wizard are processing.
|
||||
|
||||
### Improved
|
||||
|
||||
- More things are moved to the modules.
|
||||
- Includes the Main codebase. Now `main.ts` is almost stub.
|
||||
- EventHub is now more robust and typesafe.
|
||||
|
||||
## 0.24.0
|
||||
|
||||
### Improved
|
||||
|
||||
- The welcome message is now more simple to encourage the use of the Setup-URI.
|
||||
- The secondary message is also simpler to guide users to Minimal Setup.
|
||||
- But Setup-URI will be recommended again, due to its importance.
|
||||
- These dialogues contain a link to the documentation which can be clicked.
|
||||
- The minimal setup is more minimal now. And, the setup is more user-friendly.
|
||||
- Now the Configuration of the remote database is checked more robustly, but we can ignore the warning and proceed with the setup.
|
||||
- Before we are asked about each feature, we are asked if we want to use optional features in the first place.
|
||||
- This is to prevent the user from being overwhelmed by the features.
|
||||
- And made it clear that it is not recommended for new users.
|
||||
- Many messages have been improved for better understanding.
|
||||
- Ridiculous messages have been (carefully) refined.
|
||||
- Dialogues are more informative and friendly.
|
||||
- A lot of messages have been mostly rewritten, leveraging Markdown.
|
||||
- Especially auto-closing dialogues are now explicitly labelled: `To stop the countdown, tap anywhere on the dialogue`.
|
||||
- Now if the is plugin configured to ignore some events, we will get a chance to fix it, in addition to the warning.
|
||||
- And why that has happened is also explained in the dialogue.
|
||||
- A note relating to device names has been added to Customisation Sync on the setting dialogue.
|
||||
- We can verify and resolve also the hidden files now.
|
||||
|
||||
### Fixed
|
||||
|
||||
- We can resolve the conflict of the JSON file correctly now.
|
||||
- Verifying files between the local database and storage is now working correctly.
|
||||
- While restarting the plug-in, the shown dialogues will be automatically closed to avoid unexpected behaviour.
|
||||
- Replicated documents that the local device has configured to ignore are now correctly ignored.
|
||||
- The chunks of the document on the local device during the first transfer will be created correctly.
|
||||
- And why we should create them is now explained in the dialogue.
|
||||
- If optional features have been enabled in the wizard, `Enable advanced features` will be toggled correctly.
|
||||
The hidden file sync is now working correctly. - Now the deletion of hidden files is correctly synchronised.
|
||||
- Customisation Sync is now working correctly together with hidden file sync.
|
||||
- No longer database suffix is stored in the setting sharing markdown.
|
||||
- A fair number of bugs have been fixed.
|
||||
|
||||
### Changed
|
||||
|
||||
- Some default settings have been changed for an easier new user experience.
|
||||
- Preventing the meaningless migration of the settings.
|
||||
|
||||
### Tiding
|
||||
|
||||
- The codebase has been reorganised into clearly defined modules.
|
||||
- Commented-out codes have been gradually removed.
|
||||
|
||||
Older notes are in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md).
|
||||
|
||||
2085
updates_old.md
2085
updates_old.md
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user