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.updateLock.withLock(async () => {
|
||||
this.fileChangeNotifier.onFileChanged.add(async (relativePath) => {
|
||||
const cursors = await this.updateLock.withLock(async () => {
|
||||
for (const clientCursor of this.knownRemoteCursors) {
|
||||
if (
|
||||
clientCursor.documentsWithCursors.some(
|
||||
(document) =>
|
||||
document.relative_path === relativePath
|
||||
document.relative_path === relativePath ||
|
||||
this.database.getDocumentByDocumentId(
|
||||
document.document_id
|
||||
)?.relativePath === relativePath
|
||||
)
|
||||
) {
|
||||
clientCursor.upToDateness =
|
||||
await this.getDocumentsUpToDateness(clientCursor);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
return this.getRelevantAndPruneKnownClientCursors();
|
||||
});
|
||||
|
||||
this.onRemoteCursorsUpdated.trigger(cursors);
|
||||
});
|
||||
}
|
||||
|
||||
/// Update the local cursors for the given documents.
|
||||
|
|
@ -166,13 +172,20 @@ export class CursorTracker {
|
|||
const result: MaybeOutdatedClientCursors[] = [];
|
||||
const included = new Set<string>();
|
||||
|
||||
const relevantCursors = [];
|
||||
const retainedCursors = [];
|
||||
const retainedFuture = new Set<string>();
|
||||
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;
|
||||
}
|
||||
|
||||
if (clientCursors.upToDateness === DocumentUpToDateness.Later) {
|
||||
if (included.has(clientCursors.deviceId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -192,10 +205,10 @@ export class CursorTracker {
|
|||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue