Extract library from plugin

This commit is contained in:
Andras Schmelczer 2025-02-18 22:32:41 +00:00
parent 8374c971ee
commit ae3acb9e1e
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
37 changed files with 61 additions and 77 deletions

View file

@ -0,0 +1,61 @@
import type { Database } from "../database/database";
import type { SyncService } from "src/services/sync-service";
import { Logger } from "src/tracing/logger";
import type { Syncer } from "./syncer";
let isRunning = false;
export async function applyRemoteChangesLocally({
database,
syncService,
syncer
}: {
database: Database;
syncService: SyncService;
syncer: Syncer;
}): Promise<void> {
if (!database.getSettings().isSyncEnabled) {
Logger.getInstance().debug(
`Syncing is disabled, not fetching remote changes`
);
return;
} else if (isRunning) {
Logger.getInstance().debug(
"Applying remote changes locally is already in progress, skipping invocation"
);
return;
}
isRunning = true;
try {
const remote = await syncService.getAll(database.getLastSeenUpdateId());
if (remote.latestDocuments.length === 0) {
Logger.getInstance().debug("No remote changes to apply");
return;
}
Logger.getInstance().info("Applying remote changes locally");
await Promise.all(
remote.latestDocuments.map(async (remoteDocument) =>
syncer.syncRemotelyUpdatedFile(remoteDocument)
)
);
const lastSeenUpdateId = database.getLastSeenUpdateId();
if (
lastSeenUpdateId === undefined ||
remote.lastUpdateId > lastSeenUpdateId
) {
await database.setLastSeenUpdateId(remote.lastUpdateId);
}
} catch (e) {
Logger.getInstance().error(
`Failed to apply remote changes locally: ${e}`
);
} finally {
isRunning = false;
}
}

View file

@ -0,0 +1,79 @@
import {
tryLockDocument,
waitForDocumentLock,
unlockDocument
} from "./document-lock";
import type { RelativePath } from "src/database/document-metadata";
describe("Document Lock Operations", () => {
const testPath: RelativePath = "test/document/path";
beforeEach(() => {
// Reset the state before each test
(global as any).locked = new Set<RelativePath>();
(global as any).waiters = new Map<RelativePath, (() => void)[]>();
});
test("should lock a document successfully", () => {
const result = tryLockDocument(testPath);
expect(result).toBe(true);
});
test("should not lock a document that is already locked", () => {
tryLockDocument(testPath);
const result = tryLockDocument(testPath);
expect(result).toBe(false);
});
test("should unlock a locked document", () => {
tryLockDocument(testPath);
unlockDocument(testPath);
const result = tryLockDocument(testPath);
expect(result).toBe(true);
unlockDocument(testPath);
});
test("should throw an error when unlocking a document that is not locked", () => {
expect(() => {
unlockDocument(testPath);
}).toThrow(`Document ${testPath} is not locked, cannot unlock`);
});
test("should wait for a document lock and resolve when unlocked", async () => {
tryLockDocument(testPath);
let resolved = false;
const waitPromise = waitForDocumentLock(testPath).then(() => {
resolved = true;
});
unlockDocument(testPath);
await waitPromise;
expect(resolved).toBe(true);
});
test("should resolve multiple waiters in FIFO order", async () => {
tryLockDocument(testPath);
let firstResolved = false;
let secondResolved = false;
const firstWaitPromise = waitForDocumentLock(testPath).then(() => {
firstResolved = true;
});
const secondWaitPromise = waitForDocumentLock(testPath).then(() => {
secondResolved = true;
});
unlockDocument(testPath);
await firstWaitPromise;
expect(firstResolved).toBe(true);
expect(secondResolved).toBe(false);
unlockDocument(testPath);
await secondWaitPromise;
expect(secondResolved).toBe(true);
});
});

View file

@ -0,0 +1,48 @@
import type { RelativePath } from "src/database/document-metadata";
const locked = new Set<RelativePath>();
const waiters = new Map<RelativePath, (() => void)[]>();
export function tryLockDocument(relativePath: RelativePath): boolean {
if (locked.has(relativePath)) {
return false;
}
locked.add(relativePath);
return true;
}
export async function waitForDocumentLock(
relativePath: RelativePath
): Promise<void> {
if (tryLockDocument(relativePath)) {
return Promise.resolve();
}
return new Promise((resolve) => {
let waiting = waiters.get(relativePath);
if (!waiting) {
waiting = [];
waiters.set(relativePath, waiting);
}
waiting.push(resolve);
});
}
export function unlockDocument(relativePath: RelativePath): void {
if (!locked.has(relativePath)) {
throw new Error(
`Document ${relativePath} is not locked, cannot unlock`
);
}
// Remove the first element to ensure FIFO unblocking order
const nextWaiting = waiters.get(relativePath)?.shift();
if (nextWaiting) {
nextWaiting();
} else {
locked.delete(relativePath);
}
}