add min covered

This commit is contained in:
Andras Schmelczer 2026-04-25 14:24:39 +01:00
parent addaa1699f
commit 321b503379
5 changed files with 206 additions and 16 deletions

View file

@ -410,7 +410,7 @@ describe("SyncEventQueue", () => {
assert.strictEqual(queue.syncedDocumentCount, 2);
assert.strictEqual(queue.getSettledDocumentByPath("a.md")?.documentId, "A");
assert.strictEqual(queue.getSettledDocumentByPath("b.md")?.documentId, "B");
assert.strictEqual(queue.lastSeenUpdateId, 5);
assert.strictEqual(queue._lastSeenUpdateId, 5);
});
it("trackedPaths combines documents and pending events", () => {

View file

@ -14,6 +14,7 @@ import {
type SyncEvent,
type VaultUpdateId,
} from "./types";
import { MinCovered } from "../utils/data-structures/min-covered";
export class SyncEventQueue {
@ -39,9 +40,7 @@ export class SyncEventQueue {
// file creations for paths matching any of these patterns will be ignored
private ignorePatterns: RegExp[];
public readonly lastSeenUpdateId: VaultUpdateId;
public _lastSeenUpdateId: MinCovered;
public constructor(
private readonly settings: Settings,
@ -71,9 +70,17 @@ export class SyncEventQueue {
this.documents.set(relativePath, record);
}
}
this.lastSeenUpdateId = initialState.lastSeenUpdateId ?? -1;
this._lastSeenUpdateId = new MinCovered(initialState.lastSeenUpdateId ?? 0);
this.logger.debug(`Loaded ${this.documents.size} documents and lastSeenUpdateId=${this.lastSeenUpdateId} from storage`);
this.logger.debug(`Loaded ${this.documents.size} documents and lastSeenUpdateId=${this._lastSeenUpdateId} from storage`);
}
public get lastSeenUpdateId(): VaultUpdateId {
return this._lastSeenUpdateId.min;
}
public set lastSeenUpdateId(id: VaultUpdateId) {
this._lastSeenUpdateId.add(id);
}
public get pendingUpdateCount(): number {
@ -214,7 +221,7 @@ export class SyncEventQueue {
...record
})
),
lastSeenUpdateId: this.lastSeenUpdateId
lastSeenUpdateId: this._lastSeenUpdateId
});
}
@ -261,7 +268,7 @@ export class SyncEventQueue {
public async clearAllState(): Promise<void> {
this.clearPending();
this.documents.clear();
this.lastSeenUpdateId = -1;
this._lastSeenUpdateId.reset()
await this.save();
}

View file

@ -378,6 +378,7 @@ export class Syncer {
createEvent: event
});
this.history.addHistoryEntry({
status: SyncStatus.SUCCESS,
details: { type: SyncType.CREATE, relativePath: effectivePath },
@ -403,6 +404,8 @@ export class Syncer {
});
await this.queue.removeDocument(doc.path);
this.queue.lastSeenUpdateId = response.vaultUpdateId;
this.history.addHistoryEntry({
status: SyncStatus.SUCCESS,
@ -443,6 +446,9 @@ export class Syncer {
}
);
this.queue.lastSeenUpdateId = response.vaultUpdateId;
await this.handleMaybeMergingResponse({
path: diskPath,
response,
@ -530,6 +536,8 @@ export class Syncer {
remoteHash
});
}
this.queue.lastSeenUpdateId = response.vaultUpdateId;
}
@ -549,7 +557,13 @@ export class Syncer {
return this.processRemoteDelete(documentWithPath.path, remoteVersion);
}
if (documentWithPath?.record.parentVersionId ?? 0 >= remoteVersion.vaultUpdateId) {
this.queue.lastSeenUpdateId = remoteVersion.vaultUpdateId;
this.logger.debug(
`Document ${remoteVersion.relativePath} is already up-to-date or has newer local changes; skipping remote update`
);
return;
}
if (documentWithPath !== undefined) {
// must be the update to an existing doc
@ -570,6 +584,9 @@ export class Syncer {
await this.operations.delete(path);
await this.queue.removeDocument(path);
this.queue.lastSeenUpdateId = remoteVersion.vaultUpdateId;
this.history.addHistoryEntry({
status: SyncStatus.SUCCESS,
details: {
@ -599,12 +616,30 @@ export class Syncer {
const currentContent = await this.operations.read(path);
const remoteContent = await this.syncService.getDocumentVersionContent({ documentId: remoteVersion.documentId, vaultUpdateId: remoteVersion.vaultUpdateId });
this.operations.write(path, currentContent, remoteContent);
// todo: update last seen id
await this.updateCache(
remoteVersion.vaultUpdateId,
remoteContent,
path
);
this.queue.lastSeenUpdateId = remoteVersion.vaultUpdateId;
} // else we don't need to update the content, a subsequent local update will do that
this.syncRemotelyUpdatedFile({ // schedule it so that the lastSeenUpdateId remains consistent
document:
remoteVersion
})
// wait for a local edit to do the actual updating here, so we can't even update the lastSeenUpdateId here
this.ensurePath(path, remoteVersion.relativePath);
this.queue.setDocument(remoteVersion.relativePath, {
...record,
remoteRelativePath: remoteVersion.relativePath
});
this.history.addHistoryEntry({
status: SyncStatus.SUCCESS,
details: {
@ -642,6 +677,8 @@ export class Syncer {
remoteRelativePath: remoteVersion.relativePath
});
this.queue.lastSeenUpdateId = remoteVersion.vaultUpdateId;
this.history.addHistoryEntry({
status: SyncStatus.SUCCESS,
details: {
@ -682,12 +719,6 @@ export class Syncer {
if (canMergeText) {
const currentContent = await this.operations.read(pendingCreateEvent.path);
this.queue.resolveCreate(pendingCreateEvent, {
documentId: remoteVersion.documentId,
parentVersionId: remoteVersion.vaultUpdateId,
remoteHash,
remoteRelativePath: path
});
const merged = reconcile("", new TextDecoder().decode(currentContent), new TextDecoder().decode(remoteContent)).text;
@ -698,6 +729,14 @@ export class Syncer {
path
);
await this.queue.resolveCreate(pendingCreateEvent, {
documentId: remoteVersion.documentId,
parentVersionId: remoteVersion.vaultUpdateId,
remoteHash,
remoteRelativePath: path
});
this.queue.lastSeenUpdateId = remoteVersion.vaultUpdateId;
this.history.addHistoryEntry({
status: SyncStatus.SUCCESS,
details: {
@ -719,6 +758,8 @@ export class Syncer {
remoteHash,
remoteRelativePath: path
});
this.queue.lastSeenUpdateId = remoteVersion.vaultUpdateId;
this.history.addHistoryEntry({
status: SyncStatus.SUCCESS,
details: {