mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2025-03-03 15:32:25 +02:00
Fixed:
- 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:
parent
e05f8771b9
commit
86b9695bc2
@ -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.`);
|
||||||
|
@ -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;
|
||||||
|
2
src/lib
2
src/lib
@ -1 +1 @@
|
|||||||
Subproject commit 46256dba3a12fae405d1d86f12cdec8ab93005f6
|
Subproject commit 1c8ed1d974e4851a53bdd9bab6ee43f44f5a99c7
|
@ -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;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user