From 9f008d519c5598d87c96f06f3db6fe47a06ca8e6 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Fri, 19 Nov 2021 01:08:06 -0800 Subject: [PATCH] sdk/server: probe method to ensure creation of everything. --- sdk/gen/types.input.ts | 8 +++++++ sdk/package-lock.json | 4 ++-- sdk/package.json | 2 +- sdk/scrypted_python/scrypted_sdk/__init__.py | 23 ++++++++++++++++---- sdk/scrypted_python/scrypted_sdk/types.py | 2 ++ sdk/types.d.ts | 7 ++++++ sdk/types.js | 1 + sdk/types.ts | 9 ++++++++ server/package-lock.json | 18 +++++++-------- server/package.json | 4 ++-- server/python/plugin-remote.py | 20 ++++++++++++++--- server/src/plugin/plugin-device.ts | 13 ++++++++++- server/src/plugin/system.ts | 12 ++++++++-- 13 files changed, 99 insertions(+), 24 deletions(-) diff --git a/sdk/gen/types.input.ts b/sdk/gen/types.input.ts index 2d60fe997..3ecdf5a35 100644 --- a/sdk/gen/types.input.ts +++ b/sdk/gen/types.input.ts @@ -15,6 +15,11 @@ export interface ScryptedDevice { setType(type: ScryptedDeviceType): Promise; + /** + * Probes the device, ensuring creation of it and any mixins. + */ + probe(): Promise; + id?: string; interfaces?: string[]; mixins?: string[]; @@ -825,6 +830,9 @@ export interface DeviceManager { */ getDeviceStorage(nativeId?: ScryptedNativeId): Storage; + /** + * Get all the native ids that have been reported by this plugin. This always includes "undefined", the plugin itself. + */ getNativeIds(): string[]; /** diff --git a/sdk/package-lock.json b/sdk/package-lock.json index 9b9ac4764..702913625 100644 --- a/sdk/package-lock.json +++ b/sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/sdk", - "version": "0.0.121", + "version": "0.0.122", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/sdk", - "version": "0.0.121", + "version": "0.0.122", "license": "ISC", "dependencies": { "@babel/plugin-proposal-class-properties": "^7.14.5", diff --git a/sdk/package.json b/sdk/package.json index 4b99667e5..4e9c3f6ec 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/sdk", - "version": "0.0.121", + "version": "0.0.122", "description": "", "main": "index.js", "scripts": { diff --git a/sdk/scrypted_python/scrypted_sdk/__init__.py b/sdk/scrypted_python/scrypted_sdk/__init__.py index d1b35f4f6..1967bf927 100644 --- a/sdk/scrypted_python/scrypted_sdk/__init__.py +++ b/sdk/scrypted_python/scrypted_sdk/__init__.py @@ -1,14 +1,28 @@ from __future__ import annotations from .types import * -import zipfile +from typing import Optional +from zipfile import ZipFile +import sys deviceManager: DeviceManager = None systemManager: SystemManager = None mediaManager: MediaManager = None -zip: zipfile.ZipFile = None +zip: ZipFile = None +remote: Any = None -def sdk_init(z: zipfile.ZipFile, sm: DeviceManager, dm: SystemManager, mm: MediaManager): +_print = print + +def print(*values: object, sep: Optional[str] = ' ', + end: Optional[str] = '\n', + file = sys.stdout, + flush: bool = True): + _print(*values, sep=sep, end=end, file=file, flush=flush) + remote.print(None, *values, sep=sep, end=end, flush=flush) + + +def sdk_init(z: ZipFile, r, sm: DeviceManager, dm: SystemManager, mm: MediaManager): global zip + global remote global systemManager global deviceManager global mediaManager @@ -16,11 +30,12 @@ def sdk_init(z: zipfile.ZipFile, sm: DeviceManager, dm: SystemManager, mm: Media deviceManager = dm mediaManager = mm zip = z + remote = r class ScryptedDeviceBase(DeviceState): nativeId: str | None deviceState: DeviceState = None - + def __init__(self, nativeId: str | None = None): self.nativeId = nativeId diff --git a/sdk/scrypted_python/scrypted_sdk/types.py b/sdk/scrypted_python/scrypted_sdk/types.py index b01d0b424..925e1847d 100644 --- a/sdk/scrypted_python/scrypted_sdk/types.py +++ b/sdk/scrypted_python/scrypted_sdk/types.py @@ -673,6 +673,8 @@ class ScryptedDevice: type: ScryptedDeviceType def listen(self, event: str | EventListenerOptions, callback: EventListener) -> EventListenerRegister: pass + async def probe(self) -> bool: + pass async def setName(self, name: str) -> None: pass async def setRoom(self, room: str) -> None: diff --git a/sdk/types.d.ts b/sdk/types.d.ts index f9a9ae330..9f3cf30ad 100644 --- a/sdk/types.d.ts +++ b/sdk/types.d.ts @@ -164,6 +164,10 @@ export interface ScryptedDevice { setName(name: string): Promise; setRoom(room: string): Promise; setType(type: ScryptedDeviceType): Promise; + /** + * Probes the device, ensuring creation of it and any mixins. + */ + probe(): Promise; id?: string; interfaces?: string[]; mixins?: string[]; @@ -898,6 +902,9 @@ export interface DeviceManager { * Get the per device Storage object. */ getDeviceStorage(nativeId?: ScryptedNativeId): Storage; + /** + * Get all the native ids that have been reported by this plugin. This always includes "undefined", the plugin itself. + */ getNativeIds(): string[]; /** * onDeviceDiscovered is used to report new devices that are trickle discovered, one by one, such as via a network broadcast. diff --git a/sdk/types.js b/sdk/types.js index 57e4fd768..4dc8763f6 100644 --- a/sdk/types.js +++ b/sdk/types.js @@ -60,6 +60,7 @@ exports.ScryptedInterfaceDescriptors = { name: 'ScryptedDevice', methods: [ 'listen', + 'probe', 'setName', 'setRoom', 'setType' diff --git a/sdk/types.ts b/sdk/types.ts index 9935e7fc5..976265955 100644 --- a/sdk/types.ts +++ b/sdk/types.ts @@ -161,6 +161,7 @@ export const ScryptedInterfaceDescriptors: { [scryptedInterface: string]: Scrypt name: 'ScryptedDevice', methods: [ 'listen', + 'probe', 'setName', 'setRoom', 'setType' @@ -623,6 +624,11 @@ export interface ScryptedDevice { setType(type: ScryptedDeviceType): Promise; + /** + * Probes the device, ensuring creation of it and any mixins. + */ + probe(): Promise; + id?: string; interfaces?: string[]; mixins?: string[]; @@ -1433,6 +1439,9 @@ export interface DeviceManager { */ getDeviceStorage(nativeId?: ScryptedNativeId): Storage; + /** + * Get all the native ids that have been reported by this plugin. This always includes "undefined", the plugin itself. + */ getNativeIds(): string[]; /** diff --git a/server/package-lock.json b/server/package-lock.json index 28964cca4..093a54fef 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,15 +1,15 @@ { "name": "@scrypted/server", - "version": "0.0.75", + "version": "0.0.77", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/server", - "version": "0.0.75", + "version": "0.0.77", "license": "ISC", "dependencies": { - "@scrypted/sdk": "^0.0.121", + "@scrypted/sdk": "^0.0.122", "adm-zip": "^0.5.3", "axios": "^0.21.1", "body-parser": "^1.19.0", @@ -1301,9 +1301,9 @@ } }, "node_modules/@scrypted/sdk": { - "version": "0.0.121", - "resolved": "https://registry.npmjs.org/@scrypted/sdk/-/sdk-0.0.121.tgz", - "integrity": "sha512-07FxYp1flBObrkgl6LnBKP3EtWwoIFUaiA+vCIYfQ3+ss7k3E/lfPeIKCXGTPLpE70iz0WF+oxovzveVeWh9/w==", + "version": "0.0.122", + "resolved": "https://registry.npmjs.org/@scrypted/sdk/-/sdk-0.0.122.tgz", + "integrity": "sha512-pmDZZMd0R/Hv3RJbbtwm+IYFSO8KpnA5PKKPe8qJw4Mck9mvP9v88HB9mE2751xlK424L2ScJG0GiuZYJrcr4Q==", "dependencies": { "@babel/plugin-proposal-class-properties": "^7.14.5", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", @@ -7169,9 +7169,9 @@ } }, "@scrypted/sdk": { - "version": "0.0.121", - "resolved": "https://registry.npmjs.org/@scrypted/sdk/-/sdk-0.0.121.tgz", - "integrity": "sha512-07FxYp1flBObrkgl6LnBKP3EtWwoIFUaiA+vCIYfQ3+ss7k3E/lfPeIKCXGTPLpE70iz0WF+oxovzveVeWh9/w==", + "version": "0.0.122", + "resolved": "https://registry.npmjs.org/@scrypted/sdk/-/sdk-0.0.122.tgz", + "integrity": "sha512-pmDZZMd0R/Hv3RJbbtwm+IYFSO8KpnA5PKKPe8qJw4Mck9mvP9v88HB9mE2751xlK424L2ScJG0GiuZYJrcr4Q==", "requires": { "@babel/plugin-proposal-class-properties": "^7.14.5", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", diff --git a/server/package.json b/server/package.json index e611b22a3..fd116b24d 100644 --- a/server/package.json +++ b/server/package.json @@ -1,9 +1,9 @@ { "name": "@scrypted/server", - "version": "0.0.75", + "version": "0.0.77", "description": "", "dependencies": { - "@scrypted/sdk": "^0.0.121", + "@scrypted/sdk": "^0.0.122", "adm-zip": "^0.5.3", "axios": "^0.21.1", "body-parser": "^1.19.0", diff --git a/server/python/plugin-remote.py b/server/python/plugin-remote.py index 64a3409d7..2ff9107d5 100644 --- a/server/python/plugin-remote.py +++ b/server/python/plugin-remote.py @@ -19,6 +19,8 @@ import time import zipfile import subprocess from typing import Any +from io import StringIO +from typing import Optional class SystemDeviceState(TypedDict): lastEventTime: int @@ -98,10 +100,22 @@ class PluginRemote: nativeIds: Mapping[str, DeviceStorage] = {} pluginId: str mediaManager: MediaManager + loop: AbstractEventLoop + consoles: Mapping[str, StringIO] = {} - def __init__(self, api, pluginId): + def __init__(self, api, pluginId, loop: AbstractEventLoop): self.api = api self.pluginId = pluginId + self.loop = loop + + def print(self, nativeId: str, *values: object, sep: Optional[str] = ..., + end: Optional[str] = ..., + flush: bool = ...,): + console = self.consoles.get(nativeId) + if not console: + console = StringIO() + self.consoles[nativeId] = console + print(*values, sep = sep, end = end, file = console, flush = flush) async def loadZip(self, packageJson, zipData, options=None): zipPath = options['filename'] @@ -150,7 +164,7 @@ class PluginRemote: self.systemManager = SystemManager(self.api, self.systemState) self.deviceManager = DeviceManager(self.nativeIds, self.systemManager) self.mediaManager = await self.api.getMediaManager() - sdk_init(zip, self.systemManager, self.deviceManager, self.mediaManager) + sdk_init(zip, self, self.systemManager, self.deviceManager, self.mediaManager) from main import create_scrypted_plugin # type: ignore return create_scrypted_plugin() @@ -223,7 +237,7 @@ async def async_main(loop: AbstractEventLoop): peer.constructorSerializerMap[bytearray] = 'Buffer' peer.params['print'] = print peer.params['getRemote'] = lambda api, pluginId: PluginRemote( - api, pluginId) + api, pluginId, loop) await readLoop(loop, peer, reader) diff --git a/server/src/plugin/plugin-device.ts b/server/src/plugin/plugin-device.ts index 47fe6a894..7f1f62258 100644 --- a/server/src/plugin/plugin-device.ts +++ b/server/src/plugin/plugin-device.ts @@ -39,6 +39,7 @@ export class PluginDeviceProxyHandler implements ProxyHandler, ScryptedDevi })().catch(() => { });; } + // this must not be async, because it potentially changes execution order. ensureProxy(): Promise { const pluginDevice = this.scrypted.findPluginDeviceById(this.id); if (!pluginDevice) @@ -115,7 +116,7 @@ export class PluginDeviceProxyHandler implements ProxyHandler, ScryptedDevi return mixinTable; })(); - return this.mixinTable.then(mixinTable => pluginDevice); + return this.mixinTable.then(_ => pluginDevice); } get(target: any, p: PropertyKey, receiver: any): any { @@ -164,6 +165,16 @@ export class PluginDeviceProxyHandler implements ProxyHandler, ScryptedDevi this.scrypted.stateManager.updateDescriptor(device); } + async probe(): Promise { + try { + await this.ensureProxy(); + return true; + } + catch (e) { + return false; + } + } + async applyMixin(method: string, argArray?: any): Promise { const iface = methodInterfaces[method]; if (!iface) diff --git a/server/src/plugin/system.ts b/server/src/plugin/system.ts index db072128b..319a0597b 100644 --- a/server/src/plugin/system.ts +++ b/server/src/plugin/system.ts @@ -41,10 +41,14 @@ class DeviceProxyHandler implements ProxyHandler, ScryptedDevice { return new Proxy(() => p, this); } - async apply(target: any, thisArg: any, argArray?: any) { - const method = target(); + async ensureDevice() { if (!this.device) this.device = await this.systemManager.api.getDeviceById(this.id); + } + + async apply(target: any, thisArg: any, argArray?: any) { + const method = target(); + await this.ensureDevice(); if (false && method === 'refresh') { const name = this.systemManager.state[this.id]?.[ScryptedInterfaceProperty.name].value; this.systemManager.log.i(`requested refresh ${name}`); @@ -65,6 +69,10 @@ class DeviceProxyHandler implements ProxyHandler, ScryptedDevice { async setType(type: ScryptedDeviceType): Promise { return this.systemManager.api.setDeviceProperty(this.id, ScryptedInterfaceProperty.type, type); } + + async probe(): Promise { + return this.apply(() => 'probe', undefined, []); + } }