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 | |
|---|---|---|---|
|
|
58e328a591 | ||
|
|
1730c39d70 | ||
|
|
b42152db5e | ||
|
|
171cfc0a38 | ||
|
|
d2787bdb6a | ||
|
|
58845276e7 | ||
|
|
a2cc093a9e | ||
|
|
fec203a751 | ||
|
|
1a06837769 |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-livesync",
|
"id": "obsidian-livesync",
|
||||||
"name": "Self-hosted LiveSync",
|
"name": "Self-hosted LiveSync",
|
||||||
"version": "0.25.10",
|
"version": "0.25.14",
|
||||||
"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",
|
||||||
|
|||||||
936
package-lock.json
generated
936
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.25.10",
|
"version": "0.25.14",
|
||||||
"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",
|
||||||
@@ -92,10 +92,10 @@
|
|||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
"idb": "^8.0.3",
|
"idb": "^8.0.3",
|
||||||
"minimatch": "^10.0.1",
|
"minimatch": "^10.0.1",
|
||||||
"octagonal-wheels": "^0.1.37",
|
"octagonal-wheels": "^0.1.38",
|
||||||
"qrcode-generator": "^1.4.4",
|
"qrcode-generator": "^1.4.4",
|
||||||
"svelte-check": "^4.1.7",
|
"svelte-check": "^4.1.7",
|
||||||
"trystero": "^0.21.5",
|
"trystero": "^0.21.7",
|
||||||
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ export const OpenKeyValueDatabase = async (dbKey: string): Promise<KeyValueDatab
|
|||||||
}
|
}
|
||||||
const storeKey = dbKey;
|
const storeKey = dbKey;
|
||||||
const dbPromise = openDB(dbKey, 1, {
|
const dbPromise = openDB(dbKey, 1, {
|
||||||
upgrade(db) {
|
upgrade(db, _oldVersion, _newVersion, _transaction, _event) {
|
||||||
db.createObjectStore(storeKey);
|
return db.createObjectStore(storeKey);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const db = await dbPromise;
|
const db = await dbPromise;
|
||||||
|
|||||||
2
src/lib
2
src/lib
Submodule src/lib updated: e488bca9fc...f21001fcb2
@@ -582,6 +582,11 @@ export default class ObsidianLiveSyncPlugin
|
|||||||
$everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
$everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
||||||
return InterceptiveEvery;
|
return InterceptiveEvery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$$canReplicate(showMessage: boolean = false): Promise<boolean> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$replicate(showMessage: boolean = false): Promise<boolean | void> {
|
$$replicate(showMessage: boolean = false): Promise<boolean | void> {
|
||||||
throwShouldBeOverridden();
|
throwShouldBeOverridden();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
type EntryLeaf,
|
type EntryLeaf,
|
||||||
type LoadedEntry,
|
type LoadedEntry,
|
||||||
type MetaEntry,
|
type MetaEntry,
|
||||||
|
type RemoteType,
|
||||||
} from "../../lib/src/common/types";
|
} from "../../lib/src/common/types";
|
||||||
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
||||||
import {
|
import {
|
||||||
@@ -38,7 +39,8 @@ const KEY_REPLICATION_ON_EVENT = "replicationOnEvent";
|
|||||||
const REPLICATION_ON_EVENT_FORECASTED_TIME = 5000;
|
const REPLICATION_ON_EVENT_FORECASTED_TIME = 5000;
|
||||||
|
|
||||||
export class ModuleReplicator extends AbstractModule implements ICoreModule {
|
export class ModuleReplicator extends AbstractModule implements ICoreModule {
|
||||||
_replicatorType?: string;
|
_replicatorType?: RemoteType;
|
||||||
|
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
eventHub.onEvent(EVENT_FILE_SAVED, () => {
|
eventHub.onEvent(EVENT_FILE_SAVED, () => {
|
||||||
if (this.settings.syncOnSave && !this.core.$$isSuspended()) {
|
if (this.settings.syncOnSave && !this.core.$$isSuspended()) {
|
||||||
@@ -91,6 +93,10 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
|
|||||||
|
|
||||||
async $everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
async $everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
||||||
// Checking salt
|
// Checking salt
|
||||||
|
if (!this.core.managers.networkManager.isOnline) {
|
||||||
|
this._log("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// Showing message is false: that because be shown here. (And it is a fatal error, no way to hide it).
|
// Showing message is false: that because be shown here. (And it is a fatal error, no way to hide it).
|
||||||
if (!(await this.ensureReplicatorPBKDF2Salt(false))) {
|
if (!(await this.ensureReplicatorPBKDF2Salt(false))) {
|
||||||
Logger("Failed to initialise the encryption key, preventing replication.", LOG_LEVEL_NOTICE);
|
Logger("Failed to initialise the encryption key, preventing replication.", LOG_LEVEL_NOTICE);
|
||||||
@@ -167,25 +173,42 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async $$_replicate(showMessage: boolean = false): Promise<boolean | void> {
|
|
||||||
//--?
|
async $$canReplicate(showMessage: boolean = false): Promise<boolean> {
|
||||||
if (!this.core.$$isReady()) return;
|
if (!this.core.$$isReady()) {
|
||||||
|
Logger(`Not ready`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (isLockAcquired("cleanup")) {
|
if (isLockAcquired("cleanup")) {
|
||||||
Logger($msg("Replicator.Message.Cleaned"), LOG_LEVEL_NOTICE);
|
Logger($msg("Replicator.Message.Cleaned"), LOG_LEVEL_NOTICE);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.settings.versionUpFlash != "") {
|
if (this.settings.versionUpFlash != "") {
|
||||||
Logger($msg("Replicator.Message.VersionUpFlash"), LOG_LEVEL_NOTICE);
|
Logger($msg("Replicator.Message.VersionUpFlash"), LOG_LEVEL_NOTICE);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await this.core.$everyCommitPendingFileEvent())) {
|
if (!(await this.core.$everyCommitPendingFileEvent())) {
|
||||||
Logger($msg("Replicator.Message.Pending"), LOG_LEVEL_NOTICE);
|
Logger($msg("Replicator.Message.Pending"), LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.core.managers.networkManager.isOnline) {
|
||||||
|
this._log("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!(await this.core.$everyBeforeReplicate(showMessage))) {
|
if (!(await this.core.$everyBeforeReplicate(showMessage))) {
|
||||||
Logger($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE);
|
Logger($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async $$_replicate(showMessage: boolean = false): Promise<boolean | void> {
|
||||||
|
const checkBeforeReplicate = await this.$$canReplicate(showMessage);
|
||||||
|
if (!checkBeforeReplicate) return false;
|
||||||
|
|
||||||
//<-- Here could be an module.
|
//<-- Here could be an module.
|
||||||
const ret = await this.core.replicator.openReplication(this.settings, false, showMessage, false);
|
const ret = await this.core.replicator.openReplication(this.settings, false, showMessage, false);
|
||||||
|
|||||||
@@ -15,15 +15,22 @@ export class ModuleReplicatorCouchDB extends AbstractModule implements ICoreModu
|
|||||||
return Promise.resolve(new LiveSyncCouchDBReplicator(this.core));
|
return Promise.resolve(new LiveSyncCouchDBReplicator(this.core));
|
||||||
}
|
}
|
||||||
$everyAfterResumeProcess(): Promise<boolean> {
|
$everyAfterResumeProcess(): Promise<boolean> {
|
||||||
|
if (!this.core.$$isSuspended) return Promise.resolve(true);
|
||||||
|
if (!this.core.$$isReady) return Promise.resolve(true);
|
||||||
if (this.settings.remoteType != REMOTE_MINIO && this.settings.remoteType != REMOTE_P2P) {
|
if (this.settings.remoteType != REMOTE_MINIO && this.settings.remoteType != REMOTE_P2P) {
|
||||||
// If LiveSync enabled, open replication
|
const LiveSyncEnabled = this.settings.liveSync;
|
||||||
if (this.settings.liveSync) {
|
const continuous = LiveSyncEnabled;
|
||||||
fireAndForget(() => this.core.replicator.openReplication(this.settings, true, false, false));
|
const eventualOnStart = !LiveSyncEnabled && this.settings.syncOnStart;
|
||||||
}
|
|
||||||
// If sync on start enabled, open replication
|
// If enabled LiveSync or on start, open replication
|
||||||
if (!this.settings.liveSync && this.settings.syncOnStart) {
|
if (LiveSyncEnabled || eventualOnStart) {
|
||||||
// Possibly ok as if only share the result
|
// And note that we do not open the conflict detection dialogue directly during this process.
|
||||||
fireAndForget(() => this.core.replicator.openReplication(this.settings, false, false, false));
|
// This should be raised explicitly if needed.
|
||||||
|
fireAndForget(async () => {
|
||||||
|
const canReplicate = await this.core.$$canReplicate(false);
|
||||||
|
if (!canReplicate) return;
|
||||||
|
void this.core.replicator.openReplication(this.settings, continuous, false, false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import { $msg } from "src/lib/src/common/i18n.ts";
|
|||||||
|
|
||||||
export class ModuleCheckRemoteSize extends AbstractModule implements ICoreModule {
|
export class ModuleCheckRemoteSize extends AbstractModule implements ICoreModule {
|
||||||
async $allScanStat(): Promise<boolean> {
|
async $allScanStat(): Promise<boolean> {
|
||||||
|
if (this.core.managers.networkManager.isOnline === false) {
|
||||||
|
this._log("Network is offline, skipping remote size check.", LOG_LEVEL_INFO);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
this._log($msg("moduleCheckRemoteSize.logCheckingStorageSizes"), LOG_LEVEL_VERBOSE);
|
this._log($msg("moduleCheckRemoteSize.logCheckingStorageSizes"), LOG_LEVEL_VERBOSE);
|
||||||
if (this.settings.notifyThresholdOfRemoteStorageSize < 0) {
|
if (this.settings.notifyThresholdOfRemoteStorageSize < 0) {
|
||||||
const message = $msg("moduleCheckRemoteSize.msgSetDBCapacity");
|
const message = $msg("moduleCheckRemoteSize.msgSetDBCapacity");
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { type UXFileInfo } from "../../../lib/src/common/types.ts";
|
|||||||
function getFileLockKey(file: TFile | TFolder | string | UXFileInfo) {
|
function getFileLockKey(file: TFile | TFolder | string | UXFileInfo) {
|
||||||
return `fl:${typeof file == "string" ? file : file.path}`;
|
return `fl:${typeof file == "string" ? file : file.path}`;
|
||||||
}
|
}
|
||||||
function toArrayBuffer(arr: Uint8Array | ArrayBuffer | DataView): ArrayBufferLike {
|
function toArrayBuffer(arr: Uint8Array<ArrayBuffer> | ArrayBuffer | DataView<ArrayBuffer>): ArrayBuffer {
|
||||||
if (arr instanceof Uint8Array) {
|
if (arr instanceof Uint8Array) {
|
||||||
return arr.buffer;
|
return arr.buffer;
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,11 @@ export class SerializedFileAccess {
|
|||||||
return await processReadFile(file, () => this.app.vault.adapter.readBinary(path));
|
return await processReadFile(file, () => this.app.vault.adapter.readBinary(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
async adapterWrite(file: TFile | string, data: string | ArrayBuffer | Uint8Array, options?: DataWriteOptions) {
|
async adapterWrite(
|
||||||
|
file: TFile | string,
|
||||||
|
data: string | ArrayBuffer | Uint8Array<ArrayBuffer>,
|
||||||
|
options?: DataWriteOptions
|
||||||
|
) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
if (typeof data === "string") {
|
if (typeof data === "string") {
|
||||||
return await processWriteFile(file, () => this.app.vault.adapter.write(path, data, options));
|
return await processWriteFile(file, () => this.app.vault.adapter.write(path, data, options));
|
||||||
@@ -106,7 +110,7 @@ export class SerializedFileAccess {
|
|||||||
return await processReadFile(file, () => this.app.vault.readBinary(file));
|
return await processReadFile(file, () => this.app.vault.readBinary(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
async vaultModify(file: TFile, data: string | ArrayBuffer | Uint8Array, options?: DataWriteOptions) {
|
async vaultModify(file: TFile, data: string | ArrayBuffer | Uint8Array<ArrayBuffer>, options?: DataWriteOptions) {
|
||||||
if (typeof data === "string") {
|
if (typeof data === "string") {
|
||||||
return await processWriteFile(file, async () => {
|
return await processWriteFile(file, async () => {
|
||||||
const oldData = await this.app.vault.read(file);
|
const oldData = await this.app.vault.read(file);
|
||||||
@@ -131,7 +135,7 @@ export class SerializedFileAccess {
|
|||||||
}
|
}
|
||||||
async vaultCreate(
|
async vaultCreate(
|
||||||
path: string,
|
path: string,
|
||||||
data: string | ArrayBuffer | Uint8Array,
|
data: string | ArrayBuffer | Uint8Array<ArrayBuffer>,
|
||||||
options?: DataWriteOptions
|
options?: DataWriteOptions
|
||||||
): Promise<TFile> {
|
): Promise<TFile> {
|
||||||
if (typeof data === "string") {
|
if (typeof data === "string") {
|
||||||
|
|||||||
@@ -207,6 +207,9 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (file instanceof TFolder) continue;
|
if (file instanceof TFolder) continue;
|
||||||
|
// TODO: Confirm why only the TFolder skipping
|
||||||
|
// Possibly following line is needed...
|
||||||
|
// if (file?.isFolder) continue;
|
||||||
if (!(await this.core.$$isTargetFile(file.path))) continue;
|
if (!(await this.core.$$isTargetFile(file.path))) continue;
|
||||||
|
|
||||||
// Stop cache using to prevent the corruption;
|
// Stop cache using to prevent the corruption;
|
||||||
|
|||||||
@@ -17,6 +17,15 @@ import { isMetaEntry } from "../../lib/src/common/types.ts";
|
|||||||
import { isDeletedEntry, isDocContentSame, isLoadedEntry, readAsBlob } from "../../lib/src/common/utils.ts";
|
import { isDeletedEntry, isDocContentSame, isLoadedEntry, readAsBlob } from "../../lib/src/common/utils.ts";
|
||||||
import { countCompromisedChunks } from "../../lib/src/pouchdb/negotiation.ts";
|
import { countCompromisedChunks } from "../../lib/src/pouchdb/negotiation.ts";
|
||||||
|
|
||||||
|
type ErrorInfo = {
|
||||||
|
path: string;
|
||||||
|
recordedSize: number;
|
||||||
|
actualSize: number;
|
||||||
|
storageSize: number;
|
||||||
|
contentMatched: boolean;
|
||||||
|
isConflicted?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export class ModuleMigration extends AbstractModule implements ICoreModule {
|
export class ModuleMigration extends AbstractModule implements ICoreModule {
|
||||||
async migrateUsingDoctor(skipRebuild: boolean = false, activateReason = "updated", forceRescan = false) {
|
async migrateUsingDoctor(skipRebuild: boolean = false, activateReason = "updated", forceRescan = false) {
|
||||||
const { shouldRebuild, shouldRebuildLocal, isModified, settings } = await performDoctorConsultation(
|
const { shouldRebuild, shouldRebuildLocal, isModified, settings } = await performDoctorConsultation(
|
||||||
@@ -112,7 +121,8 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._log("Checking for incomplete documents...", LOG_LEVEL_NOTICE, "check-incomplete");
|
this._log("Checking for incomplete documents...", LOG_LEVEL_NOTICE, "check-incomplete");
|
||||||
const errorFiles = [];
|
|
||||||
|
const errorFiles = [] as ErrorInfo[];
|
||||||
for await (const metaDoc of this.localDatabase.findAllNormalDocs({ conflicts: true })) {
|
for await (const metaDoc of this.localDatabase.findAllNormalDocs({ conflicts: true })) {
|
||||||
const path = getPath(metaDoc);
|
const path = getPath(metaDoc);
|
||||||
|
|
||||||
@@ -133,17 +143,38 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
if (isDeletedEntry(doc)) {
|
if (isDeletedEntry(doc)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const storageFileContent = await this.core.storageAccess.readHiddenFileBinary(path);
|
const isConflicted = metaDoc?._conflicts && metaDoc._conflicts.length > 0;
|
||||||
|
|
||||||
|
let storageFileContent;
|
||||||
|
try {
|
||||||
|
storageFileContent = await this.core.storageAccess.readHiddenFileBinary(path);
|
||||||
|
} catch (e) {
|
||||||
|
Logger(`Failed to read file ${path}: Possibly unprocessed or missing`);
|
||||||
|
Logger(e, LOG_LEVEL_VERBOSE);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// const storageFileBlob = createBlob(storageFileContent);
|
// const storageFileBlob = createBlob(storageFileContent);
|
||||||
const sizeOnStorage = storageFileContent.byteLength;
|
const sizeOnStorage = storageFileContent.byteLength;
|
||||||
const recordedSize = doc.size;
|
const recordedSize = doc.size;
|
||||||
const docBlob = readAsBlob(doc);
|
const docBlob = readAsBlob(doc);
|
||||||
const actualSize = docBlob.size;
|
const actualSize = docBlob.size;
|
||||||
if (recordedSize !== actualSize || sizeOnStorage !== actualSize || sizeOnStorage !== recordedSize) {
|
if (
|
||||||
|
recordedSize !== actualSize ||
|
||||||
|
sizeOnStorage !== actualSize ||
|
||||||
|
sizeOnStorage !== recordedSize ||
|
||||||
|
isConflicted
|
||||||
|
) {
|
||||||
const contentMatched = await isDocContentSame(doc.data, storageFileContent);
|
const contentMatched = await isDocContentSame(doc.data, storageFileContent);
|
||||||
errorFiles.push({ path, recordedSize, actualSize, storageSize: sizeOnStorage, contentMatched });
|
errorFiles.push({
|
||||||
|
path,
|
||||||
|
recordedSize,
|
||||||
|
actualSize,
|
||||||
|
storageSize: sizeOnStorage,
|
||||||
|
contentMatched,
|
||||||
|
isConflicted,
|
||||||
|
});
|
||||||
Logger(
|
Logger(
|
||||||
`Size mismatch for ${path}: ${recordedSize} (DB Recorded) , ${actualSize} (DB Stored) , ${sizeOnStorage} (Storage Stored), ${contentMatched ? "Content Matched" : "Content Mismatched"}`
|
`Size mismatch for ${path}: ${recordedSize} (DB Recorded) , ${actualSize} (DB Stored) , ${sizeOnStorage} (Storage Stored), ${contentMatched ? "Content Matched" : "Content Mismatched"} ${isConflicted ? "Conflicted" : "Not Conflicted"}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,24 +198,23 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
// Probably restored by the user by resolving A or B on other device, We should overwrite the storage
|
// Probably restored by the user by resolving A or B on other device, We should overwrite the storage
|
||||||
// Also do not fix it automatically. It should be overwritten by replication.
|
// Also do not fix it automatically. It should be overwritten by replication.
|
||||||
const recoverable = errorFiles.filter((e) => {
|
const recoverable = errorFiles.filter((e) => {
|
||||||
return e.recordedSize === e.storageSize;
|
return e.recordedSize === e.storageSize && !e.isConflicted;
|
||||||
});
|
});
|
||||||
const unrecoverable = errorFiles.filter((e) => {
|
const unrecoverable = errorFiles.filter((e) => {
|
||||||
return e.recordedSize !== e.storageSize;
|
return e.recordedSize !== e.storageSize || e.isConflicted;
|
||||||
});
|
});
|
||||||
|
const fileInfo = (e: (typeof errorFiles)[0]) => {
|
||||||
|
return `${e.path} (M: ${e.recordedSize}, A: ${e.actualSize}, S: ${e.storageSize}) ${e.isConflicted ? "(Conflicted)" : ""}`;
|
||||||
|
};
|
||||||
const messageUnrecoverable =
|
const messageUnrecoverable =
|
||||||
unrecoverable.length > 0
|
unrecoverable.length > 0
|
||||||
? $msg("moduleMigration.fix0256.messageUnrecoverable", {
|
? $msg("moduleMigration.fix0256.messageUnrecoverable", {
|
||||||
filesNotRecoverable: unrecoverable
|
filesNotRecoverable: unrecoverable.map((e) => `- ${fileInfo(e)}`).join("\n"),
|
||||||
.map((e) => `- ${e.path} (M: ${e.recordedSize}, A: ${e.actualSize}, S: ${e.storageSize})`)
|
})
|
||||||
.join("\n"),
|
|
||||||
})
|
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const message = $msg("moduleMigration.fix0256.message", {
|
const message = $msg("moduleMigration.fix0256.message", {
|
||||||
files: recoverable
|
files: recoverable.map((e) => `- ${fileInfo(e)}`).join("\n"),
|
||||||
.map((e) => `- ${e.path} (M: ${e.recordedSize}, A: ${e.actualSize}, S: ${e.storageSize})`)
|
|
||||||
.join("\n"),
|
|
||||||
messageUnrecoverable,
|
messageUnrecoverable,
|
||||||
});
|
});
|
||||||
const CHECK_IT_LATER = $msg("moduleMigration.fix0256.buttons.checkItLater");
|
const CHECK_IT_LATER = $msg("moduleMigration.fix0256.buttons.checkItLater");
|
||||||
@@ -228,7 +258,9 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
// Check local database for compromised chunks
|
// Check local database for compromised chunks
|
||||||
const localCompromised = await countCompromisedChunks(this.localDatabase.localDatabase);
|
const localCompromised = await countCompromisedChunks(this.localDatabase.localDatabase);
|
||||||
const remote = this.core.$$getReplicator();
|
const remote = this.core.$$getReplicator();
|
||||||
const remoteCompromised = await remote.countCompromisedChunks();
|
const remoteCompromised = this.core.managers.networkManager.isOnline
|
||||||
|
? await remote.countCompromisedChunks()
|
||||||
|
: 0;
|
||||||
if (localCompromised === false) {
|
if (localCompromised === false) {
|
||||||
Logger(`Failed to count compromised chunks in local database`, LOG_LEVEL_NOTICE);
|
Logger(`Failed to count compromised chunks in local database`, LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -106,6 +106,9 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid";
|
if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid";
|
||||||
if (uri.toLowerCase() != uri) return "Remote URI and database name could not contain capital letters.";
|
if (uri.toLowerCase() != uri) return "Remote URI and database name could not contain capital letters.";
|
||||||
if (uri.indexOf(" ") !== -1) return "Remote URI and database name could not contain spaces.";
|
if (uri.indexOf(" ") !== -1) return "Remote URI and database name could not contain spaces.";
|
||||||
|
if (!this.core.managers.networkManager.isOnline) {
|
||||||
|
return "Network is offline";
|
||||||
|
}
|
||||||
// let authHeader = await this._authHeader.getAuthorizationHeader(auth);
|
// let authHeader = await this._authHeader.getAuthorizationHeader(auth);
|
||||||
|
|
||||||
const conf: PouchDB.HttpAdapter.HttpAdapterConfiguration = {
|
const conf: PouchDB.HttpAdapter.HttpAdapterConfiguration = {
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ export class ConflictResolveModal extends Modal {
|
|||||||
title: string = "Conflicting changes";
|
title: string = "Conflicting changes";
|
||||||
|
|
||||||
pluginPickMode: boolean = false;
|
pluginPickMode: boolean = false;
|
||||||
localName: string = "Use Base";
|
localName: string = "Base";
|
||||||
remoteName: string = "Use Conflicted";
|
remoteName: string = "Conflicted";
|
||||||
offEvent?: ReturnType<typeof eventHub.onEvent>;
|
offEvent?: ReturnType<typeof eventHub.onEvent>;
|
||||||
|
|
||||||
constructor(app: App, filename: string, diff: diff_result, pluginPickMode?: boolean, remoteName?: string) {
|
constructor(app: App, filename: string, diff: diff_result, pluginPickMode?: boolean, remoteName?: string) {
|
||||||
@@ -36,8 +36,8 @@ export class ConflictResolveModal extends Modal {
|
|||||||
this.pluginPickMode = pluginPickMode || false;
|
this.pluginPickMode = pluginPickMode || false;
|
||||||
if (this.pluginPickMode) {
|
if (this.pluginPickMode) {
|
||||||
this.title = "Pick a version";
|
this.title = "Pick a version";
|
||||||
this.remoteName = `Use ${remoteName || "Remote"}`;
|
this.remoteName = `${remoteName || "Remote"}`;
|
||||||
this.localName = "Use Local";
|
this.localName = "Local";
|
||||||
}
|
}
|
||||||
// Send cancel signal for the previous merge dialogue
|
// Send cancel signal for the previous merge dialogue
|
||||||
// if not there, simply be ignored.
|
// if not there, simply be ignored.
|
||||||
@@ -93,12 +93,13 @@ export class ConflictResolveModal extends Modal {
|
|||||||
const date2 =
|
const date2 =
|
||||||
new Date(this.result.right.mtime).toLocaleString() + (this.result.right.deleted ? " (Deleted)" : "");
|
new Date(this.result.right.mtime).toLocaleString() + (this.result.right.deleted ? " (Deleted)" : "");
|
||||||
div2.innerHTML = `
|
div2.innerHTML = `
|
||||||
<span class='deleted'>A:${date1}</span><br /><span class='added'>B:${date2}</span><br>
|
<span class='deleted'><span class='conflict-dev-name'>${this.localName}</span>: ${date1}</span><br>
|
||||||
|
<span class='added'><span class='conflict-dev-name'>${this.remoteName}</span>: ${date2}</span><br>
|
||||||
`;
|
`;
|
||||||
contentEl.createEl("button", { text: this.localName }, (e) =>
|
contentEl.createEl("button", { text: `Use ${this.localName}` }, (e) =>
|
||||||
e.addEventListener("click", () => this.sendResponse(this.result.right.rev))
|
e.addEventListener("click", () => this.sendResponse(this.result.right.rev))
|
||||||
).style.marginRight = "4px";
|
).style.marginRight = "4px";
|
||||||
contentEl.createEl("button", { text: this.remoteName }, (e) =>
|
contentEl.createEl("button", { text: `Use ${this.remoteName}` }, (e) =>
|
||||||
e.addEventListener("click", () => this.sendResponse(this.result.left.rev))
|
e.addEventListener("click", () => this.sendResponse(this.result.left.rev))
|
||||||
).style.marginRight = "4px";
|
).style.marginRight = "4px";
|
||||||
if (!this.pluginPickMode) {
|
if (!this.pluginPickMode) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { type IObsidianModule, AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
import { type IObsidianModule, AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
||||||
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events";
|
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
|
||||||
import {
|
import {
|
||||||
type BucketSyncSetting,
|
type BucketSyncSetting,
|
||||||
ChunkAlgorithmNames,
|
ChunkAlgorithmNames,
|
||||||
@@ -11,8 +11,8 @@ import {
|
|||||||
SALT_OF_PASSPHRASE,
|
SALT_OF_PASSPHRASE,
|
||||||
} from "../../lib/src/common/types";
|
} from "../../lib/src/common/types";
|
||||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_URGENT } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_NOTICE, LOG_LEVEL_URGENT } from "octagonal-wheels/common/logger";
|
||||||
import { $msg, setLang } from "../../lib/src/common/i18n";
|
import { $msg, setLang } from "../../lib/src/common/i18n.ts";
|
||||||
import { isCloudantURI } from "../../lib/src/pouchdb/utils_couchdb";
|
import { isCloudantURI } from "../../lib/src/pouchdb/utils_couchdb.ts";
|
||||||
import { getLanguage } from "obsidian";
|
import { getLanguage } from "obsidian";
|
||||||
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "../../lib/src/common/rosetta.ts";
|
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "../../lib/src/common/rosetta.ts";
|
||||||
import { decryptString, encryptString } from "@/lib/src/encryption/stringEncryption.ts";
|
import { decryptString, encryptString } from "@/lib/src/encryption/stringEncryption.ts";
|
||||||
@@ -23,8 +23,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
const obsidianLanguage = getLanguage();
|
const obsidianLanguage = getLanguage();
|
||||||
if (
|
if (
|
||||||
SUPPORTED_I18N_LANGS.indexOf(obsidianLanguage) !== -1 && // Check if the language is supported
|
SUPPORTED_I18N_LANGS.indexOf(obsidianLanguage) !== -1 && // Check if the language is supported
|
||||||
obsidianLanguage != this.settings.displayLanguage && // Check if the language is different from the current setting
|
obsidianLanguage != this.settings.displayLanguage // Check if the language is different from the current setting
|
||||||
this.settings.displayLanguage != ""
|
|
||||||
) {
|
) {
|
||||||
// Check if the current setting is not empty (Means migrated or installed).
|
// Check if the current setting is not empty (Means migrated or installed).
|
||||||
this.settings.displayLanguage = obsidianLanguage as I18N_LANGS;
|
this.settings.displayLanguage = obsidianLanguage as I18N_LANGS;
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
background-color: var(--text-muted);
|
background-color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.conflict-dev-name {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
.op-scrollable {
|
.op-scrollable {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
/* min-height: 280px; */
|
/* min-height: 280px; */
|
||||||
|
|||||||
74
updates.md
74
updates.md
@@ -1,3 +1,40 @@
|
|||||||
|
## 0.25.14
|
||||||
|
|
||||||
|
2nd September, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Opening IndexedDB handling has been ensured.
|
||||||
|
- Migration check of corrupted files detection has been fixed.
|
||||||
|
- Now informs us about conflicted files as non-recoverable, but noted so.
|
||||||
|
- No longer errors on not-found files.
|
||||||
|
|
||||||
|
## 0.25.13
|
||||||
|
|
||||||
|
1st September, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Conflict resolving dialogue now properly displays the changeset name instead of A or B (#691).
|
||||||
|
|
||||||
|
## 0.25.12
|
||||||
|
|
||||||
|
29th August, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed an issue with automatic synchronisation starting (#702).
|
||||||
|
|
||||||
|
## 0.25.11
|
||||||
|
|
||||||
|
28th August, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Automatic translation detection on the first launch now works correctly (#630).
|
||||||
|
- No errors are shown during synchronisations in offline (if not explicitly requested) (#699).
|
||||||
|
- Missing some checking during automatic-synchronisation now works correctly.
|
||||||
|
|
||||||
## 0.25.10
|
## 0.25.10
|
||||||
|
|
||||||
26th August, 2025
|
26th August, 2025
|
||||||
@@ -49,43 +86,6 @@
|
|||||||
|
|
||||||
- Type errors have been corrected.
|
- Type errors have been corrected.
|
||||||
|
|
||||||
## 0.25.7
|
|
||||||
|
|
||||||
15th August, 2025
|
|
||||||
|
|
||||||
**Since the release of 0.25.6, there are two large problem. Please update immediately.**
|
|
||||||
|
|
||||||
- We may have corrupted some documents during the migration process. **Please check your documents on the wizard.**
|
|
||||||
- Due to a chunk ID assignment issue, some data has not been encrypted. **Please rebuild the database using Rebuild Everything** if you have enabled E2EE.
|
|
||||||
|
|
||||||
**_So, If you have enabled E2EE, please perform `Rebuild everything`. If not, please check your documents on the wizard._**
|
|
||||||
|
|
||||||
In next version, insecure chunk detection will be implemented.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Off-loaded chunking have been fixed to ensure proper functionality (#693).
|
|
||||||
- Chunk document ID assignment has been fixed.
|
|
||||||
- Replication prevention message during version up detection has been improved (#686).
|
|
||||||
- `Keep A` and `Keep B` on Conflict resolving dialogue has been renamed to `Use Base` and `Use Conflicted` (#691).
|
|
||||||
|
|
||||||
### Improved
|
|
||||||
|
|
||||||
- Metadata and content-size unmatched documents are now detected and reported, prevented to be applied to the storage.
|
|
||||||
- This behaviour can be configured in `Patch` -> `Edge case addressing (Behaviour)` -> `Process files even if seems to be corrupted`
|
|
||||||
- Note: this toggle is for the direct-database-manipulation users.
|
|
||||||
|
|
||||||
### New Features
|
|
||||||
|
|
||||||
- `Scan for Broken files` has been implemented on `Hatch` -> `TroubleShooting`.
|
|
||||||
|
|
||||||
### Refactored
|
|
||||||
|
|
||||||
- Off-loaded processes have been refactored for the better maintainability.
|
|
||||||
- Files prefixed `bg.worker` are now work on the worker threads.
|
|
||||||
- Files prefixed `bgWorker.` are now also controls these worker threads. (I know what you want to say... I will rename them).
|
|
||||||
- Removed unused code.
|
|
||||||
|
|
||||||
## 0.25.0
|
## 0.25.0
|
||||||
|
|
||||||
19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
||||||
|
|||||||
@@ -11,6 +11,45 @@ As a result, this is the first time in a while that forward compatibility has be
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## 0.25.7
|
||||||
|
|
||||||
|
15th August, 2025
|
||||||
|
|
||||||
|
**Since the release of 0.25.6, there are two large problem. Please update immediately.**
|
||||||
|
|
||||||
|
- We may have corrupted some documents during the migration process. **Please check your documents on the wizard.**
|
||||||
|
- Due to a chunk ID assignment issue, some data has not been encrypted. **Please rebuild the database using Rebuild Everything** if you have enabled E2EE.
|
||||||
|
|
||||||
|
**_So, If you have enabled E2EE, please perform `Rebuild everything`. If not, please check your documents on the wizard._**
|
||||||
|
|
||||||
|
In next version, insecure chunk detection will be implemented.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Off-loaded chunking have been fixed to ensure proper functionality (#693).
|
||||||
|
- Chunk document ID assignment has been fixed.
|
||||||
|
- Replication prevention message during version up detection has been improved (#686).
|
||||||
|
- `Keep A` and `Keep B` on Conflict resolving dialogue has been renamed to `Use Base` and `Use Conflicted` (#691).
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
|
||||||
|
- Metadata and content-size unmatched documents are now detected and reported, prevented to be applied to the storage.
|
||||||
|
- This behaviour can be configured in `Patch` -> `Edge case addressing (Behaviour)` -> `Process files even if seems to be corrupted`
|
||||||
|
- Note: this toggle is for the direct-database-manipulation users.
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- `Scan for Broken files` has been implemented on `Hatch` -> `TroubleShooting`.
|
||||||
|
|
||||||
|
### Refactored
|
||||||
|
|
||||||
|
- Off-loaded processes have been refactored for the better maintainability.
|
||||||
|
- Files prefixed `bg.worker` are now work on the worker threads.
|
||||||
|
- Files prefixed `bgWorker.` are now also controls these worker threads. (I know what you want to say... I will rename them).
|
||||||
|
- Removed unused code.
|
||||||
|
|
||||||
|
|
||||||
## ~~0.25.5~~ 0.25.6
|
## ~~0.25.5~~ 0.25.6
|
||||||
|
|
||||||
(0.25.5 has been withdrawn due to a bug in the `Fetch chunks on demand` feature).
|
(0.25.5 has been withdrawn due to a bug in the `Fetch chunks on demand` feature).
|
||||||
|
|||||||
Reference in New Issue
Block a user