@scrypted/server 0.2.8 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @scrypted/server might be problematic. Click here for more details.
- package/dist/mixin/mixin-cycle.js +30 -0
- package/dist/mixin/mixin-cycle.js.map +1 -0
- package/dist/plugin/media.js +11 -1
- package/dist/plugin/media.js.map +1 -1
- package/dist/plugin/plugin-host.js +2 -2
- package/dist/plugin/plugin-host.js.map +1 -1
- package/dist/plugin/plugin-remote-worker.js +1 -1
- package/dist/plugin/plugin-remote-worker.js.map +1 -1
- package/dist/plugin/plugin-state-check.js +3 -0
- package/dist/plugin/plugin-state-check.js.map +1 -1
- package/dist/plugin/runtime/python-worker.js +14 -9
- package/dist/plugin/runtime/python-worker.js.map +1 -1
- package/dist/rpc.js +11 -1
- package/dist/rpc.js.map +1 -1
- package/dist/runtime.js +8 -0
- package/dist/runtime.js.map +1 -1
- package/dist/scrypted-server-main.js +39 -42
- package/dist/scrypted-server-main.js.map +1 -1
- package/dist/services/plugin.js +8 -1
- package/dist/services/plugin.js.map +1 -1
- package/dist/services/service-control.js +10 -2
- package/dist/services/service-control.js.map +1 -1
- package/dist/state.js +3 -0
- package/dist/state.js.map +1 -1
- package/dist/usertoken.js +46 -0
- package/dist/usertoken.js.map +1 -0
- package/package.json +2 -2
- package/python/plugin-remote.py +120 -19
- package/python/rpc.py +24 -21
- package/src/mixin/mixin-cycle.ts +31 -0
- package/src/plugin/media.ts +13 -3
- package/src/plugin/plugin-host.ts +2 -2
- package/src/plugin/plugin-remote-worker.ts +1 -1
- package/src/plugin/plugin-state-check.ts +3 -0
- package/src/plugin/runtime/python-worker.ts +14 -9
- package/src/rpc.ts +12 -1
- package/src/runtime.ts +9 -0
- package/src/scrypted-server-main.ts +41 -46
- package/src/services/plugin.ts +8 -1
- package/src/services/service-control.ts +10 -2
- package/src/state.ts +3 -0
- package/src/usertoken.ts +38 -0
package/python/plugin-remote.py
CHANGED
@@ -4,6 +4,7 @@ import asyncio
|
|
4
4
|
import base64
|
5
5
|
import gc
|
6
6
|
import json
|
7
|
+
import mimetypes
|
7
8
|
import os
|
8
9
|
import platform
|
9
10
|
import shutil
|
@@ -17,12 +18,11 @@ from asyncio.streams import StreamReader, StreamWriter
|
|
17
18
|
from collections.abc import Mapping
|
18
19
|
from io import StringIO
|
19
20
|
from os import sys
|
20
|
-
from typing import Any, Optional, Set, Tuple
|
21
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
21
22
|
|
22
23
|
import aiofiles
|
23
24
|
import scrypted_python.scrypted_sdk.types
|
24
25
|
from scrypted_python.scrypted_sdk.types import (Device, DeviceManifest,
|
25
|
-
MediaManager,
|
26
26
|
ScryptedInterfaceProperty,
|
27
27
|
Storage)
|
28
28
|
from typing_extensions import TypedDict
|
@@ -43,6 +43,50 @@ class SystemManager(scrypted_python.scrypted_sdk.types.SystemManager):
|
|
43
43
|
async def getComponent(self, id: str) -> Any:
|
44
44
|
return await self.api.getComponent(id)
|
45
45
|
|
46
|
+
class MediaObjectRemote(scrypted_python.scrypted_sdk.types.MediaObject):
|
47
|
+
def __init__(self, data, mimeType, sourceId):
|
48
|
+
self.mimeType = mimeType
|
49
|
+
self.data = data
|
50
|
+
setattr(self, '__proxy_props', {
|
51
|
+
'mimeType': mimeType,
|
52
|
+
'sourceId': sourceId,
|
53
|
+
})
|
54
|
+
|
55
|
+
async def getData(self):
|
56
|
+
return self.data
|
57
|
+
|
58
|
+
class MediaManager:
|
59
|
+
def __init__(self, mediaManager: scrypted_python.scrypted_sdk.types.MediaManager):
|
60
|
+
self.mediaManager = mediaManager
|
61
|
+
|
62
|
+
async def addConverter(self, converter: scrypted_python.scrypted_sdk.types.BufferConverter) -> None:
|
63
|
+
return await self.mediaManager.addConverter(converter)
|
64
|
+
async def clearConverters(self) -> None:
|
65
|
+
return await self.mediaManager.clearConverters()
|
66
|
+
async def convertMediaObject(self, mediaObject: scrypted_python.scrypted_sdk.types.MediaObject, toMimeType: str) -> Any:
|
67
|
+
return await self.mediaManager.convertMediaObject(mediaObject, toMimeType)
|
68
|
+
async def convertMediaObjectToBuffer(self, mediaObject: scrypted_python.scrypted_sdk.types.MediaObject, toMimeType: str) -> bytearray:
|
69
|
+
return await self.mediaManager.convertMediaObjectToBuffer(mediaObject, toMimeType)
|
70
|
+
async def convertMediaObjectToInsecureLocalUrl(self, mediaObject: str | scrypted_python.scrypted_sdk.types.MediaObject, toMimeType: str) -> str:
|
71
|
+
return await self.mediaManager.convertMediaObjectToInsecureLocalUrl(mediaObject, toMimeType)
|
72
|
+
async def convertMediaObjectToJSON(self, mediaObject: scrypted_python.scrypted_sdk.types.MediaObject, toMimeType: str) -> Any:
|
73
|
+
return await self.mediaManager.convertMediaObjectToJSON(mediaObject, toMimeType)
|
74
|
+
async def convertMediaObjectToLocalUrl(self, mediaObject: str | scrypted_python.scrypted_sdk.types.MediaObject, toMimeType: str) -> str:
|
75
|
+
return await self.mediaManager.convertMediaObjectToLocalUrl(mediaObject, toMimeType)
|
76
|
+
async def convertMediaObjectToUrl(self, mediaObject: str | scrypted_python.scrypted_sdk.types.MediaObject, toMimeType: str) -> str:
|
77
|
+
return await self.mediaManager.convertMediaObjectToUrl(mediaObject, toMimeType)
|
78
|
+
async def createFFmpegMediaObject(self, ffmpegInput: scrypted_python.scrypted_sdk.types.FFmpegInput, options: scrypted_python.scrypted_sdk.types.MediaObjectOptions = None) -> scrypted_python.scrypted_sdk.types.MediaObject:
|
79
|
+
return await self.mediaManager.createFFmpegMediaObject(ffmpegInput, options)
|
80
|
+
async def createMediaObject(self, data: Any, mimeType: str, options: scrypted_python.scrypted_sdk.types.MediaObjectOptions = None) -> scrypted_python.scrypted_sdk.types.MediaObject:
|
81
|
+
# return await self.createMediaObject(data, mimetypes, options)
|
82
|
+
return MediaObjectRemote(data, mimeType, options.get('sourceId', None) if options else None)
|
83
|
+
async def createMediaObjectFromUrl(self, data: str, options:scrypted_python.scrypted_sdk.types. MediaObjectOptions = None) -> scrypted_python.scrypted_sdk.types.MediaObject:
|
84
|
+
return await self.mediaManager.createMediaObjectFromUrl(data, options)
|
85
|
+
async def getFFmpegPath(self) -> str:
|
86
|
+
return await self.mediaManager.getFFmpegPath()
|
87
|
+
async def getFilesPath(self) -> str:
|
88
|
+
return await self.mediaManager.getFilesPath()
|
89
|
+
|
46
90
|
class DeviceState(scrypted_python.scrypted_sdk.types.DeviceState):
|
47
91
|
def __init__(self, id: str, nativeId: str, systemManager: SystemManager, deviceManager: scrypted_python.scrypted_sdk.types.DeviceManager) -> None:
|
48
92
|
super().__init__()
|
@@ -139,13 +183,27 @@ class DeviceManager(scrypted_python.scrypted_sdk.types.DeviceManager):
|
|
139
183
|
return self.nativeIds.get(nativeId, None)
|
140
184
|
|
141
185
|
class BufferSerializer(rpc.RpcSerializer):
|
142
|
-
def serialize(self, value):
|
186
|
+
def serialize(self, value, serializationContext):
|
143
187
|
return base64.b64encode(value).decode('utf8')
|
144
188
|
|
145
|
-
def deserialize(self, value):
|
189
|
+
def deserialize(self, value, serializationContext):
|
146
190
|
return base64.b64decode(value)
|
147
191
|
|
148
192
|
|
193
|
+
class SidebandBufferSerializer(rpc.RpcSerializer):
|
194
|
+
def serialize(self, value, serializationContext):
|
195
|
+
buffers = serializationContext.get('buffers', None)
|
196
|
+
if not buffers:
|
197
|
+
buffers = []
|
198
|
+
serializationContext['buffers'] = buffers
|
199
|
+
buffers.append(value)
|
200
|
+
return len(buffers) - 1
|
201
|
+
|
202
|
+
def deserialize(self, value, serializationContext):
|
203
|
+
buffers: List = serializationContext.get('buffers', None)
|
204
|
+
buffer = buffers.pop()
|
205
|
+
return buffer
|
206
|
+
|
149
207
|
class PluginRemote:
|
150
208
|
systemState: Mapping[str, Mapping[str, SystemDeviceState]] = {}
|
151
209
|
nativeIds: Mapping[str, DeviceStorage] = {}
|
@@ -278,7 +336,7 @@ class PluginRemote:
|
|
278
336
|
from scrypted_sdk import sdk_init # type: ignore
|
279
337
|
self.systemManager = SystemManager(self.api, self.systemState)
|
280
338
|
self.deviceManager = DeviceManager(self.nativeIds, self.systemManager)
|
281
|
-
self.mediaManager = await self.api.getMediaManager()
|
339
|
+
self.mediaManager = MediaManager(await self.api.getMediaManager())
|
282
340
|
sdk_init(zip, self, self.systemManager,
|
283
341
|
self.deviceManager, self.mediaManager)
|
284
342
|
from main import create_scrypted_plugin # type: ignore
|
@@ -329,29 +387,72 @@ class PluginRemote:
|
|
329
387
|
pass
|
330
388
|
|
331
389
|
|
332
|
-
async def readLoop(loop, peer, reader):
|
333
|
-
|
390
|
+
async def readLoop(loop, peer: rpc.RpcPeer, reader):
|
391
|
+
deserializationContext = {
|
392
|
+
'buffers': []
|
393
|
+
}
|
394
|
+
|
395
|
+
while True:
|
334
396
|
try:
|
335
|
-
|
336
|
-
|
397
|
+
lengthBytes = await reader.read(4)
|
398
|
+
typeBytes = await reader.read(1)
|
399
|
+
type = typeBytes[0]
|
400
|
+
length = int.from_bytes(lengthBytes, 'big')
|
401
|
+
data = await reader.read(length - 1)
|
402
|
+
|
403
|
+
if type == 1:
|
404
|
+
deserializationContext['buffers'].append(data)
|
405
|
+
continue
|
406
|
+
|
407
|
+
message = json.loads(data)
|
408
|
+
asyncio.run_coroutine_threadsafe(peer.handleMessage(message, deserializationContext), loop)
|
409
|
+
|
410
|
+
deserializationContext = {
|
411
|
+
'buffers': []
|
412
|
+
}
|
337
413
|
except Exception as e:
|
338
414
|
print('read loop error', e)
|
339
415
|
sys.exit()
|
340
416
|
|
341
417
|
|
342
418
|
async def async_main(loop: AbstractEventLoop):
|
343
|
-
reader = await aiofiles.open(3, mode='
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
419
|
+
reader = await aiofiles.open(3, mode='rb')
|
420
|
+
|
421
|
+
mutex = threading.Lock()
|
422
|
+
|
423
|
+
def send(message, reject=None, serializationContext = None):
|
424
|
+
with mutex:
|
425
|
+
if serializationContext:
|
426
|
+
buffers = serializationContext.get('buffers', None)
|
427
|
+
if buffers:
|
428
|
+
for buffer in buffers:
|
429
|
+
length = len(buffer) + 1
|
430
|
+
lb = length.to_bytes(4, 'big')
|
431
|
+
type = 1
|
432
|
+
try:
|
433
|
+
os.write(4, lb)
|
434
|
+
os.write(4, bytes([type]))
|
435
|
+
os.write(4, buffer)
|
436
|
+
except Exception as e:
|
437
|
+
if reject:
|
438
|
+
reject(e)
|
439
|
+
return
|
440
|
+
|
441
|
+
jsonString = json.dumps(message)
|
442
|
+
b = bytes(jsonString, 'utf8')
|
443
|
+
length = len(b) + 1
|
444
|
+
lb = length.to_bytes(4, 'big')
|
445
|
+
type = 0
|
446
|
+
try:
|
447
|
+
os.write(4, lb)
|
448
|
+
os.write(4, bytes([type]))
|
449
|
+
os.write(4, b)
|
450
|
+
except Exception as e:
|
451
|
+
if reject:
|
452
|
+
reject(e)
|
352
453
|
|
353
454
|
peer = rpc.RpcPeer(send)
|
354
|
-
peer.nameDeserializerMap['Buffer'] =
|
455
|
+
peer.nameDeserializerMap['Buffer'] = SidebandBufferSerializer()
|
355
456
|
peer.constructorSerializerMap[bytes] = 'Buffer'
|
356
457
|
peer.constructorSerializerMap[bytearray] = 'Buffer'
|
357
458
|
peer.params['print'] = print
|
package/python/rpc.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
from asyncio.futures import Future
|
2
|
-
from typing import Any, Callable, Mapping, List
|
2
|
+
from typing import Any, Callable, Dict, Mapping, List
|
3
3
|
import traceback
|
4
4
|
import inspect
|
5
5
|
from typing_extensions import TypedDict
|
@@ -31,10 +31,10 @@ class RpcResultException(Exception):
|
|
31
31
|
|
32
32
|
|
33
33
|
class RpcSerializer:
|
34
|
-
def serialize(self, value):
|
34
|
+
def serialize(self, value, serializationContext):
|
35
35
|
pass
|
36
36
|
|
37
|
-
def deserialize(self, value):
|
37
|
+
def deserialize(self, value, deserializationContext):
|
38
38
|
pass
|
39
39
|
|
40
40
|
|
@@ -72,7 +72,7 @@ class RpcProxy(object):
|
|
72
72
|
|
73
73
|
def __setattr__(self, name: str, value: Any) -> None:
|
74
74
|
if name == '__proxy_finalizer_id':
|
75
|
-
self.
|
75
|
+
self.__dict__['__proxy_entry']['finalizerId'] = value
|
76
76
|
|
77
77
|
return super().__setattr__(name, value)
|
78
78
|
|
@@ -85,7 +85,7 @@ class RpcProxy(object):
|
|
85
85
|
|
86
86
|
|
87
87
|
class RpcPeer:
|
88
|
-
def __init__(self, send: Callable[[object, Callable[[Exception], None]], None]) -> None:
|
88
|
+
def __init__(self, send: Callable[[object, Callable[[Exception], None], Dict], None]) -> None:
|
89
89
|
self.send = send
|
90
90
|
self.idCounter = 1
|
91
91
|
self.peerName = 'Unnamed Peer'
|
@@ -99,9 +99,10 @@ class RpcPeer:
|
|
99
99
|
self.nameDeserializerMap: Mapping[str, RpcSerializer] = {}
|
100
100
|
|
101
101
|
def __apply__(self, proxyId: str, oneWayMethods: List[str], method: str, args: list):
|
102
|
+
serializationContext: Dict = {}
|
102
103
|
serializedArgs = []
|
103
104
|
for arg in args:
|
104
|
-
serializedArgs.append(self.serialize(arg, False))
|
105
|
+
serializedArgs.append(self.serialize(arg, False, serializationContext))
|
105
106
|
|
106
107
|
rpcApply = {
|
107
108
|
'type': 'apply',
|
@@ -113,25 +114,25 @@ class RpcPeer:
|
|
113
114
|
|
114
115
|
if oneWayMethods and method in oneWayMethods:
|
115
116
|
rpcApply['oneway'] = True
|
116
|
-
self.send(rpcApply)
|
117
|
+
self.send(rpcApply, None, serializationContext)
|
117
118
|
future = Future()
|
118
119
|
future.set_result(None)
|
119
120
|
return future
|
120
121
|
|
121
122
|
async def send(id: str, reject: Callable[[Exception], None]):
|
122
123
|
rpcApply['id'] = id
|
123
|
-
self.send(rpcApply, reject)
|
124
|
+
self.send(rpcApply, reject, serializationContext)
|
124
125
|
return self.createPendingResult(send)
|
125
126
|
|
126
127
|
def kill(self):
|
127
128
|
self.killed = True
|
128
129
|
|
129
|
-
def createErrorResult(self, result:
|
130
|
+
def createErrorResult(self, result: Any, name: str, message: str, tb: str):
|
130
131
|
result['stack'] = tb if tb else 'no stack'
|
131
132
|
result['result'] = name if name else 'no name'
|
132
133
|
result['message'] = message if message else 'no message'
|
133
134
|
|
134
|
-
def serialize(self, value, requireProxy):
|
135
|
+
def serialize(self, value, requireProxy, serializationContext: Dict):
|
135
136
|
if (not value or (not requireProxy and type(value) in jsonSerializable)):
|
136
137
|
return value
|
137
138
|
|
@@ -164,7 +165,7 @@ class RpcPeer:
|
|
164
165
|
if serializerMapName:
|
165
166
|
__remote_constructor_name = serializerMapName
|
166
167
|
serializer = self.nameDeserializerMap.get(serializerMapName, None)
|
167
|
-
serialized = serializer.serialize(value)
|
168
|
+
serialized = serializer.serialize(value, serializationContext)
|
168
169
|
ret = {
|
169
170
|
'__remote_proxy_id': None,
|
170
171
|
'__remote_proxy_finalizer_id': None,
|
@@ -216,7 +217,7 @@ class RpcPeer:
|
|
216
217
|
weakref.finalize(proxy, lambda: self.finalize(localProxiedEntry))
|
217
218
|
return proxy
|
218
219
|
|
219
|
-
def deserialize(self, value):
|
220
|
+
def deserialize(self, value, deserializationContext: Dict):
|
220
221
|
if not value:
|
221
222
|
return value
|
222
223
|
|
@@ -240,7 +241,7 @@ class RpcPeer:
|
|
240
241
|
if not proxy:
|
241
242
|
proxy = self.newProxy(__remote_proxy_id, __remote_constructor_name,
|
242
243
|
__remote_proxy_props, __remote_proxy_oneway_methods)
|
243
|
-
proxy
|
244
|
+
setattr(proxy, '__proxy_finalizer_id', __remote_proxy_finalizer_id)
|
244
245
|
return proxy
|
245
246
|
|
246
247
|
if __local_proxy_id:
|
@@ -253,11 +254,11 @@ class RpcPeer:
|
|
253
254
|
deserializer = self.nameDeserializerMap.get(
|
254
255
|
__remote_constructor_name, None)
|
255
256
|
if deserializer:
|
256
|
-
return deserializer.deserialize(__serialized_value)
|
257
|
+
return deserializer.deserialize(__serialized_value, deserializationContext)
|
257
258
|
|
258
259
|
return value
|
259
260
|
|
260
|
-
async def handleMessage(self, message:
|
261
|
+
async def handleMessage(self, message: Any, deserializationContext: Dict):
|
261
262
|
try:
|
262
263
|
messageType = message['type']
|
263
264
|
if messageType == 'param':
|
@@ -266,17 +267,18 @@ class RpcPeer:
|
|
266
267
|
'id': message['id'],
|
267
268
|
}
|
268
269
|
|
270
|
+
serializationContext: Dict = {}
|
269
271
|
try:
|
270
272
|
value = self.params.get(message['param'], None)
|
271
273
|
value = await maybe_await(value)
|
272
274
|
result['result'] = self.serialize(
|
273
|
-
value, message.get('requireProxy', None))
|
275
|
+
value, message.get('requireProxy', None), serializationContext)
|
274
276
|
except Exception as e:
|
275
277
|
tb = traceback.format_exc()
|
276
278
|
self.createErrorResult(
|
277
279
|
result, type(e).__name, str(e), tb)
|
278
280
|
|
279
|
-
self.send(result)
|
281
|
+
self.send(result, None, serializationContext)
|
280
282
|
|
281
283
|
elif messageType == 'apply':
|
282
284
|
result = {
|
@@ -286,6 +288,7 @@ class RpcPeer:
|
|
286
288
|
method = message.get('method', None)
|
287
289
|
|
288
290
|
try:
|
291
|
+
serializationContext: Dict = {}
|
289
292
|
target = self.localProxyMap.get(
|
290
293
|
message['proxyId'], None)
|
291
294
|
if not target:
|
@@ -294,7 +297,7 @@ class RpcPeer:
|
|
294
297
|
|
295
298
|
args = []
|
296
299
|
for arg in (message['args'] or []):
|
297
|
-
args.append(self.deserialize(arg))
|
300
|
+
args.append(self.deserialize(arg, deserializationContext))
|
298
301
|
|
299
302
|
value = None
|
300
303
|
if method:
|
@@ -306,7 +309,7 @@ class RpcPeer:
|
|
306
309
|
else:
|
307
310
|
value = await maybe_await(target(*args))
|
308
311
|
|
309
|
-
result['result'] = self.serialize(value, False)
|
312
|
+
result['result'] = self.serialize(value, False, serializationContext)
|
310
313
|
except Exception as e:
|
311
314
|
tb = traceback.format_exc()
|
312
315
|
# print('failure', method, e, tb)
|
@@ -314,7 +317,7 @@ class RpcPeer:
|
|
314
317
|
result, type(e).__name__, str(e), tb)
|
315
318
|
|
316
319
|
if not message.get('oneway', False):
|
317
|
-
self.send(result)
|
320
|
+
self.send(result, None, serializationContext)
|
318
321
|
|
319
322
|
elif messageType == 'result':
|
320
323
|
id = message['id']
|
@@ -331,7 +334,7 @@ class RpcPeer:
|
|
331
334
|
future.set_exception(e)
|
332
335
|
return
|
333
336
|
future.set_result(self.deserialize(
|
334
|
-
message.get('result', None)))
|
337
|
+
message.get('result', None), deserializationContext))
|
335
338
|
elif messageType == 'finalize':
|
336
339
|
finalizerId = message.get('__local_proxy_finalizer_id', None)
|
337
340
|
proxyId = message['__local_proxy_id']
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import { ScryptedInterfaceProperty } from "@scrypted/types";
|
2
|
+
import { ScryptedRuntime } from "../runtime";
|
3
|
+
import { getState } from "../state";
|
4
|
+
|
5
|
+
function getMixins(scrypted: ScryptedRuntime, id: string) {
|
6
|
+
const pluginDevice = scrypted.findPluginDeviceById(id);
|
7
|
+
if (!pluginDevice)
|
8
|
+
return [];
|
9
|
+
return getState(pluginDevice, ScryptedInterfaceProperty.mixins) || [];
|
10
|
+
}
|
11
|
+
|
12
|
+
export function hasMixinCycle(scrypted: ScryptedRuntime, id: string, mixins?: string[]) {
|
13
|
+
mixins = mixins || getMixins(scrypted, id);
|
14
|
+
|
15
|
+
// given the mixins for a device, find all the mixins for those mixins,
|
16
|
+
// and create a visited graph.
|
17
|
+
// if the visited graphs includes the original device, that indicates
|
18
|
+
// a cyclical dependency for that device.
|
19
|
+
const visitedMixins = new Set(mixins);
|
20
|
+
|
21
|
+
while (mixins.length) {
|
22
|
+
const mixin = mixins.pop();
|
23
|
+
if (visitedMixins.has(mixin))
|
24
|
+
continue;
|
25
|
+
visitedMixins.add(mixin);
|
26
|
+
const providerMixins = getMixins(scrypted, mixin);
|
27
|
+
mixins.push(...providerMixins);
|
28
|
+
}
|
29
|
+
|
30
|
+
return visitedMixins.has(id);
|
31
|
+
}
|
package/src/plugin/media.ts
CHANGED
@@ -201,7 +201,17 @@ export abstract class MediaManagerBase implements MediaManager {
|
|
201
201
|
getConverters(): IdBufferConverter[] {
|
202
202
|
const converters = Object.entries(this.getSystemState())
|
203
203
|
.filter(([id, state]) => state[ScryptedInterfaceProperty.interfaces]?.value?.includes(ScryptedInterface.BufferConverter))
|
204
|
-
.map(([id]) =>
|
204
|
+
.map(([id]) => {
|
205
|
+
const device = this.getDeviceById<BufferConverter>(id);
|
206
|
+
return {
|
207
|
+
id,
|
208
|
+
fromMimeType: device.fromMimeType,
|
209
|
+
toMimeType: device.toMimeType,
|
210
|
+
convert(data, fromMimeType, toMimeType, options?) {
|
211
|
+
return device.convert(data, fromMimeType, toMimeType, options);
|
212
|
+
},
|
213
|
+
} as IdBufferConverter;
|
214
|
+
});
|
205
215
|
|
206
216
|
// builtins should be after system converters. these should not be overriden by system,
|
207
217
|
// as it could cause system instability with misconfiguration.
|
@@ -328,7 +338,7 @@ export abstract class MediaManagerBase implements MediaManager {
|
|
328
338
|
converterMap.set(c.id, c);
|
329
339
|
}
|
330
340
|
|
331
|
-
const nodes:
|
341
|
+
const nodes: { [node: string]: { [edge: string]: number } } = {};
|
332
342
|
const mediaNode: any = {};
|
333
343
|
nodes['mediaObject'] = mediaNode;
|
334
344
|
nodes['output'] = {};
|
@@ -341,7 +351,7 @@ export abstract class MediaManagerBase implements MediaManager {
|
|
341
351
|
// const convertedWeight = parseFloat(convertedMime.parameters.get('converter-weight')) || (convertedMime.essence === ScryptedMimeTypes.MediaObject ? 1000 : 1);
|
342
352
|
// const conversionWeight = inputWeight + convertedWeight;
|
343
353
|
const targetId = converter.id;
|
344
|
-
const node:
|
354
|
+
const node: { [edge: string]: number } = nodes[targetId] = {};
|
345
355
|
|
346
356
|
// edge matches
|
347
357
|
for (const candidate of converters) {
|
@@ -293,9 +293,9 @@ export class PluginHost {
|
|
293
293
|
}
|
294
294
|
}
|
295
295
|
|
296
|
-
this.peer = new RpcPeer('host', this.pluginId, (message, reject) => {
|
296
|
+
this.peer = new RpcPeer('host', this.pluginId, (message, reject, serializationContext) => {
|
297
297
|
if (connected) {
|
298
|
-
this.worker.send(message, reject);
|
298
|
+
this.worker.send(message, reject, serializationContext);
|
299
299
|
}
|
300
300
|
else if (reject) {
|
301
301
|
reject(new Error('peer disconnected'));
|
@@ -397,7 +397,7 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
|
|
397
397
|
peer.evalLocal(script, zipOptions?.filename || '/plugin/main.nodejs.js', params);
|
398
398
|
|
399
399
|
if (zipOptions?.fork) {
|
400
|
-
pluginConsole?.log('plugin forked');
|
400
|
+
// pluginConsole?.log('plugin forked');
|
401
401
|
const fork = exports.fork;
|
402
402
|
const forked = await fork();
|
403
403
|
forked[RpcPeer.PROPERTY_JSON_DISABLE_SERIALIZATION] = true;
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { ScryptedInterface, ScryptedInterfaceProperty } from "@scrypted/types";
|
2
|
+
import { RpcPeer } from "../rpc";
|
2
3
|
import { propertyInterfaces } from "./descriptor";
|
3
4
|
|
4
5
|
export function checkProperty(key: string, value: any) {
|
@@ -8,6 +9,8 @@ export function checkProperty(key: string, value: any) {
|
|
8
9
|
throw new Error("mixins is read only");
|
9
10
|
if (key === ScryptedInterfaceProperty.interfaces)
|
10
11
|
throw new Error("interfaces is a read only post-mixin computed property, use providedInterfaces");
|
12
|
+
if (RpcPeer.isRpcProxy(value))
|
13
|
+
throw new Error('value must be a primitive type')
|
11
14
|
const iface = propertyInterfaces[key.toString()];
|
12
15
|
if (iface === ScryptedInterface.ScryptedDevice) {
|
13
16
|
// only allow info to be set, since that doesn't actually change the descriptor
|
@@ -5,10 +5,12 @@ import path from 'path';
|
|
5
5
|
import readline from 'readline';
|
6
6
|
import { Readable, Writable } from 'stream';
|
7
7
|
import { RpcMessage, RpcPeer } from "../../rpc";
|
8
|
+
import { createRpcDuplexSerializer } from '../../rpc-serializer';
|
8
9
|
import { ChildProcessWorker } from "./child-process-worker";
|
9
10
|
import { RuntimeWorkerOptions } from "./runtime-worker";
|
10
11
|
|
11
12
|
export class PythonRuntimeWorker extends ChildProcessWorker {
|
13
|
+
serializer: ReturnType<typeof createRpcDuplexSerializer>;
|
12
14
|
|
13
15
|
constructor(pluginId: string, options: RuntimeWorkerOptions) {
|
14
16
|
super(pluginId, options);
|
@@ -62,21 +64,24 @@ export class PythonRuntimeWorker extends ChildProcessWorker {
|
|
62
64
|
const peerin = this.worker.stdio[3] as Writable;
|
63
65
|
const peerout = this.worker.stdio[4] as Readable;
|
64
66
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
67
|
+
const serializer = this.serializer = createRpcDuplexSerializer(peerin);
|
68
|
+
serializer.setupRpcPeer(peer);
|
69
|
+
peerout.on('data', data => serializer.onData(data));
|
70
|
+
peerin.on('error', e => {
|
71
|
+
this.emit('error', e);
|
72
|
+
serializer.onDisconnected();
|
73
|
+
});
|
74
|
+
peerout.on('error', e => {
|
75
|
+
this.emit('error', e)
|
76
|
+
serializer.onDisconnected();
|
71
77
|
});
|
72
|
-
readInterface.on('line', line => peer.handleMessage(JSON.parse(line)));
|
73
78
|
}
|
74
79
|
|
75
|
-
send(message: RpcMessage, reject?: (e: Error) => void): void {
|
80
|
+
send(message: RpcMessage, reject?: (e: Error) => void, serializationContext?: any): void {
|
76
81
|
try {
|
77
82
|
if (!this.worker)
|
78
83
|
throw new Error('worked has been killed');
|
79
|
-
|
84
|
+
this.serializer.sendMessage(message, reject, serializationContext);
|
80
85
|
}
|
81
86
|
catch (e) {
|
82
87
|
reject?.(e);
|
package/src/rpc.ts
CHANGED
@@ -84,7 +84,7 @@ class RpcProxy implements PrimitiveProxyHandler<any> {
|
|
84
84
|
}
|
85
85
|
|
86
86
|
get(target: any, p: PropertyKey, receiver: any): any {
|
87
|
-
if (p ===
|
87
|
+
if (p === RpcPeer.PROPERTY_PROXY_ID)
|
88
88
|
return this.entry.id;
|
89
89
|
if (p === '__proxy_constructor')
|
90
90
|
return this.constructorName;
|
@@ -211,9 +211,15 @@ export class RpcPeer {
|
|
211
211
|
nameDeserializerMap = new Map<string, RpcSerializer>();
|
212
212
|
constructorSerializerMap = new Map<any, string>();
|
213
213
|
transportSafeArgumentTypes = RpcPeer.getDefaultTransportSafeArgumentTypes();
|
214
|
+
killed: Promise<void>;
|
215
|
+
killedDeferred: Deferred;
|
214
216
|
|
215
217
|
static readonly finalizerIdSymbol = Symbol('rpcFinalizerId');
|
216
218
|
|
219
|
+
static isRpcProxy(value: any) {
|
220
|
+
return !!value?.[RpcPeer.PROPERTY_PROXY_ID];
|
221
|
+
}
|
222
|
+
|
217
223
|
static getDefaultTransportSafeArgumentTypes() {
|
218
224
|
const jsonSerializable = new Set<string>();
|
219
225
|
jsonSerializable.add(Number.name);
|
@@ -242,6 +248,7 @@ export class RpcPeer {
|
|
242
248
|
}
|
243
249
|
}
|
244
250
|
|
251
|
+
static readonly PROPERTY_PROXY_ID = '__proxy_id';
|
245
252
|
static readonly PROPERTY_PROXY_ONEWAY_METHODS = '__proxy_oneway_methods';
|
246
253
|
static readonly PROPERTY_JSON_DISABLE_SERIALIZATION = '__json_disable_serialization';
|
247
254
|
static readonly PROPERTY_PROXY_PROPERTIES = '__proxy_props';
|
@@ -259,6 +266,9 @@ export class RpcPeer {
|
|
259
266
|
]);
|
260
267
|
|
261
268
|
constructor(public selfName: string, public peerName: string, public send: (message: RpcMessage, reject?: (e: Error) => void, serializationContext?: any) => void) {
|
269
|
+
this.killed = new Promise((resolve, reject) => {
|
270
|
+
this.killedDeferred = { resolve, reject };
|
271
|
+
});
|
262
272
|
}
|
263
273
|
|
264
274
|
createPendingResult(cb: (id: string, reject: (e: Error) => void) => void): Promise<any> {
|
@@ -280,6 +290,7 @@ export class RpcPeer {
|
|
280
290
|
|
281
291
|
kill(message?: string) {
|
282
292
|
const error = new RPCResultError(this, message || 'peer was killed');
|
293
|
+
this.killedDeferred.reject(error);
|
283
294
|
for (const result of Object.values(this.pendingResults)) {
|
284
295
|
result.reject(error);
|
285
296
|
}
|
package/src/runtime.ts
CHANGED
@@ -20,6 +20,7 @@ import { getDisplayName, getDisplayRoom, getDisplayType, getProvidedNameOrDefaul
|
|
20
20
|
import { IOServer } from './io';
|
21
21
|
import { Level } from './level';
|
22
22
|
import { LogEntry, Logger, makeAlertId } from './logger';
|
23
|
+
import { hasMixinCycle } from './mixin/mixin-cycle';
|
23
24
|
import { PluginDebug } from './plugin/plugin-debug';
|
24
25
|
import { PluginDeviceProxyHandler } from './plugin/plugin-device';
|
25
26
|
import { PluginHost } from './plugin/plugin-host';
|
@@ -812,6 +813,14 @@ export class ScryptedRuntime extends PluginHttp<HttpPluginData> {
|
|
812
813
|
}
|
813
814
|
}
|
814
815
|
|
816
|
+
for (const id of Object.keys(this.stateManager.getSystemState())) {
|
817
|
+
if (hasMixinCycle(this, id)) {
|
818
|
+
console.warn(`initialize: ${id} has a mixin cycle. Clearing mixins.`);
|
819
|
+
const pluginDevice = this.findPluginDeviceById(id);
|
820
|
+
setState(pluginDevice, ScryptedInterfaceProperty.mixins, []);
|
821
|
+
}
|
822
|
+
}
|
823
|
+
|
815
824
|
for await (const plugin of this.datastore.getAll(Plugin)) {
|
816
825
|
try {
|
817
826
|
const pluginDevice = this.findPluginDevice(plugin._id);
|