1
0
mirror of https://github.com/vrtmrz/obsidian-livesync.git synced 2025-03-03 15:32:25 +02:00
- No longer deleted hidden files were ignored.
- The document history dialogue is now able to process the deleted revisions.
- Deletion of a hidden file is now surely performed even if the file is already conflicted.
This commit is contained in:
vorotamoroz 2024-02-20 09:32:48 +00:00
parent e05f8771b9
commit 86b9695bc2
4 changed files with 54 additions and 27 deletions

View File

@ -1,5 +1,5 @@
import { normalizePath, type PluginManifest } from "./deps"; import { normalizePath, type PluginManifest } from "./deps";
import { type EntryDoc, type LoadedEntry, type InternalFileEntry, type FilePathWithPrefix, type FilePath, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE, MODE_PAUSED, type SavingEntry } from "./lib/src/types"; import { type EntryDoc, type LoadedEntry, type InternalFileEntry, type FilePathWithPrefix, type FilePath, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE, MODE_PAUSED, type SavingEntry, type DocumentID } from "./lib/src/types";
import { type InternalFileInfo, ICHeader, ICHeaderEnd } from "./types"; import { type InternalFileInfo, ICHeader, ICHeaderEnd } from "./types";
import { createBinaryBlob, isDocContentSame, sendSignal } from "./lib/src/utils"; import { createBinaryBlob, isDocContentSame, sendSignal } from "./lib/src/utils";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
@ -102,11 +102,13 @@ export class HiddenFileSync extends LiveSyncCommands {
} }
const stat = await this.vaultAccess.adapterStat(path); const stat = await this.vaultAccess.adapterStat(path);
// sometimes folder is coming. // sometimes folder is coming.
if (stat && stat.type != "file") if (stat != null && stat.type != "file") {
return; return;
const storageMTime = ~~((stat && stat.mtime || 0) / 1000); }
const mtime = stat == null ? 0 : stat?.mtime ?? 0;
const storageMTime = ~~((mtime) / 1000);
const key = `${path}-${storageMTime}`; const key = `${path}-${storageMTime}`;
if (this.recentProcessedInternalFiles.contains(key)) { if (mtime != 0 && this.recentProcessedInternalFiles.contains(key)) {
//If recently processed, it may caused by self. //If recently processed, it may caused by self.
return; return;
} }
@ -150,6 +152,27 @@ export class HiddenFileSync extends LiveSyncCommands {
await this.conflictResolutionProcessor.startPipeline().waitForPipeline(); await this.conflictResolutionProcessor.startPipeline().waitForPipeline();
} }
async resolveByNewerEntry(id: DocumentID, path: FilePathWithPrefix, currentDoc: EntryDoc, currentRev: string, conflictedRev: string) {
const conflictedDoc = await this.localDatabase.getRaw(id, { rev: conflictedRev });
// determine which revision should been deleted.
// simply check modified time
const mtimeCurrent = ("mtime" in currentDoc && currentDoc.mtime) || 0;
const mtimeConflicted = ("mtime" in conflictedDoc && conflictedDoc.mtime) || 0;
// Logger(`Revisions:${new Date(mtimeA).toLocaleString} and ${new Date(mtimeB).toLocaleString}`);
// console.log(`mtime:${mtimeA} - ${mtimeB}`);
const delRev = mtimeCurrent < mtimeConflicted ? currentRev : conflictedRev;
// delete older one.
await this.localDatabase.removeRevision(id, delRev);
Logger(`Older one has been deleted:${path}`);
const cc = await this.localDatabase.getRaw(id, { conflicts: true });
if (cc._conflicts.length == 0) {
await this.extractInternalFileFromDatabase(stripAllPrefixes(path))
} else {
this.conflictResolutionProcessor.enqueue(path);
}
// check the file again
}
conflictResolutionProcessor = new QueueProcessor(async (paths: FilePathWithPrefix[]) => { conflictResolutionProcessor = new QueueProcessor(async (paths: FilePathWithPrefix[]) => {
const path = paths[0]; const path = paths[0];
sendSignal(`cancel-internal-conflict:${path}`); sendSignal(`cancel-internal-conflict:${path}`);
@ -185,27 +208,16 @@ export class HiddenFileSync extends LiveSyncCommands {
const stat = await this.vaultAccess.adapterStat(filename); const stat = await this.vaultAccess.adapterStat(filename);
await this.storeInternalFileToDatabase({ path: filename, ...stat }); await this.storeInternalFileToDatabase({ path: filename, ...stat });
await this.extractInternalFileFromDatabase(filename); await this.extractInternalFileFromDatabase(filename);
await this.localDatabase.removeRaw(id, revB); await this.localDatabase.removeRevision(id, revB);
this.conflictResolutionProcessor.enqueue(path); this.conflictResolutionProcessor.enqueue(path);
return; return;
} else { } else {
Logger(`Object merge is not applicable.`, LOG_LEVEL_VERBOSE); Logger(`Object merge is not applicable.`, LOG_LEVEL_VERBOSE);
} }
return [{ path, revA, revB }]; return [{ path, revA, revB, id, doc }];
} }
const revBDoc = await this.localDatabase.getRaw(id, { rev: revB }); // When not JSON file, resolve conflicts by choosing a newer one.
// determine which revision should been deleted. await this.resolveByNewerEntry(id, path, doc, revA, revB);
// simply check modified time
const mtimeA = ("mtime" in doc && doc.mtime) || 0;
const mtimeB = ("mtime" in revBDoc && revBDoc.mtime) || 0;
// Logger(`Revisions:${new Date(mtimeA).toLocaleString} and ${new Date(mtimeB).toLocaleString}`);
// console.log(`mtime:${mtimeA} - ${mtimeB}`);
const delRev = mtimeA < mtimeB ? revA : revB;
// delete older one.
await this.localDatabase.removeRaw(id, delRev);
Logger(`Older one has been deleted:${path}`);
// check the file again
this.conflictResolutionProcessor.enqueue(path);
return; return;
} catch (ex) { } catch (ex) {
Logger(`Failed to resolve conflict (Hidden): ${path}`); Logger(`Failed to resolve conflict (Hidden): ${path}`);
@ -215,7 +227,7 @@ export class HiddenFileSync extends LiveSyncCommands {
}, { }, {
suspended: false, batchSize: 1, concurrentLimit: 5, delay: 10, keepResultUntilDownstreamConnected: true, yieldThreshold: 10, suspended: false, batchSize: 1, concurrentLimit: 5, delay: 10, keepResultUntilDownstreamConnected: true, yieldThreshold: 10,
pipeTo: new QueueProcessor(async (results) => { pipeTo: new QueueProcessor(async (results) => {
const { path, revA, revB } = results[0] const { id, doc, path, revA, revB } = results[0];
const docAMerge = await this.localDatabase.getDBEntry(path, { rev: revA }); const docAMerge = await this.localDatabase.getDBEntry(path, { rev: revA });
const docBMerge = await this.localDatabase.getDBEntry(path, { rev: revB }); const docBMerge = await this.localDatabase.getDBEntry(path, { rev: revB });
if (docAMerge != false && docBMerge != false) { if (docAMerge != false && docBMerge != false) {
@ -224,6 +236,9 @@ export class HiddenFileSync extends LiveSyncCommands {
this.conflictResolutionProcessor.enqueue(path); this.conflictResolutionProcessor.enqueue(path);
} }
return; return;
} else {
// If either revision could not read, force resolving by the newer one.
await this.resolveByNewerEntry(id, path, doc, revA, revB);
} }
}, { suspended: false, batchSize: 1, concurrentLimit: 1, delay: 10, keepResultUntilDownstreamConnected: false, yieldThreshold: 10 }) }, { suspended: false, batchSize: 1, concurrentLimit: 1, delay: 10, keepResultUntilDownstreamConnected: false, yieldThreshold: 10 })
}) })
@ -361,7 +376,11 @@ export class HiddenFileSync extends LiveSyncCommands {
} }
} }
} else if (xFileOnStorage && !xFileOnDatabase) { } else if (xFileOnStorage && !xFileOnDatabase) {
await this.storeInternalFileToDatabase(xFileOnStorage); if (direction == "push" || direction == "pushForce" || direction == "safe") {
await this.storeInternalFileToDatabase(xFileOnStorage);
} else {
await this.extractInternalFileFromDatabase(xFileOnStorage.path);
}
} else { } else {
throw new Error("Invalid state on hidden file sync"); throw new Error("Invalid state on hidden file sync");
// Something corrupted? // Something corrupted?
@ -513,6 +532,14 @@ export class HiddenFileSync extends LiveSyncCommands {
type: "newnote", type: "newnote",
}; };
} else { } else {
// Remove all conflicted before deleting.
const conflicts = await this.localDatabase.getRaw(old._id, { conflicts: true });
if ("_conflicts" in conflicts) {
for (const conflictRev of conflicts._conflicts) {
await this.localDatabase.removeRevision(old._id, conflictRev);
Logger(`STORAGE -x> DB:${filename}: (hidden) conflict removed ${old._rev} => ${conflictRev}`, LOG_LEVEL_VERBOSE);
}
}
if (old.deleted) { if (old.deleted) {
Logger(`STORAGE -x> DB:${filename}: (hidden) already deleted`); Logger(`STORAGE -x> DB:${filename}: (hidden) already deleted`);
return; return;
@ -546,8 +573,7 @@ export class HiddenFileSync extends LiveSyncCommands {
return await serialized("file-" + prefixedFileName, async () => { return await serialized("file-" + prefixedFileName, async () => {
try { try {
// Check conflicted status // Check conflicted status
//TODO option const fileOnDB = await this.localDatabase.getDBEntry(prefixedFileName, { conflicts: true }, false, true, true);
const fileOnDB = await this.localDatabase.getDBEntry(prefixedFileName, { conflicts: true }, false, true);
if (fileOnDB === false) if (fileOnDB === false)
throw new Error(`File not found on database.:${filename}`); throw new Error(`File not found on database.:${filename}`);
// Prevent overwrite for Prevent overwriting while some conflicted revision exists. // Prevent overwrite for Prevent overwriting while some conflicted revision exists.
@ -555,7 +581,7 @@ export class HiddenFileSync extends LiveSyncCommands {
Logger(`Hidden file ${filename} has conflicted revisions, to keep in safe, writing to storage has been prevented`, LOG_LEVEL_INFO); Logger(`Hidden file ${filename} has conflicted revisions, to keep in safe, writing to storage has been prevented`, LOG_LEVEL_INFO);
return; return;
} }
const deleted = "deleted" in fileOnDB ? fileOnDB.deleted : false; const deleted = fileOnDB.deleted || fileOnDB._deleted || false;
if (deleted) { if (deleted) {
if (!isExists) { if (!isExists) {
Logger(`STORAGE <x- DB:${filename}: deleted (hidden) Deleted on DB, but the file is already not found on storage.`); Logger(`STORAGE <x- DB:${filename}: deleted (hidden) Deleted on DB, but the file is already not found on storage.`);

View File

@ -21,6 +21,7 @@ function isComparableTextDecode(path: string) {
return ["json"].includes(ext) return ["json"].includes(ext)
} }
function readDocument(w: LoadedEntry) { function readDocument(w: LoadedEntry) {
if (w.data.length == 0) return "";
if (isImage(w.path)) { if (isImage(w.path)) {
return new Uint8Array(decodeBinary(w.data)); return new Uint8Array(decodeBinary(w.data));
} }
@ -71,7 +72,7 @@ export class DocumentHistoryModal extends Modal {
} }
const db = this.plugin.localDatabase; const db = this.plugin.localDatabase;
try { try {
const w = await db.localDatabase.get(this.id, { revs_info: true }); const w = await db.getRaw(this.id, { revs_info: true });
this.revs_info = w._revs_info?.filter((e) => e?.status == "available") ?? []; this.revs_info = w._revs_info?.filter((e) => e?.status == "available") ?? [];
this.range.max = `${Math.max(this.revs_info.length - 1, 0)}`; this.range.max = `${Math.max(this.revs_info.length - 1, 0)}`;
this.range.value = this.range.max; this.range.value = this.range.max;

@ -1 +1 @@
Subproject commit 46256dba3a12fae405d1d86f12cdec8ab93005f6 Subproject commit 1c8ed1d974e4851a53bdd9bab6ee43f44f5a99c7

View File

@ -2864,7 +2864,7 @@ Or if you are sure know what had been happened, we can unlock the database from
const mtimeB = ("mtime" in revBDoc && revBDoc.mtime) || 0; const mtimeB = ("mtime" in revBDoc && revBDoc.mtime) || 0;
const delRev = mtimeA < mtimeB ? revA : revB; const delRev = mtimeA < mtimeB ? revA : revB;
// delete older one. // delete older one.
await this.localDatabase.removeRaw(id, delRev); await this.localDatabase.removeRevision(id, delRev);
Logger(`Older one has been deleted:${this.getPath(doc)}`); Logger(`Older one has been deleted:${this.getPath(doc)}`);
return true; return true;
} }