Keyborg/server/src/lib/devices.ts
2025-02-13 18:31:44 +03:00

129 lines
3.9 KiB
TypeScript

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<string, Device>,
CommandExecutionError | UsbipUnknownError
> = ok(new Map());
public update(
busid: string,
update: Partial<DeviceMutables>,
): Result<void, DeviceDoesNotExistError | FailedToAccessDevices> {
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<Option<Device[]>, 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<typeof deviceMutablesSchema>;
export type Device = InferSchemaType<typeof deviceSchema>;
const devices = new Devices();
devices.updateDevices();
export default devices;