Improve settings (#168)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
e75298c4f1
commit
c08feba0ad
19 changed files with 302 additions and 128 deletions
|
|
@ -9,6 +9,8 @@ export interface SyncSettings {
|
|||
maxFileSizeMB: number;
|
||||
ignorePatterns: string[];
|
||||
webSocketRetryIntervalMs: number;
|
||||
diffCacheSizeMB: number;
|
||||
enableTelemetry: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: SyncSettings = {
|
||||
|
|
@ -19,7 +21,9 @@ export const DEFAULT_SETTINGS: SyncSettings = {
|
|||
isSyncEnabled: false,
|
||||
maxFileSizeMB: 10,
|
||||
ignorePatterns: [],
|
||||
webSocketRetryIntervalMs: 3500
|
||||
webSocketRetryIntervalMs: 3500,
|
||||
diffCacheSizeMB: 4,
|
||||
enableTelemetry: false
|
||||
};
|
||||
|
||||
export class Settings {
|
||||
|
|
|
|||
|
|
@ -22,11 +22,13 @@ import type { CursorSpan } from "./services/types/CursorSpan";
|
|||
import type { MaybeOutdatedClientCursors } from "./types/maybe-outdated-client-cursors";
|
||||
import { FileChangeNotifier } from "./sync-operations/file-change-notifier";
|
||||
import { FixedSizeDocumentCache } from "./utils/fix-sized-cache";
|
||||
import { setUpTelemetry } from "./utils/set-up-telemetry";
|
||||
|
||||
export class SyncClient {
|
||||
private static readonly MINIMUM_SAVE_INTERVAL_MS = 1000;
|
||||
private hasStartedOfflineSync = false;
|
||||
private hasFinishedOfflineSync = false;
|
||||
private unloadTelemetry?: () => void;
|
||||
|
||||
private constructor(
|
||||
private readonly history: SyncHistory,
|
||||
|
|
@ -38,8 +40,13 @@ export class SyncClient {
|
|||
private readonly _logger: Logger,
|
||||
private readonly connectionStatus: ConnectionStatus,
|
||||
private readonly cursorTracker: CursorTracker,
|
||||
private readonly fileChangeNotifier: FileChangeNotifier
|
||||
private readonly fileChangeNotifier: FileChangeNotifier,
|
||||
private readonly contentCache: FixedSizeDocumentCache
|
||||
) {
|
||||
if (settings.getSettings().enableTelemetry) {
|
||||
this.unloadTelemetry = setUpTelemetry();
|
||||
}
|
||||
|
||||
this.settings.addOnSettingsChangeListener(
|
||||
async (newSettings, oldSettings) => {
|
||||
if (newSettings.vaultName !== oldSettings.vaultName) {
|
||||
|
|
@ -53,6 +60,24 @@ export class SyncClient {
|
|||
this.stop();
|
||||
}
|
||||
}
|
||||
|
||||
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?.();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -65,6 +90,10 @@ export class SyncClient {
|
|||
return this.database.length;
|
||||
}
|
||||
|
||||
public get isWebSocketConnected(): boolean {
|
||||
return this.webSocketManager.isWebSocketConnected;
|
||||
}
|
||||
|
||||
public static async create({
|
||||
fs,
|
||||
persistence,
|
||||
|
|
@ -152,8 +181,7 @@ export class SyncClient {
|
|||
settings,
|
||||
syncService,
|
||||
fileOperations,
|
||||
unrestrictedSyncer,
|
||||
contentCache
|
||||
unrestrictedSyncer
|
||||
);
|
||||
|
||||
const webSocketManager = new WebSocketManager(
|
||||
|
|
@ -182,7 +210,8 @@ export class SyncClient {
|
|||
logger,
|
||||
connectionStatus,
|
||||
cursorTracker,
|
||||
fileChangeNotifier
|
||||
fileChangeNotifier,
|
||||
contentCache
|
||||
);
|
||||
|
||||
logger.info("SyncClient initialised");
|
||||
|
|
@ -235,6 +264,7 @@ export class SyncClient {
|
|||
public async reset(): Promise<void> {
|
||||
this.stop();
|
||||
this.connectionStatus.startReset();
|
||||
this.contentCache.clear();
|
||||
await this.syncer.reset();
|
||||
this.history.reset();
|
||||
this.database.reset();
|
||||
|
|
|
|||
|
|
@ -34,8 +34,7 @@ export class Syncer {
|
|||
settings: Settings,
|
||||
private readonly syncService: SyncService,
|
||||
private readonly operations: FileOperations,
|
||||
private readonly internalSyncer: UnrestrictedSyncer,
|
||||
private readonly contentCache: FixedSizeDocumentCache
|
||||
private readonly internalSyncer: UnrestrictedSyncer
|
||||
) {
|
||||
this.syncQueue = new PQueue({
|
||||
concurrency: settings.getSettings().syncConcurrency
|
||||
|
|
@ -252,7 +251,6 @@ export class Syncer {
|
|||
|
||||
public async reset(): Promise<void> {
|
||||
await this.waitUntilFinished();
|
||||
this.contentCache.clear();
|
||||
}
|
||||
|
||||
public async syncRemotelyUpdatedFile(
|
||||
|
|
|
|||
|
|
@ -236,4 +236,40 @@ describe("fixedSizeDocumentCache", () => {
|
|||
assert.equal(cache.get(2), doc2);
|
||||
assert.equal(cache.get(3), doc3);
|
||||
});
|
||||
|
||||
it("resizeToLargerSizeNoEviction", async () => {
|
||||
const cache = new FixedSizeDocumentCache(4);
|
||||
const doc1 = new Uint8Array([1, 2]);
|
||||
const doc2 = new Uint8Array([3, 4]);
|
||||
|
||||
cache.put(1, doc1);
|
||||
cache.put(2, doc2);
|
||||
|
||||
cache.resize(10);
|
||||
|
||||
assert.equal(cache.get(1), doc1);
|
||||
assert.equal(cache.get(2), doc2);
|
||||
});
|
||||
|
||||
it("resizeCausesMultipleEvictions", async () => {
|
||||
const cache = new FixedSizeDocumentCache(10);
|
||||
const doc1 = new Uint8Array([1, 2]);
|
||||
const doc2 = new Uint8Array([3, 4]);
|
||||
const doc3 = new Uint8Array([5, 6]);
|
||||
const doc4 = new Uint8Array([7, 8]);
|
||||
|
||||
cache.put(1, doc1);
|
||||
cache.put(2, doc2);
|
||||
cache.put(3, doc3);
|
||||
cache.put(4, doc4);
|
||||
// Cache has 8 bytes total
|
||||
|
||||
cache.resize(2);
|
||||
|
||||
// Should evict doc1, doc2, doc3 to get down to 2 bytes
|
||||
assert.equal(cache.get(1), undefined);
|
||||
assert.equal(cache.get(2), undefined);
|
||||
assert.equal(cache.get(3), undefined);
|
||||
assert.equal(cache.get(4), doc4);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,14 +14,12 @@ class LRUNode {
|
|||
|
||||
// evicting the least recently used documents when the size limit is exceeded.
|
||||
export class FixedSizeDocumentCache {
|
||||
private readonly maxSizeInBytes: number;
|
||||
private currentSizeInBytes: number;
|
||||
private readonly cache: Map<VaultUpdateId, LRUNode>;
|
||||
private head: LRUNode | null; // Least recently used
|
||||
private tail: LRUNode | null; // Most recently used
|
||||
|
||||
public constructor(maxSizeInBytes: number) {
|
||||
this.maxSizeInBytes = maxSizeInBytes;
|
||||
public constructor(private maxSizeInBytes: number) {
|
||||
this.currentSizeInBytes = 0;
|
||||
this.cache = new Map();
|
||||
this.head = null;
|
||||
|
|
@ -56,14 +54,7 @@ export class FixedSizeDocumentCache {
|
|||
this.cache.set(updateId, newNode);
|
||||
this.addToTail(newNode);
|
||||
this.currentSizeInBytes += content.byteLength;
|
||||
|
||||
// Evict least recently used documents if over size limit
|
||||
while (this.currentSizeInBytes > this.maxSizeInBytes && this.head) {
|
||||
const lruNode = this.head;
|
||||
this.removeNode(lruNode);
|
||||
this.cache.delete(lruNode.key);
|
||||
this.currentSizeInBytes -= lruNode.value.byteLength;
|
||||
}
|
||||
this.fitBelowMaxSize();
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
|
|
@ -73,6 +64,21 @@ export class FixedSizeDocumentCache {
|
|||
this.currentSizeInBytes = 0;
|
||||
}
|
||||
|
||||
public resize(newMaxSizeInBytes: number): void {
|
||||
this.maxSizeInBytes = newMaxSizeInBytes;
|
||||
this.fitBelowMaxSize();
|
||||
}
|
||||
|
||||
private fitBelowMaxSize(): void {
|
||||
// Evict least recently used documents if over size limit
|
||||
while (this.currentSizeInBytes > this.maxSizeInBytes && this.head) {
|
||||
const lruNode = this.head;
|
||||
this.removeNode(lruNode);
|
||||
this.cache.delete(lruNode.key);
|
||||
this.currentSizeInBytes -= lruNode.value.byteLength;
|
||||
}
|
||||
}
|
||||
|
||||
private removeNode(node: LRUNode): void {
|
||||
if (node.prev) {
|
||||
node.prev.next = node.next;
|
||||
|
|
|
|||
33
frontend/sync-client/src/utils/set-up-telemetry.ts
Normal file
33
frontend/sync-client/src/utils/set-up-telemetry.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import * as Sentry from "@sentry/browser";
|
||||
|
||||
export const setUpTelemetry = (): (() => void) => {
|
||||
Sentry.init({
|
||||
dsn: "https://56accd39d92442e788a457a04623cf57@bugs.schmelczer.dev/1",
|
||||
skipBrowserExtensionCheck: false
|
||||
});
|
||||
|
||||
const onError = (event: ErrorEvent): void => {
|
||||
Sentry.captureException(event.error, {
|
||||
extra: {
|
||||
message: event.message,
|
||||
filename: event.filename,
|
||||
lineno: event.lineno,
|
||||
colno: event.colno
|
||||
}
|
||||
});
|
||||
};
|
||||
window.addEventListener("error", onError);
|
||||
|
||||
const onUnhandledRejection = (event: PromiseRejectionEvent): void => {
|
||||
Sentry.captureException(event.reason);
|
||||
};
|
||||
window.addEventListener("unhandledrejection", onUnhandledRejection);
|
||||
|
||||
return (): void => {
|
||||
window.removeEventListener("error", onError);
|
||||
window.removeEventListener("unhandledrejection", onUnhandledRejection);
|
||||
Sentry.close(5000).catch(() => {
|
||||
// Ignore errors during shutdown
|
||||
});
|
||||
};
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue