129 lines
3.9 KiB
TypeScript
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;
|