Fix cursor tracker
This commit is contained in:
parent
fb778b69d5
commit
9bb219734f
2 changed files with 90 additions and 10 deletions
|
|
@ -0,0 +1,67 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-type-assertion */
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { it } from "node:test";
|
||||||
|
import type { Database } from "../persistence/database";
|
||||||
|
import type { FileOperations } from "../file-operations/file-operations";
|
||||||
|
import type { WebSocketManager } from "../services/websocket-manager";
|
||||||
|
import type { ClientCursors } from "../services/types/ClientCursors";
|
||||||
|
import { DocumentUpToDateness } from "../types/document-up-to-dateness";
|
||||||
|
import { EventListeners } from "../utils/data-structures/event-listeners";
|
||||||
|
import { hash } from "../utils/hash";
|
||||||
|
import { CursorTracker } from "./cursor-tracker";
|
||||||
|
import { FileChangeNotifier } from "./file-change-notifier";
|
||||||
|
|
||||||
|
it("retains future cursors and publishes them when content catches up", async () => {
|
||||||
|
const content = new TextEncoder().encode("current");
|
||||||
|
const contentHash = await hash(content);
|
||||||
|
const database = {
|
||||||
|
getDocumentByDocumentId: () => ({
|
||||||
|
documentId: "document",
|
||||||
|
relativePath: "note.md",
|
||||||
|
isDeleted: false,
|
||||||
|
metadata: { parentVersionId: 2, hash: contentHash }
|
||||||
|
})
|
||||||
|
} as unknown as Database;
|
||||||
|
const websocket = {
|
||||||
|
onRemoteCursorsUpdateReceived: new EventListeners<
|
||||||
|
(cursors: ClientCursors[]) => Promise<void>
|
||||||
|
>(),
|
||||||
|
updateLocalCursors: () => {}
|
||||||
|
} as unknown as WebSocketManager;
|
||||||
|
const files = { read: async () => content } as unknown as FileOperations;
|
||||||
|
const notifier = new FileChangeNotifier();
|
||||||
|
const tracker = new CursorTracker(database, websocket, files, notifier);
|
||||||
|
const future = {
|
||||||
|
userName: "Other",
|
||||||
|
deviceId: "device",
|
||||||
|
documentsWithCursors: [
|
||||||
|
{
|
||||||
|
document_id: "document",
|
||||||
|
relative_path: "old-name.md",
|
||||||
|
vault_update_id: 2,
|
||||||
|
cursors: [{ start: 0, end: 1 }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
upToDateness: DocumentUpToDateness.Later
|
||||||
|
};
|
||||||
|
const internals = tracker as unknown as {
|
||||||
|
knownRemoteCursors: (typeof future)[];
|
||||||
|
getRelevantAndPruneKnownClientCursors: () => ClientCursors[];
|
||||||
|
};
|
||||||
|
internals.knownRemoteCursors = [future];
|
||||||
|
|
||||||
|
assert.deepEqual(internals.getRelevantAndPruneKnownClientCursors(), []);
|
||||||
|
assert.strictEqual(internals.knownRemoteCursors.length, 1);
|
||||||
|
|
||||||
|
let received: ClientCursors[] = [];
|
||||||
|
tracker.onRemoteCursorsUpdated.add((cursors) => {
|
||||||
|
received = cursors;
|
||||||
|
});
|
||||||
|
await notifier.onFileChanged.triggerAsync("note.md");
|
||||||
|
|
||||||
|
assert.strictEqual(received.length, 1);
|
||||||
|
assert.strictEqual(
|
||||||
|
received[0].documentsWithCursors[0].relative_path,
|
||||||
|
"note.md"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -72,21 +72,27 @@ export class CursorTracker {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.fileChangeNotifier.onFileChanged.add(async (relativePath) =>
|
this.fileChangeNotifier.onFileChanged.add(async (relativePath) => {
|
||||||
this.updateLock.withLock(async () => {
|
const cursors = await this.updateLock.withLock(async () => {
|
||||||
for (const clientCursor of this.knownRemoteCursors) {
|
for (const clientCursor of this.knownRemoteCursors) {
|
||||||
if (
|
if (
|
||||||
clientCursor.documentsWithCursors.some(
|
clientCursor.documentsWithCursors.some(
|
||||||
(document) =>
|
(document) =>
|
||||||
document.relative_path === relativePath
|
document.relative_path === relativePath ||
|
||||||
|
this.database.getDocumentByDocumentId(
|
||||||
|
document.document_id
|
||||||
|
)?.relativePath === relativePath
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
clientCursor.upToDateness =
|
clientCursor.upToDateness =
|
||||||
await this.getDocumentsUpToDateness(clientCursor);
|
await this.getDocumentsUpToDateness(clientCursor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
return this.getRelevantAndPruneKnownClientCursors();
|
||||||
);
|
});
|
||||||
|
|
||||||
|
this.onRemoteCursorsUpdated.trigger(cursors);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the local cursors for the given documents.
|
/// Update the local cursors for the given documents.
|
||||||
|
|
@ -166,13 +172,20 @@ export class CursorTracker {
|
||||||
const result: MaybeOutdatedClientCursors[] = [];
|
const result: MaybeOutdatedClientCursors[] = [];
|
||||||
const included = new Set<string>();
|
const included = new Set<string>();
|
||||||
|
|
||||||
const relevantCursors = [];
|
const retainedCursors = [];
|
||||||
|
const retainedFuture = new Set<string>();
|
||||||
for (const clientCursors of [...this.knownRemoteCursors].reverse()) {
|
for (const clientCursors of [...this.knownRemoteCursors].reverse()) {
|
||||||
if (included.has(clientCursors.deviceId)) {
|
if (clientCursors.upToDateness === DocumentUpToDateness.Later) {
|
||||||
|
// Retain the latest future position so it can become relevant
|
||||||
|
// after the corresponding content or manifest is applied.
|
||||||
|
if (!retainedFuture.has(clientCursors.deviceId)) {
|
||||||
|
retainedCursors.unshift(clientCursors);
|
||||||
|
retainedFuture.add(clientCursors.deviceId);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientCursors.upToDateness === DocumentUpToDateness.Later) {
|
if (included.has(clientCursors.deviceId)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,10 +205,10 @@ export class CursorTracker {
|
||||||
});
|
});
|
||||||
|
|
||||||
included.add(clientCursors.deviceId);
|
included.add(clientCursors.deviceId);
|
||||||
relevantCursors.unshift(clientCursors); // to reverse order back to normal
|
retainedCursors.unshift(clientCursors); // to reverse order back to normal
|
||||||
}
|
}
|
||||||
|
|
||||||
this.knownRemoteCursors = relevantCursors;
|
this.knownRemoteCursors = retainedCursors;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue