import { SysCallMapping, System } from "../../plugos/system.ts"; import type { SyncEndpoint } from "../../plug-api/silverbullet-syscall/sync.ts"; import { SpaceSync, SyncStatusItem } from "../spaces/sync.ts"; import { HttpSpacePrimitives } from "../spaces/http_space_primitives.ts"; import { SpacePrimitives } from "../spaces/space_primitives.ts"; import { race, timeout } from "../async_util.ts"; export function syncSyscalls( localSpace: SpacePrimitives, system: System, ): SysCallMapping { return { "sync.syncAll": async ( _ctx, endpoint: SyncEndpoint, snapshot: Record, ): Promise< { snapshot: Record; operations: number; // The reason to not just throw an Error is so that the partially updated snapshot can still be saved error?: string; } > => { const { spaceSync } = setupSync(endpoint, snapshot); try { const operations = await spaceSync.syncFiles( SpaceSync.primaryConflictResolver, ); return { // And convert back to JSON snapshot: Object.fromEntries(spaceSync.snapshot), operations, }; } catch (e: any) { return { snapshot: Object.fromEntries(spaceSync.snapshot), operations: -1, error: e.message, }; } }, "sync.syncFile": async ( _ctx, endpoint: SyncEndpoint, snapshot: Record, name: string, ): Promise< { snapshot: Record; operations: number; // The reason to not just throw an Error is so that the partially updated snapshot can still be saved error?: string; } > => { const { spaceSync, remoteSpace } = setupSync(endpoint, snapshot); try { const localHash = (await localSpace.getFileMeta(name)).lastModified; let remoteHash: number | undefined = undefined; try { remoteHash = (await remoteSpace.getFileMeta(name)).lastModified; } catch (e: any) { if (e.message.includes("File not found")) { // File doesn't exist remotely, that's ok } else { throw e; } } const operations = await spaceSync.syncFile( name, localHash, remoteHash, SpaceSync.primaryConflictResolver, ); return { // And convert back to JSON snapshot: Object.fromEntries(spaceSync.snapshot), operations, }; } catch (e: any) { return { snapshot: Object.fromEntries(spaceSync.snapshot), operations: -1, error: e.message, }; } }, "sync.check": async (_ctx, endpoint: SyncEndpoint): Promise => { const syncSpace = new HttpSpacePrimitives( endpoint.url, endpoint.user, endpoint.password, ); // Let's just fetch the file list and see if it works try { await syncSpace.fetchFileList(); } catch (e: any) { console.error("Sync check failure", e.message); throw e; } }, }; function setupSync( endpoint: SyncEndpoint, snapshot: Record, ) { const remoteSpace = new HttpSpacePrimitives( endpoint.url, endpoint.user, endpoint.password, // Base64 PUTs to support mobile true, ); // Convert from JSON to a Map const syncStatusMap = new Map( Object.entries(snapshot), ); const spaceSync = new SpaceSync( localSpace, remoteSpace, syncStatusMap, // Log to the "sync" plug sandbox system.loadedPlugs.get("sync")!.sandbox!, ); return { spaceSync, remoteSpace }; } }