Fix lint
This commit is contained in:
parent
9139b4fa4d
commit
ca42f614e0
19 changed files with 301 additions and 226 deletions
|
|
@ -226,7 +226,7 @@ async function main(): Promise<void> {
|
||||||
);
|
);
|
||||||
|
|
||||||
fileWatcher.stop();
|
fileWatcher.stop();
|
||||||
await client.waitAndStop();
|
await client.destroy();
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import type { Stat, Vault, Workspace } from "obsidian";
|
import type { Stat, Vault, Workspace } from "obsidian";
|
||||||
import { MarkdownView, normalizePath } from "obsidian";
|
import { MarkdownView, normalizePath } from "obsidian";
|
||||||
import type {
|
import type { CursorPosition, TextWithCursors } from "sync-client";
|
||||||
CursorPosition,
|
|
||||||
TextWithCursors} from "sync-client";
|
|
||||||
import {
|
import {
|
||||||
utils,
|
utils,
|
||||||
type FileSystemOperations,
|
type FileSystemOperations,
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ export default class VaultLinkPlugin extends Plugin {
|
||||||
|
|
||||||
this.registerEditorEvents(client);
|
this.registerEditorEvents(client);
|
||||||
|
|
||||||
this.register(() => client.destroy());
|
this.register(async () => client.destroy());
|
||||||
await client.start();
|
await client.start();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -58,8 +58,16 @@ export default class VaultLinkPlugin extends Plugin {
|
||||||
new Notice(
|
new Notice(
|
||||||
"VaultLink has been enabled, check out the docs for tips on getting started!"
|
"VaultLink has been enabled, check out the docs for tips on getting started!"
|
||||||
);
|
);
|
||||||
this.activateView(LogsView.TYPE);
|
void this.activateView(HistoryView.TYPE).catch((e: unknown) => {
|
||||||
this.activateView(HistoryView.TYPE);
|
this.syncClient?.logger.error(
|
||||||
|
`Failed to open history view on enable: ${e}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
void this.activateView(LogsView.TYPE).catch((e: unknown) => {
|
||||||
|
this.syncClient?.logger.error(
|
||||||
|
`Failed to open logs view on enable: ${e}`
|
||||||
|
);
|
||||||
|
});
|
||||||
this.openSettings();
|
this.openSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,7 +177,9 @@ export default class VaultLinkPlugin extends Plugin {
|
||||||
client,
|
client,
|
||||||
this.app.workspace
|
this.app.workspace
|
||||||
);
|
);
|
||||||
this.register(() => cursorListener.dispose);
|
this.register(() => {
|
||||||
|
cursorListener.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
this.app.workspace.updateOptions();
|
this.app.workspace.updateOptions();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export class FileOperations {
|
||||||
): [RelativePath, RelativePath] {
|
): [RelativePath, RelativePath] {
|
||||||
const pathParts = path.split("/");
|
const pathParts = path.split("/");
|
||||||
const fileName = pathParts.pop();
|
const fileName = pathParts.pop();
|
||||||
if (!fileName || fileName === "") {
|
if (fileName == null || fileName === "") {
|
||||||
throw new Error(`Path '${path}' cannot be empty`);
|
throw new Error(`Path '${path}' cannot be empty`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,6 +166,10 @@ export class FileOperations {
|
||||||
await this.deletingEmptyParentDirectoriesOfDeletedFile(oldPath);
|
await this.deletingEmptyParentDirectoriesOfDeletedFile(oldPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public reset(): void {
|
||||||
|
this.fs.reset();
|
||||||
|
}
|
||||||
|
|
||||||
private async deletingEmptyParentDirectoriesOfDeletedFile(
|
private async deletingEmptyParentDirectoriesOfDeletedFile(
|
||||||
path: RelativePath
|
path: RelativePath
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|
@ -254,8 +258,4 @@ export class FileOperations {
|
||||||
|
|
||||||
return newName;
|
return newName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public reset(): void {
|
|
||||||
this.fs.reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,10 @@ export class SafeFileSystemOperations implements FileSystemOperations {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public reset(): void {
|
||||||
|
this.locks.reset();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decorate an operation to ensure that the file exists before running it.
|
* Decorate an operation to ensure that the file exists before running it.
|
||||||
* If the operation fails, it will check if the file still exists and throw
|
* If the operation fails, it will check if the file still exists and throw
|
||||||
|
|
@ -138,8 +142,4 @@ export class SafeFileSystemOperations implements FileSystemOperations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public reset(): void {
|
|
||||||
this.locks.reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -319,13 +319,6 @@ export class Database {
|
||||||
this.saveInTheBackground();
|
this.saveInTheBackground();
|
||||||
}
|
}
|
||||||
|
|
||||||
private saveInTheBackground(): void {
|
|
||||||
this.ensureConsistency();
|
|
||||||
void this.save().catch((error: unknown) => {
|
|
||||||
this.logger.error(`Error saving data: ${error}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async save(): Promise<void> {
|
public async save(): Promise<void> {
|
||||||
return this.saveData({
|
return this.saveData({
|
||||||
documents: this.resolvedDocuments.map(
|
documents: this.resolvedDocuments.map(
|
||||||
|
|
@ -362,4 +355,11 @@ export class Database {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private saveInTheBackground(): void {
|
||||||
|
this.ensureConsistency();
|
||||||
|
void this.save().catch((error: unknown) => {
|
||||||
|
this.logger.error(`Error saving data: ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type { Mock } from "node:test";
|
||||||
import { describe, it, mock, beforeEach, afterEach } from "node:test";
|
import { describe, it, mock, beforeEach, afterEach } from "node:test";
|
||||||
import assert from "node:assert";
|
import assert from "node:assert";
|
||||||
import { FetchController } from "./fetch-controller";
|
import { FetchController } from "./fetch-controller";
|
||||||
|
|
@ -6,7 +7,9 @@ import { SyncResetError } from "./sync-reset-error";
|
||||||
import { sleep } from "../utils/sleep";
|
import { sleep } from "../utils/sleep";
|
||||||
|
|
||||||
describe("FetchController", () => {
|
describe("FetchController", () => {
|
||||||
const createMockFetch = (shouldSleep: boolean) =>
|
const createMockFetch = (
|
||||||
|
shouldSleep: boolean
|
||||||
|
): Mock<() => Promise<Response>> =>
|
||||||
mock.fn(async () => {
|
mock.fn(async () => {
|
||||||
if (shouldSleep) {
|
if (shouldSleep) {
|
||||||
await sleep(30);
|
await sleep(30);
|
||||||
|
|
|
||||||
|
|
@ -24,16 +24,6 @@ export class FetchController {
|
||||||
createPromise<symbol>();
|
createPromise<symbol>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getUrlFromInput(input: RequestInfo | URL): string {
|
|
||||||
if (input instanceof URL) {
|
|
||||||
return input.href;
|
|
||||||
}
|
|
||||||
if (typeof input === "string") {
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
return input.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the fetch implementation can immediately send requests once outside of a reset.
|
* Whether the fetch implementation can immediately send requests once outside of a reset.
|
||||||
*/
|
*/
|
||||||
|
|
@ -58,6 +48,16 @@ export class FetchController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static getUrlFromInput(input: RequestInfo | URL): string {
|
||||||
|
if (input instanceof URL) {
|
||||||
|
return input.href;
|
||||||
|
}
|
||||||
|
if (typeof input === "string") {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
return input.url;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a reset, causing all ongoing and future fetches to be rejected
|
* Starts a reset, causing all ongoing and future fetches to be rejected
|
||||||
* with a SyncResetError until finishReset is called.
|
* with a SyncResetError until finishReset is called.
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ export class WebSocketManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async stop(): Promise<void> {
|
public async stop(): Promise<void> {
|
||||||
const [promise, resolve] = createPromise<void>();
|
const [promise, resolve] = createPromise();
|
||||||
this.resolveDisconnectingPromise = resolve;
|
this.resolveDisconnectingPromise = resolve;
|
||||||
|
|
||||||
this.isStopped = true;
|
this.isStopped = true;
|
||||||
|
|
@ -99,7 +99,7 @@ export class WebSocketManager {
|
||||||
await promise;
|
await promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
await awaitAll(this.outstandingPromises).then(() => {});
|
await awaitAll(this.outstandingPromises);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendHandshakeMessage(
|
public sendHandshakeMessage(
|
||||||
|
|
@ -164,10 +164,25 @@ export class WebSocketManager {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.webSocket.onmessage = async (event): Promise<void> => {
|
this.webSocket.onmessage = (event): void => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
try {
|
||||||
const message = JSON.parse(event.data) as WebSocketServerMessage;
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||||
return this.handleWebSocketMessage(message);
|
const message = JSON.parse(
|
||||||
|
event.data
|
||||||
|
) as WebSocketServerMessage;
|
||||||
|
|
||||||
|
void this.handleWebSocketMessage(message).catch(
|
||||||
|
(error: unknown) => {
|
||||||
|
this.logger.error(
|
||||||
|
`Error handling WebSocket message: ${String(error)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(
|
||||||
|
`Error parsing WebSocket message: ${String(error)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.webSocket.onclose = (event): void => {
|
this.webSocket.onclose = (event): void => {
|
||||||
|
|
@ -194,42 +209,58 @@ export class WebSocketManager {
|
||||||
message: WebSocketServerMessage
|
message: WebSocketServerMessage
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (message.type === "vaultUpdate") {
|
if (message.type === "vaultUpdate") {
|
||||||
this.outstandingPromises.push(
|
const promises = this.remoteVaultUpdateListeners.map(
|
||||||
...this.remoteVaultUpdateListeners.map(async (listener) => {
|
async (listener) => {
|
||||||
const promise = listener(message);
|
const trackedPromise = listener(message)
|
||||||
return promise.finally(() => {
|
.catch((error: unknown) => {
|
||||||
if (this.outstandingPromises.includes(promise)) {
|
this.logger.error(
|
||||||
this.outstandingPromises.splice(
|
`Error in vault update listener: ${String(error)}`
|
||||||
this.outstandingPromises.indexOf(promise),
|
|
||||||
1
|
|
||||||
);
|
);
|
||||||
}
|
})
|
||||||
});
|
.finally(() => {
|
||||||
})
|
const index =
|
||||||
|
this.outstandingPromises.indexOf(
|
||||||
|
trackedPromise
|
||||||
|
);
|
||||||
|
if (index !== -1) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
this.outstandingPromises.splice(index, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await trackedPromise;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
this.outstandingPromises.push(...promises);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
} else if (message.type === "cursorPositions") {
|
} else if (message.type === "cursorPositions") {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Received cursor positions for ${JSON.stringify(message.clients)}`
|
`Received cursor positions for ${JSON.stringify(message.clients)}`
|
||||||
);
|
);
|
||||||
this.outstandingPromises.push(
|
const filteredClients = message.clients.filter(
|
||||||
...this.remoteCursorsUpdateListeners.map(async (listener) => {
|
(client) => client.deviceId !== this.deviceId
|
||||||
const promise = listener(
|
|
||||||
message.clients.filter(
|
|
||||||
(client) => client.deviceId !== this.deviceId
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return promise.finally(() => {
|
|
||||||
if (this.outstandingPromises.includes(promise)) {
|
|
||||||
this.outstandingPromises.splice(
|
|
||||||
this.outstandingPromises.indexOf(promise),
|
|
||||||
1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
const promises = this.remoteCursorsUpdateListeners.map(
|
||||||
|
async (listener) => {
|
||||||
|
const trackedPromise = listener(filteredClients)
|
||||||
|
.catch((error: unknown) => {
|
||||||
|
this.logger.error(
|
||||||
|
`Error in cursor positions listener: ${String(error)}`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
const index =
|
||||||
|
this.outstandingPromises.indexOf(
|
||||||
|
trackedPromise
|
||||||
|
);
|
||||||
|
if (index !== -1) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
this.outstandingPromises.splice(index, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await trackedPromise;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.outstandingPromises.push(...promises);
|
||||||
} else {
|
} else {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`Received unknown message type: ${JSON.stringify(message)}`
|
`Received unknown message type: ${JSON.stringify(message)}`
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export class SyncClient {
|
||||||
private hasStartedOfflineSync = false;
|
private hasStartedOfflineSync = false;
|
||||||
private hasFinishedOfflineSync = false;
|
private hasFinishedOfflineSync = false;
|
||||||
private hasStarted = false;
|
private hasStarted = false;
|
||||||
private readonly hasBeenDestroyed = false;
|
private hasBeenDestroyed = false;
|
||||||
private unloadTelemetry?: () => void;
|
private unloadTelemetry?: () => void;
|
||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
|
|
@ -54,92 +54,6 @@ export class SyncClient {
|
||||||
>
|
>
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async start(): Promise<void> {
|
|
||||||
this.checkIfDestroyed();
|
|
||||||
|
|
||||||
if (this.hasStarted) {
|
|
||||||
throw new Error("SyncClient has already been started");
|
|
||||||
}
|
|
||||||
this.hasStarted = true;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!this.unloadTelemetry &&
|
|
||||||
this.settings.getSettings().enableTelemetry
|
|
||||||
) {
|
|
||||||
this.unloadTelemetry = setUpTelemetry();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.addOnMessageListener((log): void => {
|
|
||||||
if (log.level === LogLevel.ERROR && Sentry.isInitialized()) {
|
|
||||||
Sentry.captureMessage(log.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.settings.addOnSettingsChangeListener(
|
|
||||||
this.onSettingsChange.bind(this)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.settings.getSettings().isSyncEnabled) {
|
|
||||||
this.logger.info("Starting SyncClient");
|
|
||||||
await this.startSyncing();
|
|
||||||
this.logger.info("SyncClient has successfully started");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reload settings from disk overriding current in-memory settings.
|
|
||||||
* Missing values will be filled in from DEFAULT_SETTINGS rather than
|
|
||||||
* retaining current in-memory settings.
|
|
||||||
*/
|
|
||||||
public async reloadSettings(): Promise<void> {
|
|
||||||
this.checkIfDestroyed();
|
|
||||||
|
|
||||||
const state = (await this.persistence.load()) ?? {
|
|
||||||
settings: undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
const settings = {
|
|
||||||
...DEFAULT_SETTINGS,
|
|
||||||
...(state.settings ?? {})
|
|
||||||
};
|
|
||||||
|
|
||||||
this.setSettings(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async onSettingsChange(
|
|
||||||
newSettings: SyncSettings,
|
|
||||||
oldSettings: SyncSettings
|
|
||||||
): Promise<void> {
|
|
||||||
this.checkIfDestroyed();
|
|
||||||
|
|
||||||
if (
|
|
||||||
newSettings.vaultName !== oldSettings.vaultName ||
|
|
||||||
newSettings.remoteUri !== oldSettings.remoteUri
|
|
||||||
) {
|
|
||||||
await this.applyChangedConnectionSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newSettings.isSyncEnabled !== oldSettings.isSyncEnabled) {
|
|
||||||
if (newSettings.isSyncEnabled) {
|
|
||||||
await this.startSyncing();
|
|
||||||
} else {
|
|
||||||
await this.pause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newSettings.diffCacheSizeMB !== oldSettings.diffCacheSizeMB) {
|
|
||||||
this.contentCache.resize(newSettings.diffCacheSizeMB * 1024 * 1024);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newSettings.enableTelemetry !== oldSettings.enableTelemetry) {
|
|
||||||
if (newSettings.enableTelemetry) {
|
|
||||||
this.unloadTelemetry = setUpTelemetry();
|
|
||||||
} else {
|
|
||||||
this.unloadTelemetry?.();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public get documentCount(): number {
|
public get documentCount(): number {
|
||||||
this.checkIfDestroyed();
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
|
|
@ -151,7 +65,6 @@ export class SyncClient {
|
||||||
|
|
||||||
return this.webSocketManager.isWebSocketConnected;
|
return this.webSocketManager.isWebSocketConnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async create({
|
public static async create({
|
||||||
fs,
|
fs,
|
||||||
persistence,
|
persistence,
|
||||||
|
|
@ -292,6 +205,58 @@ export class SyncClient {
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async start(): Promise<void> {
|
||||||
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
|
if (this.hasStarted) {
|
||||||
|
throw new Error("SyncClient has already been started");
|
||||||
|
}
|
||||||
|
this.hasStarted = true;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!this.unloadTelemetry &&
|
||||||
|
this.settings.getSettings().enableTelemetry
|
||||||
|
) {
|
||||||
|
this.unloadTelemetry = setUpTelemetry();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.addOnMessageListener((log): void => {
|
||||||
|
if (log.level === LogLevel.ERROR && Sentry.isInitialized()) {
|
||||||
|
Sentry.captureMessage(log.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.settings.addOnSettingsChangeListener(
|
||||||
|
this.onSettingsChange.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.settings.getSettings().isSyncEnabled) {
|
||||||
|
this.logger.info("Starting SyncClient");
|
||||||
|
await this.startSyncing();
|
||||||
|
this.logger.info("SyncClient has successfully started");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload settings from disk overriding current in-memory settings.
|
||||||
|
* Missing values will be filled in from DEFAULT_SETTINGS rather than
|
||||||
|
* retaining current in-memory settings.
|
||||||
|
*/
|
||||||
|
public async reloadSettings(): Promise<void> {
|
||||||
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
|
const state = (await this.persistence.load()) ?? {
|
||||||
|
settings: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
const settings = {
|
||||||
|
...DEFAULT_SETTINGS,
|
||||||
|
...(state.settings ?? {})
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.setSettings(settings);
|
||||||
|
}
|
||||||
|
|
||||||
public async checkConnection(): Promise<NetworkConnectionStatus> {
|
public async checkConnection(): Promise<NetworkConnectionStatus> {
|
||||||
this.checkIfDestroyed();
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
|
|
@ -317,19 +282,6 @@ export class SyncClient {
|
||||||
this.history.addSyncHistoryUpdateListener(listener);
|
this.history.addSyncHistoryUpdateListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async startSyncing(): Promise<void> {
|
|
||||||
this.checkIfDestroyed();
|
|
||||||
|
|
||||||
if (!this.hasStartedOfflineSync) {
|
|
||||||
this.hasStartedOfflineSync = true;
|
|
||||||
await this.syncer.scheduleSyncForOfflineChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hasFinishedOfflineSync = true;
|
|
||||||
this.fetchController.finishReset();
|
|
||||||
this.webSocketManager.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait for the in-flight operations to finish, reset all tracking,
|
* Wait for the in-flight operations to finish, reset all tracking,
|
||||||
* and the local database but retain the settings.
|
* and the local database but retain the settings.
|
||||||
|
|
@ -367,6 +319,8 @@ export class SyncClient {
|
||||||
this.fetchController.startReset();
|
this.fetchController.startReset();
|
||||||
await this.pause();
|
await this.pause();
|
||||||
|
|
||||||
|
this.hasBeenDestroyed = true;
|
||||||
|
|
||||||
// clean-up memory early
|
// clean-up memory early
|
||||||
this.resetInMemoryState();
|
this.resetInMemoryState();
|
||||||
|
|
||||||
|
|
@ -375,24 +329,9 @@ export class SyncClient {
|
||||||
this.unloadTelemetry?.();
|
this.unloadTelemetry?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async pause(): Promise<void> {
|
public getSettings(): SyncSettings {
|
||||||
this.checkIfDestroyed();
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
this.fetchController.startReset();
|
|
||||||
await this.webSocketManager.stop();
|
|
||||||
await this.syncer.waitUntilFinished();
|
|
||||||
await this.database.save(); // flush all changes to disk
|
|
||||||
}
|
|
||||||
|
|
||||||
private resetInMemoryState(): void {
|
|
||||||
this.history.reset();
|
|
||||||
this.contentCache.reset();
|
|
||||||
this.logger.reset();
|
|
||||||
this.cursorTracker.reset();
|
|
||||||
this.syncer.reset();
|
|
||||||
this.fileOperations.reset();
|
|
||||||
}
|
|
||||||
public getSettings(): SyncSettings {
|
|
||||||
return this.settings.getSettings();
|
return this.settings.getSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -400,32 +339,44 @@ export class SyncClient {
|
||||||
key: T,
|
key: T,
|
||||||
value: SyncSettings[T]
|
value: SyncSettings[T]
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
await this.settings.setSetting(key, value);
|
await this.settings.setSetting(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setSettings(value: Partial<SyncSettings>): Promise<void> {
|
public async setSettings(value: Partial<SyncSettings>): Promise<void> {
|
||||||
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
await this.settings.setSettings(value);
|
await this.settings.setSettings(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addOnSettingsChangeListener(
|
public addOnSettingsChangeListener(
|
||||||
listener: (settings: SyncSettings, oldSettings: SyncSettings) => unknown
|
listener: (settings: SyncSettings, oldSettings: SyncSettings) => unknown
|
||||||
): void {
|
): void {
|
||||||
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
this.settings.addOnSettingsChangeListener(listener);
|
this.settings.addOnSettingsChangeListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addRemainingSyncOperationsListener(
|
public addRemainingSyncOperationsListener(
|
||||||
listener: (remainingOperations: number) => unknown
|
listener: (remainingOperations: number) => unknown
|
||||||
): void {
|
): void {
|
||||||
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
this.syncer.addRemainingOperationsListener(listener);
|
this.syncer.addRemainingOperationsListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addWebSocketStatusChangeListener(listener: () => unknown): void {
|
public addWebSocketStatusChangeListener(listener: () => unknown): void {
|
||||||
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
this.webSocketManager.addWebSocketStatusChangeListener(listener);
|
this.webSocketManager.addWebSocketStatusChangeListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async syncLocallyCreatedFile(
|
public async syncLocallyCreatedFile(
|
||||||
relativePath: RelativePath
|
relativePath: RelativePath
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
this.fileChangeNotifier.notifyOfFileChange(relativePath);
|
this.fileChangeNotifier.notifyOfFileChange(relativePath);
|
||||||
return this.syncer.syncLocallyCreatedFile(relativePath);
|
return this.syncer.syncLocallyCreatedFile(relativePath);
|
||||||
}
|
}
|
||||||
|
|
@ -433,6 +384,8 @@ export class SyncClient {
|
||||||
public async syncLocallyDeletedFile(
|
public async syncLocallyDeletedFile(
|
||||||
relativePath: RelativePath
|
relativePath: RelativePath
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
this.fileChangeNotifier.notifyOfFileChange(relativePath);
|
this.fileChangeNotifier.notifyOfFileChange(relativePath);
|
||||||
return this.syncer.syncLocallyDeletedFile(relativePath);
|
return this.syncer.syncLocallyDeletedFile(relativePath);
|
||||||
}
|
}
|
||||||
|
|
@ -444,6 +397,8 @@ export class SyncClient {
|
||||||
oldPath?: RelativePath;
|
oldPath?: RelativePath;
|
||||||
relativePath: RelativePath;
|
relativePath: RelativePath;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
this.fileChangeNotifier.notifyOfFileChange(relativePath);
|
this.fileChangeNotifier.notifyOfFileChange(relativePath);
|
||||||
return this.syncer.syncLocallyUpdatedFile({
|
return this.syncer.syncLocallyUpdatedFile({
|
||||||
oldPath,
|
oldPath,
|
||||||
|
|
@ -454,6 +409,8 @@ export class SyncClient {
|
||||||
public getDocumentSyncingStatus(
|
public getDocumentSyncingStatus(
|
||||||
relativePath: RelativePath
|
relativePath: RelativePath
|
||||||
): DocumentSyncStatus {
|
): DocumentSyncStatus {
|
||||||
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
if (!this.settings.getSettings().isSyncEnabled) {
|
if (!this.settings.getSettings().isSyncEnabled) {
|
||||||
return DocumentSyncStatus.SYNCING_IS_DISABLED;
|
return DocumentSyncStatus.SYNCING_IS_DISABLED;
|
||||||
}
|
}
|
||||||
|
|
@ -475,15 +432,82 @@ export class SyncClient {
|
||||||
public async updateLocalCursors(
|
public async updateLocalCursors(
|
||||||
documentToCursors: Record<RelativePath, CursorSpan[]>
|
documentToCursors: Record<RelativePath, CursorSpan[]>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
await this.cursorTracker.sendLocalCursorsToServer(documentToCursors);
|
await this.cursorTracker.sendLocalCursorsToServer(documentToCursors);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addRemoteCursorsUpdateListener(
|
public addRemoteCursorsUpdateListener(
|
||||||
listener: (cursors: MaybeOutdatedClientCursors[]) => unknown
|
listener: (cursors: MaybeOutdatedClientCursors[]) => unknown
|
||||||
): void {
|
): void {
|
||||||
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
this.cursorTracker.addRemoteCursorsUpdateListener(listener);
|
this.cursorTracker.addRemoteCursorsUpdateListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async startSyncing(): Promise<void> {
|
||||||
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
|
if (!this.hasStartedOfflineSync) {
|
||||||
|
this.hasStartedOfflineSync = true;
|
||||||
|
await this.syncer.scheduleSyncForOfflineChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hasFinishedOfflineSync = true;
|
||||||
|
this.fetchController.finishReset();
|
||||||
|
this.webSocketManager.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async pause(): Promise<void> {
|
||||||
|
this.fetchController.startReset();
|
||||||
|
await this.webSocketManager.stop();
|
||||||
|
await this.syncer.waitUntilFinished();
|
||||||
|
await this.database.save(); // flush all changes to disk
|
||||||
|
}
|
||||||
|
|
||||||
|
private resetInMemoryState(): void {
|
||||||
|
this.history.reset();
|
||||||
|
this.contentCache.reset();
|
||||||
|
this.logger.reset();
|
||||||
|
this.cursorTracker.reset();
|
||||||
|
this.syncer.reset();
|
||||||
|
this.fileOperations.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onSettingsChange(
|
||||||
|
newSettings: SyncSettings,
|
||||||
|
oldSettings: SyncSettings
|
||||||
|
): Promise<void> {
|
||||||
|
this.checkIfDestroyed();
|
||||||
|
|
||||||
|
if (
|
||||||
|
newSettings.vaultName !== oldSettings.vaultName ||
|
||||||
|
newSettings.remoteUri !== oldSettings.remoteUri
|
||||||
|
) {
|
||||||
|
await this.applyChangedConnectionSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSettings.isSyncEnabled !== oldSettings.isSyncEnabled) {
|
||||||
|
if (newSettings.isSyncEnabled) {
|
||||||
|
await this.startSyncing();
|
||||||
|
} else {
|
||||||
|
await this.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSettings.diffCacheSizeMB !== oldSettings.diffCacheSizeMB) {
|
||||||
|
this.contentCache.resize(newSettings.diffCacheSizeMB * 1024 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSettings.enableTelemetry !== oldSettings.enableTelemetry) {
|
||||||
|
if (newSettings.enableTelemetry) {
|
||||||
|
this.unloadTelemetry = setUpTelemetry();
|
||||||
|
} else {
|
||||||
|
this.unloadTelemetry?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private checkIfDestroyed(): void {
|
private checkIfDestroyed(): void {
|
||||||
if (this.hasBeenDestroyed) {
|
if (this.hasBeenDestroyed) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,13 @@ export class CursorTracker {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public reset(): void {
|
||||||
|
this.knownRemoteCursors = [];
|
||||||
|
this.lastLocalCursorState = [];
|
||||||
|
this.lastLocalCursorStateWithoutDirtyDocuments = [];
|
||||||
|
this.updateLock.reset();
|
||||||
|
}
|
||||||
|
|
||||||
private getRelevantAndPruneKnownClientCursors(): MaybeOutdatedClientCursors[] {
|
private getRelevantAndPruneKnownClientCursors(): MaybeOutdatedClientCursors[] {
|
||||||
const result: MaybeOutdatedClientCursors[] = [];
|
const result: MaybeOutdatedClientCursors[] = [];
|
||||||
const included = new Set<string>();
|
const included = new Set<string>();
|
||||||
|
|
@ -250,11 +257,4 @@ export class CursorTracker {
|
||||||
? DocumentUpToDateness.UpToDate
|
? DocumentUpToDateness.UpToDate
|
||||||
: DocumentUpToDateness.Prior;
|
: DocumentUpToDateness.Prior;
|
||||||
}
|
}
|
||||||
|
|
||||||
public reset(): void {
|
|
||||||
this.knownRemoteCursors = [];
|
|
||||||
this.lastLocalCursorState = [];
|
|
||||||
this.lastLocalCursorStateWithoutDirtyDocuments = [];
|
|
||||||
this.updateLock.reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -299,6 +299,13 @@ export class Syncer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public reset(): void {
|
||||||
|
this._isFirstSyncComplete = false;
|
||||||
|
this.syncQueue.clear();
|
||||||
|
this.remoteDocumentsLock.reset();
|
||||||
|
this.runningScheduleSyncForOfflineChanges = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
private sendHandshakeMessage(): void {
|
private sendHandshakeMessage(): void {
|
||||||
const message: WebSocketClientMessage = {
|
const message: WebSocketClientMessage = {
|
||||||
type: "handshake",
|
type: "handshake",
|
||||||
|
|
@ -513,11 +520,4 @@ export class Syncer {
|
||||||
|
|
||||||
this.database.setHasInitialSyncCompleted(true);
|
this.database.setHasInitialSyncCompleted(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public reset(): void {
|
|
||||||
this._isFirstSyncComplete = false;
|
|
||||||
this.syncQueue.clear();
|
|
||||||
this.remoteDocumentsLock.reset();
|
|
||||||
this.runningScheduleSyncForOfflineChanges = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ type ResolvedTuple<T extends readonly unknown[]> = {
|
||||||
export const awaitAll = async <T extends readonly unknown[]>(
|
export const awaitAll = async <T extends readonly unknown[]>(
|
||||||
promises: PromiseTuple<T>
|
promises: PromiseTuple<T>
|
||||||
): Promise<ResolvedTuple<T>> => {
|
): Promise<ResolvedTuple<T>> => {
|
||||||
|
// eslint-disable-next-line no-restricted-properties
|
||||||
const result = await Promise.allSettled(promises);
|
const result = await Promise.allSettled(promises);
|
||||||
for (const res of result) {
|
for (const res of result) {
|
||||||
if (res.status === "rejected") {
|
if (res.status === "rejected") {
|
||||||
|
|
@ -16,7 +17,9 @@ export const awaitAll = async <T extends readonly unknown[]>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||||
return result.map(
|
return result.map(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||||
(res) => (res as PromiseFulfilledResult<unknown>).value
|
(res) => (res as PromiseFulfilledResult<unknown>).value
|
||||||
) as ResolvedTuple<T>;
|
) as ResolvedTuple<T>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import assert from "node:assert";
|
||||||
import { Logger } from "../../tracing/logger";
|
import { Logger } from "../../tracing/logger";
|
||||||
import type { RelativePath } from "../../persistence/database";
|
import type { RelativePath } from "../../persistence/database";
|
||||||
import { Locks } from "./locks";
|
import { Locks } from "./locks";
|
||||||
|
import { awaitAll } from "../await-all";
|
||||||
|
import { sleep } from "../sleep";
|
||||||
|
|
||||||
describe("withLock", () => {
|
describe("withLock", () => {
|
||||||
const testPath: RelativePath = "test/document/path";
|
const testPath: RelativePath = "test/document/path";
|
||||||
|
|
@ -31,7 +33,7 @@ describe("withLock", () => {
|
||||||
let executionCount = 0;
|
let executionCount = 0;
|
||||||
const result = await locks.withLock(testPath, async () => {
|
const result = await locks.withLock(testPath, async () => {
|
||||||
executionCount++;
|
executionCount++;
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
await sleep(10);
|
||||||
return "async-success";
|
return "async-success";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -56,19 +58,19 @@ describe("withLock", () => {
|
||||||
// Start two concurrent operations with keys in different orders
|
// Start two concurrent operations with keys in different orders
|
||||||
const promise1 = locks.withLock([testPath2, testPath], async () => {
|
const promise1 = locks.withLock([testPath2, testPath], async () => {
|
||||||
executionOrder.push("operation1-start");
|
executionOrder.push("operation1-start");
|
||||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
await sleep(50);
|
||||||
executionOrder.push("operation1-end");
|
executionOrder.push("operation1-end");
|
||||||
return "result1";
|
return "result1";
|
||||||
});
|
});
|
||||||
|
|
||||||
const promise2 = locks.withLock([testPath, testPath2], async () => {
|
const promise2 = locks.withLock([testPath, testPath2], async () => {
|
||||||
executionOrder.push("operation2-start");
|
executionOrder.push("operation2-start");
|
||||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
await sleep(50);
|
||||||
executionOrder.push("operation2-end");
|
executionOrder.push("operation2-end");
|
||||||
return "result2";
|
return "result2";
|
||||||
});
|
});
|
||||||
|
|
||||||
const [result1, result2] = await Promise.all([promise1, promise2]);
|
const [result1, result2] = await awaitAll([promise1, promise2]);
|
||||||
|
|
||||||
assert.strictEqual(result1, "result1");
|
assert.strictEqual(result1, "result1");
|
||||||
assert.strictEqual(result2, "result2");
|
assert.strictEqual(result2, "result2");
|
||||||
|
|
@ -86,19 +88,19 @@ describe("withLock", () => {
|
||||||
|
|
||||||
const promise1 = locks.withLock(testPath, async () => {
|
const promise1 = locks.withLock(testPath, async () => {
|
||||||
executionOrder.push("operation1-start");
|
executionOrder.push("operation1-start");
|
||||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
await sleep(50);
|
||||||
executionOrder.push("operation1-end");
|
executionOrder.push("operation1-end");
|
||||||
return "result1";
|
return "result1";
|
||||||
});
|
});
|
||||||
|
|
||||||
const promise2 = locks.withLock(testPath, async () => {
|
const promise2 = locks.withLock(testPath, async () => {
|
||||||
executionOrder.push("operation2-start");
|
executionOrder.push("operation2-start");
|
||||||
await new Promise((resolve) => setTimeout(resolve, 30));
|
await sleep(30);
|
||||||
executionOrder.push("operation2-end");
|
executionOrder.push("operation2-end");
|
||||||
return "result2";
|
return "result2";
|
||||||
});
|
});
|
||||||
|
|
||||||
const [result1, result2] = await Promise.all([promise1, promise2]);
|
const [result1, result2] = await awaitAll([promise1, promise2]);
|
||||||
|
|
||||||
assert.strictEqual(result1, "result1");
|
assert.strictEqual(result1, "result1");
|
||||||
assert.strictEqual(result2, "result2");
|
assert.strictEqual(result2, "result2");
|
||||||
|
|
@ -115,19 +117,20 @@ describe("withLock", () => {
|
||||||
|
|
||||||
const promise1 = locks.withLock(testPath, async () => {
|
const promise1 = locks.withLock(testPath, async () => {
|
||||||
executionOrder.push("operation1-start");
|
executionOrder.push("operation1-start");
|
||||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
await sleep(50);
|
||||||
|
|
||||||
executionOrder.push("operation1-end");
|
executionOrder.push("operation1-end");
|
||||||
return "result1";
|
return "result1";
|
||||||
});
|
});
|
||||||
|
|
||||||
const promise2 = locks.withLock(testPath2, async () => {
|
const promise2 = locks.withLock(testPath2, async () => {
|
||||||
executionOrder.push("operation2-start");
|
executionOrder.push("operation2-start");
|
||||||
await new Promise((resolve) => setTimeout(resolve, 30));
|
await sleep(30);
|
||||||
executionOrder.push("operation2-end");
|
executionOrder.push("operation2-end");
|
||||||
return "result2";
|
return "result2";
|
||||||
});
|
});
|
||||||
|
|
||||||
const [result1, result2] = await Promise.all([promise1, promise2]);
|
const [result1, result2] = await awaitAll([promise1, promise2]);
|
||||||
|
|
||||||
assert.strictEqual(result1, "result1");
|
assert.strictEqual(result1, "result1");
|
||||||
assert.strictEqual(result2, "result2");
|
assert.strictEqual(result2, "result2");
|
||||||
|
|
@ -159,7 +162,8 @@ describe("withLock", () => {
|
||||||
|
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
locks.withLock(testPath, async () => {
|
locks.withLock(testPath, async () => {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
await sleep(10);
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}),
|
}),
|
||||||
{ message: "async test error" }
|
{ message: "async test error" }
|
||||||
|
|
@ -184,30 +188,30 @@ describe("withLock", () => {
|
||||||
// Start first operation that holds the lock
|
// Start first operation that holds the lock
|
||||||
const firstPromise = locks.withLock(testPath, async () => {
|
const firstPromise = locks.withLock(testPath, async () => {
|
||||||
executionOrder.push("first-start");
|
executionOrder.push("first-start");
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await sleep(100);
|
||||||
executionOrder.push("first-end");
|
executionOrder.push("first-end");
|
||||||
return "first";
|
return "first";
|
||||||
});
|
});
|
||||||
|
|
||||||
// Small delay to ensure first operation starts
|
// Small delay to ensure first operation starts
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
await sleep(10);
|
||||||
|
|
||||||
// Queue second and third operations
|
// Queue second and third operations
|
||||||
const secondPromise = locks.withLock(testPath, async () => {
|
const secondPromise = locks.withLock(testPath, async () => {
|
||||||
executionOrder.push("second-start");
|
executionOrder.push("second-start");
|
||||||
await new Promise((resolve) => setTimeout(resolve, 30));
|
await sleep(50);
|
||||||
executionOrder.push("second-end");
|
executionOrder.push("second-end");
|
||||||
return "second";
|
return "second";
|
||||||
});
|
});
|
||||||
|
|
||||||
const thirdPromise = locks.withLock(testPath, async () => {
|
const thirdPromise = locks.withLock(testPath, async () => {
|
||||||
executionOrder.push("third-start");
|
executionOrder.push("third-start");
|
||||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
await sleep(20);
|
||||||
executionOrder.push("third-end");
|
executionOrder.push("third-end");
|
||||||
return "third";
|
return "third";
|
||||||
});
|
});
|
||||||
|
|
||||||
const [first, second, third] = await Promise.all([
|
const [first, second, third] = await awaitAll([
|
||||||
firstPromise,
|
firstPromise,
|
||||||
secondPromise,
|
secondPromise,
|
||||||
thirdPromise
|
thirdPromise
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,11 @@ export class Locks<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public reset(): void {
|
||||||
|
this.locked.clear();
|
||||||
|
this.waiters.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to acquire a lock immediately without waiting.
|
* Attempts to acquire a lock immediately without waiting.
|
||||||
* Must call `unlock()` if successful.
|
* Must call `unlock()` if successful.
|
||||||
|
|
@ -131,11 +136,6 @@ export class Locks<T> {
|
||||||
this.locked.delete(key);
|
this.locked.delete(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public reset(): void {
|
|
||||||
this.locked.clear();
|
|
||||||
this.waiters.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Lock {
|
export class Lock {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ export function slowWebSocketFactory(
|
||||||
jitterScaleInSeconds: number,
|
jitterScaleInSeconds: number,
|
||||||
logger: Logger
|
logger: Logger
|
||||||
): typeof WebSocket {
|
): typeof WebSocket {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||||
return class FlakyWebSocket extends WebSocket {
|
return class FlakyWebSocket extends WebSocket {
|
||||||
private static readonly RECEIVE_KEY = "websocket-receive";
|
private static readonly RECEIVE_KEY = "websocket-receive";
|
||||||
private static readonly SEND_KEY = "websocket-send";
|
private static readonly SEND_KEY = "websocket-send";
|
||||||
|
|
|
||||||
|
|
@ -127,8 +127,9 @@ export class MockAgent extends MockClient {
|
||||||
|
|
||||||
public async finish(): Promise<void> {
|
public async finish(): Promise<void> {
|
||||||
await this.client.setSetting("isSyncEnabled", true);
|
await this.client.setSetting("isSyncEnabled", true);
|
||||||
await Promise.allSettled(this.pendingActions);
|
// eslint-disable-next-line no-restricted-properties
|
||||||
await this.client.waitAndStop();
|
await Promise.all(this.pendingActions);
|
||||||
|
await this.client.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
public assertFileSystemsAreConsistent(otherAgent: MockAgent): void {
|
public assertFileSystemsAreConsistent(otherAgent: MockAgent): void {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import type { StoredDatabase ,
|
import type { StoredDatabase, TextWithCursors } from "sync-client";
|
||||||
TextWithCursors
|
|
||||||
} from "sync-client";
|
|
||||||
import { assert } from "../utils/assert";
|
import { assert } from "../utils/assert";
|
||||||
import {
|
import {
|
||||||
type RelativePath,
|
type RelativePath,
|
||||||
|
|
|
||||||
|
|
@ -53,10 +53,12 @@ async function runTest({
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line no-restricted-properties
|
||||||
await Promise.all(clients.map(async (client) => client.init()));
|
await Promise.all(clients.map(async (client) => client.init()));
|
||||||
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
for (let i = 0; i < iterations; i++) {
|
||||||
console.info(`Iteration ${i + 1}/${iterations}`);
|
console.info(`Iteration ${i + 1}/${iterations}`);
|
||||||
|
// eslint-disable-next-line no-restricted-properties
|
||||||
await Promise.all(clients.map(async (client) => client.act()));
|
await Promise.all(clients.map(async (client) => client.act()));
|
||||||
await sleep(100);
|
await sleep(100);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue