import usbip, { CommandExecutionError, DeviceDetailed, DeviceDoesNotExistError, deviceDoesNotExistError, UsbipUnknownError, } from "@src/lib/usbip.ts"; import { none, Option, some } from "@shared/utils/option.ts"; import { InferSchemaType, z } from "@shared/utils/validator.ts"; import log from "@shared/utils/logger.ts"; import { ResultAsync } from "@shared/utils/resultasync.ts"; import { err, Ok, ok, Result } from "@shared/utils/result.ts"; type FailedToAccessDevices = CommandExecutionError | UsbipUnknownError; class Devices { private devices: Result< Map, CommandExecutionError | UsbipUnknownError > = ok(new Map()); public update( busid: string, update: Partial, ): Result { return this.devices.andThen((devices) => { const device = devices.get(busid); if (device === undefined) { return err( deviceDoesNotExistError( `Device with busid ${busid} does not exist`, ), ); } for (const key of Object.keys(update)) { device[key as keyof typeof update] = update[key as keyof typeof update] || none; } return ok(); }); } public updateDevices(): ResultAsync< void, FailedToAccessDevices > { return usbip.getDevicesDetailed() .mapErr((e) => { log.error("Failed to update devices!"); this.devices = err(e); return e; }) .map((d) => d.unwrapOr([] as DeviceDetailed[])) .map( (devices) => { const current = new Set(devices.map((d) => d.busid)); const old = new Set( this.devices.unwrapOrElse(() => { this.devices = ok(new Map()); return this.devices.unwrap(); }).keys(), ); const connected = current.difference(old); const disconnected = old.difference(current); for (const device of devices) { if (connected.has(device.busid)) { this.devices.unwrap().set( device.busid, this.deviceFromDetailed(device), ); } } for (const device of disconnected) { this.devices.unwrap().delete(device); } }, ); } private deviceFromDetailed(d: DeviceDetailed): Device { return { busid: d.busid, usbid: d.usbid, vendor: d.vendor, name: d.name, displayName: none, description: none, connectedAt: new Date(), }; } public list(): Result, FailedToAccessDevices> { return this.devices.map((devices) => devices.values().toArray()).map(( devices, ) => devices.length > 0 ? some(devices) : none); } } export const deviceSchema = z.obj({ busid: z.string(), usbid: z.option(z.string()), vendor: z.option(z.string()), name: z.option(z.string()), displayName: z.option(z.string()), description: z.option(z.string()), connectedAt: z.date(), }).strict(); export const deviceMutablesSchema = deviceSchema.pick({ displayName: true, description: true, }); export type DeviceMutables = InferSchemaType; export type Device = InferSchemaType; const devices = new Devices(); devices.updateDevices(); export default devices;