@scrypted/server 0.115.0 → 0.115.2
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.
- package/deno/deno-plugin-remote.ts +4 -0
- package/dist/cluster/cluster-hash.js +1 -1
- package/dist/cluster/cluster-hash.js.map +1 -1
- package/dist/cluster/connect-rpc-object.d.ts +2 -1
- package/dist/plugin/plugin-api.d.ts +1 -0
- package/dist/plugin/plugin-remote-stats.js +19 -15
- package/dist/plugin/plugin-remote-stats.js.map +1 -1
- package/dist/plugin/plugin-remote-worker.js +90 -54
- package/dist/plugin/plugin-remote-worker.js.map +1 -1
- package/dist/plugin/runtime/deno-worker.d.ts +12 -0
- package/dist/plugin/runtime/deno-worker.js +84 -0
- package/dist/plugin/runtime/deno-worker.js.map +1 -0
- package/dist/plugin/runtime/node-fork-worker.d.ts +1 -1
- package/dist/plugin/runtime/node-fork-worker.js +2 -2
- package/dist/plugin/runtime/node-fork-worker.js.map +1 -1
- package/dist/plugin/runtime/node-thread-worker.d.ts +3 -3
- package/dist/plugin/runtime/node-thread-worker.js +22 -7
- package/dist/plugin/runtime/node-thread-worker.js.map +1 -1
- package/dist/rpc-peer-eval.d.ts +4 -1
- package/dist/rpc-peer-eval.js +4 -10
- package/dist/rpc-peer-eval.js.map +1 -1
- package/dist/rpc.d.ts +4 -1
- package/dist/rpc.js +8 -4
- package/dist/rpc.js.map +1 -1
- package/dist/runtime.js +2 -0
- package/dist/runtime.js.map +1 -1
- package/dist/scrypted-main-exports.js +2 -2
- package/dist/scrypted-main-exports.js.map +1 -1
- package/dist/scrypted-main.js +4 -1
- package/dist/scrypted-main.js.map +1 -1
- package/dist/scrypted-plugin-main.js +14 -4
- package/dist/scrypted-plugin-main.js.map +1 -1
- package/package.json +4 -3
- package/python/plugin_remote.py +423 -221
- package/python/rpc.py +10 -10
- package/src/cluster/cluster-hash.ts +1 -1
- package/src/cluster/connect-rpc-object.ts +2 -1
- package/src/plugin/plugin-api.ts +1 -0
- package/src/plugin/plugin-remote-stats.ts +20 -15
- package/src/plugin/plugin-remote-worker.ts +103 -56
- package/src/plugin/runtime/deno-worker.ts +91 -0
- package/src/plugin/runtime/node-fork-worker.ts +3 -5
- package/src/plugin/runtime/node-thread-worker.ts +22 -6
- package/src/rpc-peer-eval.ts +9 -14
- package/src/rpc.ts +17 -7
- package/src/runtime.ts +2 -0
- package/src/scrypted-main-exports.ts +2 -2
- package/src/scrypted-main.ts +4 -1
- package/src/scrypted-plugin-main.ts +14 -4
package/python/plugin_remote.py
CHANGED
@@ -8,7 +8,6 @@ import multiprocessing
|
|
8
8
|
import multiprocessing.connection
|
9
9
|
import os
|
10
10
|
import platform
|
11
|
-
import shutil
|
12
11
|
import sys
|
13
12
|
import threading
|
14
13
|
import time
|
@@ -39,11 +38,13 @@ ptpython
|
|
39
38
|
wheel
|
40
39
|
""".strip()
|
41
40
|
|
41
|
+
|
42
42
|
class ClusterObject(TypedDict):
|
43
43
|
id: str
|
44
|
+
address: str
|
44
45
|
port: int
|
45
46
|
proxyId: str
|
46
|
-
|
47
|
+
sourceKey: str
|
47
48
|
sha256: str
|
48
49
|
|
49
50
|
|
@@ -60,7 +61,7 @@ class DeviceProxy(object):
|
|
60
61
|
self.device: asyncio.Future[rpc.RpcProxy] = None
|
61
62
|
|
62
63
|
def __getattr__(self, name):
|
63
|
-
if name ==
|
64
|
+
if name == "id":
|
64
65
|
return self.id
|
65
66
|
|
66
67
|
if hasattr(ScryptedInterfaceProperty, name):
|
@@ -70,28 +71,33 @@ class DeviceProxy(object):
|
|
70
71
|
p = state.get(name)
|
71
72
|
if not p:
|
72
73
|
return
|
73
|
-
return p.get(
|
74
|
+
return p.get("value", None)
|
74
75
|
if hasattr(ScryptedInterfaceMethods, name):
|
75
76
|
return rpc.RpcProxyMethod(self, name)
|
76
77
|
|
77
78
|
def __setattr__(self, name: str, value: Any) -> None:
|
78
|
-
if name ==
|
79
|
-
self.__dict__[
|
79
|
+
if name == "__proxy_finalizer_id":
|
80
|
+
self.__dict__["__proxy_entry"]["finalizerId"] = value
|
80
81
|
|
81
82
|
return super().__setattr__(name, value)
|
82
83
|
|
83
84
|
def __apply__(self, method: str, args: list):
|
84
85
|
if not self.device:
|
85
|
-
self.device = asyncio.ensure_future(
|
86
|
+
self.device = asyncio.ensure_future(
|
87
|
+
self.systemManager.api.getDeviceById(self.id)
|
88
|
+
)
|
86
89
|
|
87
90
|
async def apply():
|
88
91
|
device = await self.device
|
89
92
|
return await device.__apply__(method, args)
|
93
|
+
|
90
94
|
return apply()
|
91
95
|
|
92
96
|
|
93
97
|
class SystemManager(scrypted_python.scrypted_sdk.types.SystemManager):
|
94
|
-
def __init__(
|
98
|
+
def __init__(
|
99
|
+
self, api: Any, systemState: Mapping[str, Mapping[str, SystemDeviceState]]
|
100
|
+
) -> None:
|
95
101
|
super().__init__()
|
96
102
|
self.api = api
|
97
103
|
self.systemState = systemState
|
@@ -103,7 +109,9 @@ class SystemManager(scrypted_python.scrypted_sdk.types.SystemManager):
|
|
103
109
|
def getSystemState(self) -> Any:
|
104
110
|
return self.systemState
|
105
111
|
|
106
|
-
def getDeviceById(
|
112
|
+
def getDeviceById(
|
113
|
+
self, idOrPluginId: str, nativeId: str = None
|
114
|
+
) -> scrypted_python.scrypted_sdk.ScryptedDevice:
|
107
115
|
id: str = None
|
108
116
|
if self.systemState.get(idOrPluginId, None):
|
109
117
|
if nativeId is not None:
|
@@ -114,15 +122,15 @@ class SystemManager(scrypted_python.scrypted_sdk.types.SystemManager):
|
|
114
122
|
state = self.systemState.get(check, None)
|
115
123
|
if not state:
|
116
124
|
continue
|
117
|
-
pluginId = state.get(
|
125
|
+
pluginId = state.get("pluginId", None)
|
118
126
|
if not pluginId:
|
119
127
|
continue
|
120
|
-
pluginId = pluginId.get(
|
128
|
+
pluginId = pluginId.get("value", None)
|
121
129
|
if pluginId == idOrPluginId:
|
122
|
-
checkNativeId = state.get(
|
130
|
+
checkNativeId = state.get("nativeId", None)
|
123
131
|
if not checkNativeId:
|
124
132
|
continue
|
125
|
-
checkNativeId = checkNativeId.get(
|
133
|
+
checkNativeId = checkNativeId.get("value", None)
|
126
134
|
if nativeId == checkNativeId:
|
127
135
|
id = idOrPluginId
|
128
136
|
break
|
@@ -140,31 +148,38 @@ class SystemManager(scrypted_python.scrypted_sdk.types.SystemManager):
|
|
140
148
|
state = self.systemState.get(check, None)
|
141
149
|
if not state:
|
142
150
|
continue
|
143
|
-
checkInterfaces = state.get(
|
151
|
+
checkInterfaces = state.get("interfaces", None)
|
144
152
|
if not checkInterfaces:
|
145
153
|
continue
|
146
|
-
interfaces = checkInterfaces.get(
|
154
|
+
interfaces = checkInterfaces.get("value", [])
|
147
155
|
if ScryptedInterface.ScryptedPlugin.value in interfaces:
|
148
|
-
checkPluginId = state.get(
|
156
|
+
checkPluginId = state.get("pluginId", None)
|
149
157
|
if not checkPluginId:
|
150
158
|
continue
|
151
|
-
pluginId = checkPluginId.get(
|
159
|
+
pluginId = checkPluginId.get("value", None)
|
152
160
|
if not pluginId:
|
153
161
|
continue
|
154
162
|
if pluginId == name:
|
155
163
|
return self.getDeviceById(check)
|
156
|
-
checkName = state.get(
|
164
|
+
checkName = state.get("name", None)
|
157
165
|
if not checkName:
|
158
166
|
continue
|
159
|
-
if checkName.get(
|
167
|
+
if checkName.get("value", None) == name:
|
160
168
|
return self.getDeviceById(check)
|
161
169
|
|
162
170
|
# TODO
|
163
|
-
async def listen(
|
171
|
+
async def listen(
|
172
|
+
self, callback: scrypted_python.scrypted_sdk.EventListener
|
173
|
+
) -> scrypted_python.scrypted_sdk.EventListenerRegister:
|
164
174
|
return super().listen(callback)
|
165
175
|
|
166
176
|
# TODO
|
167
|
-
async def listenDevice(
|
177
|
+
async def listenDevice(
|
178
|
+
self,
|
179
|
+
id: str,
|
180
|
+
event: str | scrypted_python.scrypted_sdk.EventListenerOptions,
|
181
|
+
callback: scrypted_python.scrypted_sdk.EventListener,
|
182
|
+
) -> scrypted_python.scrypted_sdk.EventListenerRegister:
|
168
183
|
return super().listenDevice(id, event, callback)
|
169
184
|
|
170
185
|
async def removeDevice(self, id: str) -> None:
|
@@ -179,7 +194,7 @@ class MediaObject(scrypted_python.scrypted_sdk.types.MediaObject):
|
|
179
194
|
setattr(self, rpc.RpcPeer.PROPERTY_PROXY_PROPERTIES, proxyProps)
|
180
195
|
|
181
196
|
options = options or {}
|
182
|
-
options[
|
197
|
+
options["mimeType"] = mimeType
|
183
198
|
|
184
199
|
for key, value in options.items():
|
185
200
|
if rpc.RpcPeer.isTransportSafe(value):
|
@@ -194,38 +209,83 @@ class MediaManager:
|
|
194
209
|
def __init__(self, mediaManager: scrypted_python.scrypted_sdk.types.MediaManager):
|
195
210
|
self.mediaManager = mediaManager
|
196
211
|
|
197
|
-
async def addConverter(
|
212
|
+
async def addConverter(
|
213
|
+
self, converter: scrypted_python.scrypted_sdk.types.BufferConverter
|
214
|
+
) -> None:
|
198
215
|
return await self.mediaManager.addConverter(converter)
|
199
216
|
|
200
217
|
async def clearConverters(self) -> None:
|
201
218
|
return await self.mediaManager.clearConverters()
|
202
219
|
|
203
|
-
async def convertMediaObject(
|
220
|
+
async def convertMediaObject(
|
221
|
+
self,
|
222
|
+
mediaObject: scrypted_python.scrypted_sdk.types.MediaObject,
|
223
|
+
toMimeType: str,
|
224
|
+
) -> Any:
|
204
225
|
return await self.mediaManager.convertMediaObject(mediaObject, toMimeType)
|
205
226
|
|
206
|
-
async def convertMediaObjectToBuffer(
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
227
|
+
async def convertMediaObjectToBuffer(
|
228
|
+
self,
|
229
|
+
mediaObject: scrypted_python.scrypted_sdk.types.MediaObject,
|
230
|
+
toMimeType: str,
|
231
|
+
) -> bytearray:
|
232
|
+
return await self.mediaManager.convertMediaObjectToBuffer(
|
233
|
+
mediaObject, toMimeType
|
234
|
+
)
|
235
|
+
|
236
|
+
async def convertMediaObjectToInsecureLocalUrl(
|
237
|
+
self,
|
238
|
+
mediaObject: str | scrypted_python.scrypted_sdk.types.MediaObject,
|
239
|
+
toMimeType: str,
|
240
|
+
) -> str:
|
241
|
+
return await self.mediaManager.convertMediaObjectToInsecureLocalUrl(
|
242
|
+
mediaObject, toMimeType
|
243
|
+
)
|
244
|
+
|
245
|
+
async def convertMediaObjectToJSON(
|
246
|
+
self,
|
247
|
+
mediaObject: scrypted_python.scrypted_sdk.types.MediaObject,
|
248
|
+
toMimeType: str,
|
249
|
+
) -> Any:
|
213
250
|
return await self.mediaManager.convertMediaObjectToJSON(mediaObject, toMimeType)
|
214
251
|
|
215
|
-
async def convertMediaObjectToLocalUrl(
|
216
|
-
|
217
|
-
|
218
|
-
|
252
|
+
async def convertMediaObjectToLocalUrl(
|
253
|
+
self,
|
254
|
+
mediaObject: str | scrypted_python.scrypted_sdk.types.MediaObject,
|
255
|
+
toMimeType: str,
|
256
|
+
) -> str:
|
257
|
+
return await self.mediaManager.convertMediaObjectToLocalUrl(
|
258
|
+
mediaObject, toMimeType
|
259
|
+
)
|
260
|
+
|
261
|
+
async def convertMediaObjectToUrl(
|
262
|
+
self,
|
263
|
+
mediaObject: str | scrypted_python.scrypted_sdk.types.MediaObject,
|
264
|
+
toMimeType: str,
|
265
|
+
) -> str:
|
219
266
|
return await self.mediaManager.convertMediaObjectToUrl(mediaObject, toMimeType)
|
220
267
|
|
221
|
-
async def createFFmpegMediaObject(
|
268
|
+
async def createFFmpegMediaObject(
|
269
|
+
self,
|
270
|
+
ffmpegInput: scrypted_python.scrypted_sdk.types.FFmpegInput,
|
271
|
+
options: scrypted_python.scrypted_sdk.types.MediaObjectOptions = None,
|
272
|
+
) -> scrypted_python.scrypted_sdk.types.MediaObject:
|
222
273
|
return await self.mediaManager.createFFmpegMediaObject(ffmpegInput, options)
|
223
274
|
|
224
|
-
async def createMediaObject(
|
275
|
+
async def createMediaObject(
|
276
|
+
self,
|
277
|
+
data: Any,
|
278
|
+
mimeType: str,
|
279
|
+
options: scrypted_python.scrypted_sdk.types.MediaObjectOptions = None,
|
280
|
+
) -> scrypted_python.scrypted_sdk.types.MediaObject:
|
225
281
|
# return await self.createMediaObject(data, mimetypes, options)
|
226
282
|
return MediaObject(data, mimeType, options)
|
227
283
|
|
228
|
-
async def createMediaObjectFromUrl(
|
284
|
+
async def createMediaObjectFromUrl(
|
285
|
+
self,
|
286
|
+
data: str,
|
287
|
+
options: scrypted_python.scrypted_sdk.types.MediaObjectOptions = None,
|
288
|
+
) -> scrypted_python.scrypted_sdk.types.MediaObject:
|
229
289
|
return await self.mediaManager.createMediaObjectFromUrl(data, options)
|
230
290
|
|
231
291
|
async def getFFmpegPath(self) -> str:
|
@@ -236,7 +296,13 @@ class MediaManager:
|
|
236
296
|
|
237
297
|
|
238
298
|
class DeviceState(scrypted_python.scrypted_sdk.types.DeviceState):
|
239
|
-
def __init__(
|
299
|
+
def __init__(
|
300
|
+
self,
|
301
|
+
id: str,
|
302
|
+
nativeId: str,
|
303
|
+
systemManager: SystemManager,
|
304
|
+
deviceManager: scrypted_python.scrypted_sdk.types.DeviceManager,
|
305
|
+
) -> None:
|
240
306
|
super().__init__()
|
241
307
|
self._id = id
|
242
308
|
self.nativeId = nativeId
|
@@ -253,7 +319,7 @@ class DeviceState(scrypted_python.scrypted_sdk.types.DeviceState):
|
|
253
319
|
sdd = deviceState.get(property, None)
|
254
320
|
if not sdd:
|
255
321
|
return None
|
256
|
-
return sdd.get(
|
322
|
+
return sdd.get("value", None)
|
257
323
|
|
258
324
|
def setScryptedProperty(self, property: str, value: Any):
|
259
325
|
if property == ScryptedInterfaceProperty.id.value:
|
@@ -262,18 +328,26 @@ class DeviceState(scrypted_python.scrypted_sdk.types.DeviceState):
|
|
262
328
|
raise Exception("mixins is read only")
|
263
329
|
if property == ScryptedInterfaceProperty.interfaces.value:
|
264
330
|
raise Exception(
|
265
|
-
"interfaces is a read only post-mixin computed property, use providedInterfaces"
|
331
|
+
"interfaces is a read only post-mixin computed property, use providedInterfaces"
|
332
|
+
)
|
266
333
|
|
267
334
|
now = int(time.time() * 1000)
|
268
335
|
self.systemManager.systemState[self._id][property] = {
|
269
336
|
"lastEventTime": now,
|
270
337
|
"stateTime": now,
|
271
|
-
"value": value
|
338
|
+
"value": value,
|
272
339
|
}
|
273
340
|
|
274
341
|
self.systemManager.api.setState(self.nativeId, property, value)
|
275
342
|
|
276
343
|
|
344
|
+
class WritableDeviceState(scrypted_python.scrypted_sdk.types.WritableDeviceState):
|
345
|
+
|
346
|
+
def __init__(self, id, setState) -> None:
|
347
|
+
self.id = id
|
348
|
+
self.setState = setState
|
349
|
+
|
350
|
+
|
277
351
|
class DeviceStorage(Storage):
|
278
352
|
id: str
|
279
353
|
nativeId: str
|
@@ -304,7 +378,9 @@ class DeviceStorage(Storage):
|
|
304
378
|
|
305
379
|
|
306
380
|
class DeviceManager(scrypted_python.scrypted_sdk.types.DeviceManager):
|
307
|
-
def __init__(
|
381
|
+
def __init__(
|
382
|
+
self, nativeIds: Mapping[str, DeviceStorage], systemManager: SystemManager
|
383
|
+
) -> None:
|
308
384
|
super().__init__()
|
309
385
|
self.nativeIds = nativeIds
|
310
386
|
self.systemManager = systemManager
|
@@ -313,7 +389,9 @@ class DeviceManager(scrypted_python.scrypted_sdk.types.DeviceManager):
|
|
313
389
|
id = self.nativeIds[nativeId].id
|
314
390
|
return DeviceState(id, nativeId, self.systemManager, self)
|
315
391
|
|
316
|
-
async def onDeviceEvent(
|
392
|
+
async def onDeviceEvent(
|
393
|
+
self, nativeId: str, eventInterface: str, eventData: Any = None
|
394
|
+
) -> None:
|
317
395
|
await self.systemManager.api.onDeviceEvent(nativeId, eventInterface, eventData)
|
318
396
|
|
319
397
|
async def onDevicesChanged(self, devices: DeviceManifest) -> None:
|
@@ -325,8 +403,12 @@ class DeviceManager(scrypted_python.scrypted_sdk.types.DeviceManager):
|
|
325
403
|
async def onDeviceRemoved(self, nativeId: str) -> None:
|
326
404
|
return await self.systemManager.api.onDeviceRemoved(nativeId)
|
327
405
|
|
328
|
-
async def onMixinEvent(
|
329
|
-
|
406
|
+
async def onMixinEvent(
|
407
|
+
self, id: str, mixinDevice: Any, eventInterface: str, eventData: Any
|
408
|
+
) -> None:
|
409
|
+
return await self.systemManager.api.onMixinEvent(
|
410
|
+
id, mixinDevice, eventInterface, eventData
|
411
|
+
)
|
330
412
|
|
331
413
|
async def requestRestart(self) -> None:
|
332
414
|
return await self.systemManager.api.requestRestart()
|
@@ -336,7 +418,9 @@ class DeviceManager(scrypted_python.scrypted_sdk.types.DeviceManager):
|
|
336
418
|
|
337
419
|
|
338
420
|
class PluginRemote:
|
339
|
-
def __init__(
|
421
|
+
def __init__(
|
422
|
+
self, peer: rpc.RpcPeer, api, pluginId: str, hostInfo, loop: AbstractEventLoop
|
423
|
+
):
|
340
424
|
self.systemState: Mapping[str, Mapping[str, SystemDeviceState]] = {}
|
341
425
|
self.nativeIds: Mapping[str, DeviceStorage] = {}
|
342
426
|
self.mediaManager: MediaManager
|
@@ -349,199 +433,267 @@ class PluginRemote:
|
|
349
433
|
self.hostInfo = hostInfo
|
350
434
|
self.loop = loop
|
351
435
|
self.replPort = None
|
352
|
-
self.__dict__[
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
436
|
+
self.__dict__["__proxy_oneway_methods"] = [
|
437
|
+
"notify",
|
438
|
+
"updateDeviceState",
|
439
|
+
"setSystemState",
|
440
|
+
"ioEvent",
|
441
|
+
"setNativeId",
|
358
442
|
]
|
359
443
|
|
360
|
-
async def print_async(
|
361
|
-
|
362
|
-
|
444
|
+
async def print_async(
|
445
|
+
self,
|
446
|
+
nativeId: str,
|
447
|
+
*values: object,
|
448
|
+
sep: Optional[str] = " ",
|
449
|
+
end: Optional[str] = "\n",
|
450
|
+
flush: bool = False,
|
451
|
+
):
|
363
452
|
consoleFuture = self.consoles.get(nativeId)
|
364
453
|
if not consoleFuture:
|
365
454
|
consoleFuture = Future()
|
366
455
|
self.consoles[nativeId] = consoleFuture
|
367
|
-
plugins = await self.api.getComponent(
|
368
|
-
port = await plugins.getRemoteServicePort(self.pluginId,
|
456
|
+
plugins = await self.api.getComponent("plugins")
|
457
|
+
port = await plugins.getRemoteServicePort(self.pluginId, "console-writer")
|
369
458
|
connection = await asyncio.open_connection(port=port)
|
370
459
|
_, writer = connection
|
371
460
|
if not nativeId:
|
372
|
-
nid =
|
461
|
+
nid = "undefined"
|
373
462
|
else:
|
374
463
|
nid = nativeId
|
375
|
-
nid +=
|
376
|
-
writer.write(nid.encode(
|
464
|
+
nid += "\n"
|
465
|
+
writer.write(nid.encode("utf8"))
|
377
466
|
consoleFuture.set_result(connection)
|
378
467
|
_, writer = await consoleFuture
|
379
468
|
strio = StringIO()
|
380
469
|
print(*values, sep=sep, end=end, flush=flush, file=strio)
|
381
470
|
strio.seek(0)
|
382
|
-
b = strio.read().encode(
|
471
|
+
b = strio.read().encode("utf8")
|
383
472
|
writer.write(b)
|
384
473
|
|
385
|
-
def print(
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
474
|
+
def print(
|
475
|
+
self,
|
476
|
+
nativeId: str,
|
477
|
+
*values: object,
|
478
|
+
sep: Optional[str] = " ",
|
479
|
+
end: Optional[str] = "\n",
|
480
|
+
flush: bool = False,
|
481
|
+
):
|
482
|
+
asyncio.run_coroutine_threadsafe(
|
483
|
+
self.print_async(nativeId, *values, sep=sep, end=end, flush=flush),
|
484
|
+
self.loop,
|
485
|
+
)
|
390
486
|
|
391
487
|
async def loadZip(self, packageJson, getZip: Any, options: dict):
|
392
488
|
try:
|
393
489
|
return await self.loadZipWrapped(packageJson, getZip, options)
|
394
490
|
except:
|
395
|
-
print(
|
491
|
+
print("plugin start/fork failed")
|
396
492
|
traceback.print_exc()
|
397
493
|
raise
|
398
494
|
|
399
495
|
async def loadZipWrapped(self, packageJson, getZip: Any, options: dict):
|
400
496
|
sdk = ScryptedStatic()
|
401
497
|
|
402
|
-
clusterId = options[
|
403
|
-
clusterSecret = options[
|
498
|
+
clusterId = options["clusterId"]
|
499
|
+
clusterSecret = options["clusterSecret"]
|
500
|
+
SCRYPTED_CLUSTER_ADDRESS = os.environ.get("SCRYPTED_CLUSTER_ADDRESS", None)
|
404
501
|
|
405
502
|
def computeClusterObjectHash(o: ClusterObject) -> str:
|
406
503
|
m = hashlib.sha256()
|
407
|
-
m.update(
|
408
|
-
|
409
|
-
|
410
|
-
|
504
|
+
m.update(
|
505
|
+
bytes(
|
506
|
+
f"{o['id']}{o.get('address') or ''}{o['port']}{o.get('sourceKey', None) or ''}{o['proxyId']}{clusterSecret}",
|
507
|
+
"utf8",
|
508
|
+
)
|
509
|
+
)
|
510
|
+
return base64.b64encode(m.digest()).decode("utf-8")
|
511
|
+
|
512
|
+
def onProxySerialization(value: Any, sourceKey: str = None):
|
411
513
|
properties: dict = rpc.RpcPeer.prepareProxyProperties(value) or {}
|
412
|
-
clusterEntry = properties.get(
|
413
|
-
|
514
|
+
clusterEntry = properties.get("__cluster", None)
|
515
|
+
proxyId: str = (
|
516
|
+
clusterEntry and clusterEntry.get("proxyId", None)
|
517
|
+
) or rpc.RpcPeer.generateId()
|
518
|
+
|
519
|
+
if (
|
520
|
+
clusterEntry
|
521
|
+
and clusterPort == clusterEntry["port"]
|
522
|
+
and sourceKey != clusterEntry.get("sourceKey", None)
|
523
|
+
):
|
524
|
+
clusterEntry = None
|
525
|
+
|
526
|
+
if not clusterEntry:
|
414
527
|
clusterEntry: ClusterObject = {
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
528
|
+
"id": clusterId,
|
529
|
+
"proxyId": proxyId,
|
530
|
+
"address": SCRYPTED_CLUSTER_ADDRESS,
|
531
|
+
"port": clusterPort,
|
532
|
+
"sourceKey": sourceKey,
|
419
533
|
}
|
420
|
-
clusterEntry[
|
421
|
-
properties[
|
534
|
+
clusterEntry["sha256"] = computeClusterObjectHash(clusterEntry)
|
535
|
+
properties["__cluster"] = clusterEntry
|
422
536
|
|
423
|
-
return properties
|
537
|
+
return proxyId, properties
|
424
538
|
|
425
539
|
self.peer.onProxySerialization = onProxySerialization
|
426
540
|
|
427
|
-
async def resolveObject(id: str,
|
428
|
-
sourcePeer: rpc.RpcPeer =
|
541
|
+
async def resolveObject(id: str, sourceKey: str):
|
542
|
+
sourcePeer: rpc.RpcPeer = (
|
543
|
+
self.peer
|
544
|
+
if not sourceKey
|
545
|
+
else await rpc.maybe_await(clusterPeers.get(sourceKey, None))
|
546
|
+
)
|
429
547
|
if not sourcePeer:
|
430
548
|
return
|
431
549
|
return sourcePeer.localProxyMap.get(id, None)
|
432
550
|
|
433
|
-
clusterPeers: Mapping[
|
551
|
+
clusterPeers: Mapping[str, asyncio.Future[rpc.RpcPeer]] = {}
|
434
552
|
|
435
|
-
|
436
|
-
|
553
|
+
def getClusterPeerKey(address: str, port: int):
|
554
|
+
return f"{address}:{port}"
|
555
|
+
|
556
|
+
async def handleClusterClient(
|
557
|
+
reader: asyncio.StreamReader, writer: asyncio.StreamWriter
|
558
|
+
):
|
559
|
+
clusterPeerAddress, clusterPeerPort = writer.get_extra_info("peername")
|
560
|
+
clusterPeerKey = getClusterPeerKey(clusterPeerAddress, clusterPeerPort)
|
437
561
|
rpcTransport = rpc_reader.RpcStreamTransport(reader, writer)
|
438
562
|
peer: rpc.RpcPeer
|
439
|
-
peer, peerReadLoop = await rpc_reader.prepare_peer_readloop(
|
440
|
-
|
441
|
-
|
563
|
+
peer, peerReadLoop = await rpc_reader.prepare_peer_readloop(
|
564
|
+
self.loop, rpcTransport
|
565
|
+
)
|
566
|
+
peer.onProxySerialization = lambda value: onProxySerialization(
|
567
|
+
value, clusterPeerPort
|
568
|
+
)
|
442
569
|
future: asyncio.Future[rpc.RpcPeer] = asyncio.Future()
|
443
570
|
future.set_result(peer)
|
444
|
-
clusterPeers[
|
571
|
+
clusterPeers[clusterPeerKey] = future
|
445
572
|
|
446
573
|
async def connectRPCObject(o: ClusterObject):
|
447
574
|
sha256 = computeClusterObjectHash(o)
|
448
|
-
if sha256 != o[
|
449
|
-
raise Exception(
|
450
|
-
return await resolveObject(o[
|
575
|
+
if sha256 != o["sha256"]:
|
576
|
+
raise Exception("secret incorrect")
|
577
|
+
return await resolveObject(o["proxyId"], o.get("sourceKey", None))
|
451
578
|
|
452
|
-
peer.params[
|
579
|
+
peer.params["connectRPCObject"] = connectRPCObject
|
453
580
|
try:
|
454
581
|
await peerReadLoop()
|
455
582
|
except:
|
456
583
|
pass
|
457
584
|
finally:
|
458
|
-
clusterPeers.pop(
|
459
|
-
peer.kill(
|
585
|
+
clusterPeers.pop(clusterPeerKey)
|
586
|
+
peer.kill("cluster client killed")
|
460
587
|
writer.close()
|
461
588
|
|
462
|
-
|
589
|
+
listenAddress = "0.0.0.0" if SCRYPTED_CLUSTER_ADDRESS else "127.0.0.1"
|
590
|
+
clusterRpcServer = await asyncio.start_server(
|
591
|
+
handleClusterClient, listenAddress, 0
|
592
|
+
)
|
463
593
|
clusterPort = clusterRpcServer.sockets[0].getsockname()[1]
|
464
594
|
|
465
|
-
def ensureClusterPeer(port: int):
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
595
|
+
def ensureClusterPeer(address: str, port: int):
|
596
|
+
if not address or address == SCRYPTED_CLUSTER_ADDRESS:
|
597
|
+
address = "127.0.0.1"
|
598
|
+
clusterPeerKey = getClusterPeerKey(address, port)
|
599
|
+
clusterPeerPromise = clusterPeers.get(clusterPeerKey)
|
600
|
+
if clusterPeerPromise:
|
601
|
+
return clusterPeerPromise
|
602
|
+
|
603
|
+
async def connectClusterPeer():
|
604
|
+
reader, writer = await asyncio.open_connection(address, port)
|
605
|
+
sourceAddress, sourcePort = writer.get_extra_info("sockname")
|
606
|
+
if (
|
607
|
+
sourceAddress != SCRYPTED_CLUSTER_ADDRESS
|
608
|
+
and sourceAddress != "127.0.0.1"
|
609
|
+
):
|
610
|
+
print("source address mismatch", sourceAddress)
|
611
|
+
sourceKey = getClusterPeerKey(sourceAddress, sourcePort)
|
612
|
+
rpcTransport = rpc_reader.RpcStreamTransport(reader, writer)
|
613
|
+
clusterPeer, peerReadLoop = await rpc_reader.prepare_peer_readloop(
|
614
|
+
self.loop, rpcTransport
|
615
|
+
)
|
616
|
+
clusterPeer.onProxySerialization = lambda value: onProxySerialization(
|
617
|
+
value, sourceKey
|
618
|
+
)
|
619
|
+
|
620
|
+
async def run_loop():
|
621
|
+
try:
|
622
|
+
await peerReadLoop()
|
623
|
+
except:
|
624
|
+
pass
|
625
|
+
finally:
|
626
|
+
clusterPeers.pop(clusterPeerKey)
|
627
|
+
|
628
|
+
asyncio.run_coroutine_threadsafe(run_loop(), self.loop)
|
629
|
+
return clusterPeer
|
630
|
+
|
631
|
+
clusterPeerPromise = self.loop.create_task(connectClusterPeer())
|
632
|
+
|
633
|
+
clusterPeers[clusterPeerKey] = clusterPeerPromise
|
491
634
|
return clusterPeerPromise
|
492
635
|
|
493
636
|
async def connectRPCObject(value):
|
494
|
-
|
495
|
-
if type(
|
637
|
+
__cluster = getattr(value, "__cluster")
|
638
|
+
if type(__cluster) is not dict:
|
496
639
|
return value
|
497
640
|
|
498
|
-
|
641
|
+
clusterObject: ClusterObject = __cluster
|
642
|
+
|
643
|
+
if clusterObject.get("id", None) != clusterId:
|
499
644
|
return value
|
500
645
|
|
501
|
-
|
502
|
-
|
503
|
-
|
646
|
+
address = clusterObject.get("address", None)
|
647
|
+
port = clusterObject["port"]
|
648
|
+
proxyId = clusterObject["proxyId"]
|
504
649
|
if port == clusterPort:
|
505
|
-
return await resolveObject(
|
650
|
+
return await resolveObject(
|
651
|
+
proxyId, clusterObject.get("sourceKey", None)
|
652
|
+
)
|
506
653
|
|
507
|
-
clusterPeerPromise = ensureClusterPeer(port)
|
654
|
+
clusterPeerPromise = ensureClusterPeer(address, port)
|
508
655
|
|
509
656
|
try:
|
510
657
|
clusterPeer = await clusterPeerPromise
|
511
|
-
|
512
|
-
|
513
|
-
|
658
|
+
weakref = clusterPeer.remoteWeakProxies.get(proxyId, None)
|
659
|
+
existing = weakref() if weakref else None
|
660
|
+
if existing:
|
661
|
+
return existing
|
662
|
+
|
663
|
+
peerConnectRPCObject = clusterPeer.tags.get("connectRPCObject")
|
514
664
|
if not peerConnectRPCObject:
|
515
|
-
peerConnectRPCObject = await clusterPeer.getParam(
|
516
|
-
|
665
|
+
peerConnectRPCObject = await clusterPeer.getParam(
|
666
|
+
"connectRPCObject"
|
667
|
+
)
|
668
|
+
clusterPeer.tags["connectRPCObject"] = peerConnectRPCObject
|
517
669
|
newValue = await peerConnectRPCObject(clusterObject)
|
518
670
|
if not newValue:
|
519
|
-
raise Exception(
|
671
|
+
raise Exception("rpc object not found?")
|
520
672
|
return newValue
|
521
673
|
except Exception as e:
|
522
674
|
return value
|
523
675
|
|
524
676
|
sdk.connectRPCObject = connectRPCObject
|
525
677
|
|
526
|
-
forkMain = options and options.get(
|
527
|
-
debug = options.get(
|
678
|
+
forkMain = options and options.get("fork")
|
679
|
+
debug = options.get("debug", None)
|
528
680
|
plugin_volume = pv.ensure_plugin_volume(self.pluginId)
|
529
|
-
plugin_zip_paths = pv.prep(plugin_volume, options.get(
|
681
|
+
plugin_zip_paths = pv.prep(plugin_volume, options.get("zipHash"))
|
530
682
|
|
531
683
|
if debug:
|
532
684
|
scrypted_volume = pv.get_scrypted_volume()
|
533
685
|
# python debugger needs a predictable path for the plugin.zip,
|
534
686
|
# as the vscode python extension doesn't seem to have a way
|
535
687
|
# to read the package.json to configure the python remoteRoot.
|
536
|
-
zipPath = os.path.join(scrypted_volume,
|
688
|
+
zipPath = os.path.join(scrypted_volume, "plugin.zip")
|
537
689
|
else:
|
538
|
-
zipPath = plugin_zip_paths.get(
|
690
|
+
zipPath = plugin_zip_paths.get("zip_file")
|
539
691
|
|
540
692
|
if not os.path.exists(zipPath) or debug:
|
541
693
|
os.makedirs(os.path.dirname(zipPath), exist_ok=True)
|
542
694
|
zipData = await getZip()
|
543
|
-
zipPathTmp = zipPath +
|
544
|
-
with open(zipPathTmp,
|
695
|
+
zipPathTmp = zipPath + ".tmp"
|
696
|
+
with open(zipPathTmp, "wb") as f:
|
545
697
|
f.write(zipData)
|
546
698
|
try:
|
547
699
|
os.remove(zipPath)
|
@@ -552,69 +704,98 @@ class PluginRemote:
|
|
552
704
|
zip = zipfile.ZipFile(zipPath)
|
553
705
|
|
554
706
|
if not forkMain:
|
555
|
-
multiprocessing.set_start_method(
|
707
|
+
multiprocessing.set_start_method("spawn")
|
556
708
|
|
557
709
|
# it's possible to run 32bit docker on aarch64, which cause pip requirements
|
558
710
|
# to fail because pip only allows filtering on machine, even if running a different architeture.
|
559
711
|
# this will cause prebuilt wheel installation to fail.
|
560
|
-
if
|
561
|
-
|
712
|
+
if (
|
713
|
+
platform.machine() == "aarch64"
|
714
|
+
and platform.architecture()[0] == "32bit"
|
715
|
+
):
|
716
|
+
print("=============================================")
|
562
717
|
print(
|
563
|
-
|
718
|
+
"Python machine vs architecture mismatch detected. Plugin installation may fail."
|
719
|
+
)
|
564
720
|
print(
|
565
|
-
|
721
|
+
"This issue occurs if a 32bit system was upgraded to a 64bit kernel."
|
722
|
+
)
|
566
723
|
print(
|
567
|
-
|
568
|
-
|
569
|
-
print(
|
724
|
+
"Reverting to the 32bit kernel (or reflashing as native 64 bit is recommended."
|
725
|
+
)
|
726
|
+
print("https://github.com/koush/scrypted/issues/678")
|
727
|
+
print("=============================================")
|
570
728
|
|
571
|
-
python_version =
|
572
|
-
sys.version_info[0])+"."+str(sys.version_info[1])
|
573
|
-
|
574
|
-
print(
|
729
|
+
python_version = (
|
730
|
+
"python%s" % str(sys.version_info[0]) + "." + str(sys.version_info[1])
|
731
|
+
)
|
732
|
+
print("python version:", python_version)
|
733
|
+
print("interpreter:", sys.executable)
|
575
734
|
|
576
|
-
python_versioned_directory =
|
577
|
-
python_version,
|
578
|
-
|
579
|
-
|
735
|
+
python_versioned_directory = "%s-%s-%s" % (
|
736
|
+
python_version,
|
737
|
+
platform.system(),
|
738
|
+
platform.machine(),
|
739
|
+
)
|
740
|
+
SCRYPTED_PYTHON_VERSION = os.environ.get("SCRYPTED_PYTHON_VERSION")
|
741
|
+
python_versioned_directory += "-" + SCRYPTED_PYTHON_VERSION
|
580
742
|
|
581
|
-
pip_target = os.path.join(
|
582
|
-
plugin_volume, python_versioned_directory)
|
743
|
+
pip_target = os.path.join(plugin_volume, python_versioned_directory)
|
583
744
|
|
584
|
-
print(
|
745
|
+
print("pip target: %s" % pip_target)
|
585
746
|
|
586
747
|
if not os.path.exists(pip_target):
|
587
748
|
os.makedirs(pip_target, exist_ok=True)
|
588
749
|
|
589
|
-
|
590
750
|
def read_requirements(filename: str) -> str:
|
591
751
|
if filename in zip.namelist():
|
592
|
-
return zip.open(filename).read().decode(
|
593
|
-
return
|
752
|
+
return zip.open(filename).read().decode("utf8")
|
753
|
+
return ""
|
594
754
|
|
595
|
-
str_requirements = read_requirements(
|
596
|
-
str_optional_requirements = read_requirements(
|
755
|
+
str_requirements = read_requirements("requirements.txt")
|
756
|
+
str_optional_requirements = read_requirements("requirements.optional.txt")
|
597
757
|
|
598
758
|
scrypted_requirements_basename = os.path.join(
|
599
|
-
pip_target,
|
600
|
-
|
601
|
-
|
759
|
+
pip_target, "requirements.scrypted"
|
760
|
+
)
|
761
|
+
requirements_basename = os.path.join(pip_target, "requirements")
|
602
762
|
optional_requirements_basename = os.path.join(
|
603
|
-
pip_target,
|
763
|
+
pip_target, "requirements.optional"
|
764
|
+
)
|
604
765
|
|
605
766
|
need_pip = True
|
606
767
|
if str_requirements:
|
607
768
|
need_pip = need_requirements(requirements_basename, str_requirements)
|
608
769
|
if not need_pip:
|
609
|
-
need_pip = need_requirements(
|
770
|
+
need_pip = need_requirements(
|
771
|
+
scrypted_requirements_basename, SCRYPTED_REQUIREMENTS
|
772
|
+
)
|
610
773
|
|
611
774
|
if need_pip:
|
612
775
|
remove_pip_dirs(plugin_volume)
|
613
|
-
install_with_pip(
|
614
|
-
|
615
|
-
|
776
|
+
install_with_pip(
|
777
|
+
pip_target,
|
778
|
+
packageJson,
|
779
|
+
SCRYPTED_REQUIREMENTS,
|
780
|
+
scrypted_requirements_basename,
|
781
|
+
ignore_error=True,
|
782
|
+
)
|
783
|
+
install_with_pip(
|
784
|
+
pip_target,
|
785
|
+
packageJson,
|
786
|
+
str_requirements,
|
787
|
+
requirements_basename,
|
788
|
+
ignore_error=False,
|
789
|
+
)
|
790
|
+
install_with_pip(
|
791
|
+
pip_target,
|
792
|
+
packageJson,
|
793
|
+
str_optional_requirements,
|
794
|
+
optional_requirements_basename,
|
795
|
+
ignore_error=True,
|
796
|
+
)
|
616
797
|
else:
|
617
|
-
print(
|
798
|
+
print("requirements.txt (up to date)")
|
618
799
|
print(str_requirements)
|
619
800
|
|
620
801
|
sys.path.insert(0, zipPath)
|
@@ -639,9 +820,10 @@ class PluginRemote:
|
|
639
820
|
def host_fork() -> PluginFork:
|
640
821
|
parent_conn, child_conn = multiprocessing.Pipe()
|
641
822
|
pluginFork = PluginFork()
|
642
|
-
print(
|
823
|
+
print("new fork")
|
643
824
|
pluginFork.worker = multiprocessing.Process(
|
644
|
-
target=plugin_fork, args=(child_conn,), daemon=True
|
825
|
+
target=plugin_fork, args=(child_conn,), daemon=True
|
826
|
+
)
|
645
827
|
pluginFork.worker.start()
|
646
828
|
|
647
829
|
def schedule_exit_check():
|
@@ -650,42 +832,47 @@ class PluginRemote:
|
|
650
832
|
pluginFork.worker.join()
|
651
833
|
else:
|
652
834
|
schedule_exit_check()
|
835
|
+
|
653
836
|
self.loop.call_later(2, exit_check)
|
654
837
|
|
655
838
|
schedule_exit_check()
|
656
839
|
|
657
840
|
async def getFork():
|
658
|
-
rpcTransport = rpc_reader.RpcConnectionTransport(
|
659
|
-
|
660
|
-
|
661
|
-
|
841
|
+
rpcTransport = rpc_reader.RpcConnectionTransport(parent_conn)
|
842
|
+
forkPeer, readLoop = await rpc_reader.prepare_peer_readloop(
|
843
|
+
self.loop, rpcTransport
|
844
|
+
)
|
845
|
+
forkPeer.peerName = "thread"
|
662
846
|
|
663
847
|
async def updateStats(stats):
|
664
|
-
self.ptimeSum += stats[
|
848
|
+
self.ptimeSum += stats["cpu"]["user"]
|
665
849
|
self.allMemoryStats[forkPeer] = stats
|
666
|
-
|
850
|
+
|
851
|
+
forkPeer.params["updateStats"] = updateStats
|
667
852
|
|
668
853
|
async def forkReadLoop():
|
669
854
|
try:
|
670
855
|
await readLoop()
|
671
856
|
except:
|
672
857
|
# traceback.print_exc()
|
673
|
-
print(
|
858
|
+
print("fork read loop exited")
|
674
859
|
finally:
|
675
860
|
self.allMemoryStats.pop(forkPeer)
|
676
861
|
parent_conn.close()
|
677
862
|
rpcTransport.executor.shutdown()
|
678
863
|
pluginFork.worker.kill()
|
679
|
-
|
680
|
-
|
681
|
-
getRemote = await forkPeer.getParam(
|
682
|
-
remote: PluginRemote = await getRemote(
|
864
|
+
|
865
|
+
asyncio.run_coroutine_threadsafe(forkReadLoop(), loop=self.loop)
|
866
|
+
getRemote = await forkPeer.getParam("getRemote")
|
867
|
+
remote: PluginRemote = await getRemote(
|
868
|
+
self.api, self.pluginId, self.hostInfo
|
869
|
+
)
|
683
870
|
await remote.setSystemState(self.systemManager.getSystemState())
|
684
871
|
for nativeId, ds in self.nativeIds.items():
|
685
872
|
await remote.setNativeId(nativeId, ds.id, ds.storage)
|
686
873
|
forkOptions = options.copy()
|
687
|
-
forkOptions[
|
688
|
-
forkOptions[
|
874
|
+
forkOptions["fork"] = True
|
875
|
+
forkOptions["debug"] = debug
|
689
876
|
return await remote.loadZip(packageJson, getZip, forkOptions)
|
690
877
|
|
691
878
|
pluginFork.result = asyncio.create_task(getFork())
|
@@ -697,14 +884,18 @@ class PluginRemote:
|
|
697
884
|
sdk_init2(sdk)
|
698
885
|
except:
|
699
886
|
from scrypted_sdk import sdk_init # type: ignore
|
700
|
-
|
701
|
-
|
887
|
+
|
888
|
+
sdk_init(
|
889
|
+
zip, self, self.systemManager, self.deviceManager, self.mediaManager
|
890
|
+
)
|
702
891
|
|
703
892
|
if not forkMain:
|
704
893
|
from main import create_scrypted_plugin # type: ignore
|
894
|
+
|
705
895
|
pluginInstance = await rpc.maybe_await(create_scrypted_plugin())
|
706
896
|
try:
|
707
897
|
from plugin_repl import createREPLServer
|
898
|
+
|
708
899
|
self.replPort = await createREPLServer(sdk, pluginInstance)
|
709
900
|
except Exception as e:
|
710
901
|
print(f"Warning: Python REPL cannot be loaded: {e}")
|
@@ -712,6 +903,7 @@ class PluginRemote:
|
|
712
903
|
return pluginInstance
|
713
904
|
|
714
905
|
from main import fork # type: ignore
|
906
|
+
|
715
907
|
forked = await rpc.maybe_await(fork())
|
716
908
|
if type(forked) == dict:
|
717
909
|
forked[rpc.RpcPeer.PROPERTY_JSON_COPY_SERIALIZE_CHILDREN] = True
|
@@ -739,13 +931,13 @@ class PluginRemote:
|
|
739
931
|
self.systemState[id] = state
|
740
932
|
|
741
933
|
async def notify(self, id, eventDetails: EventDetails, value):
|
742
|
-
property = eventDetails.get(
|
934
|
+
property = eventDetails.get("property")
|
743
935
|
if property:
|
744
936
|
state = None
|
745
937
|
if self.systemState:
|
746
938
|
state = self.systemState.get(id, None)
|
747
939
|
if not state:
|
748
|
-
print(
|
940
|
+
print("state not found for %s" % id)
|
749
941
|
return
|
750
942
|
state[property] = value
|
751
943
|
# systemManager.events.notify(id, eventTime, eventInterface, property, value.value, changed);
|
@@ -757,54 +949,57 @@ class PluginRemote:
|
|
757
949
|
pass
|
758
950
|
|
759
951
|
async def createDeviceState(self, id, setState):
|
760
|
-
|
952
|
+
return WritableDeviceState(id, setState)
|
761
953
|
|
762
954
|
async def getServicePort(self, name):
|
763
955
|
if name == "repl":
|
764
956
|
if self.replPort is None:
|
765
|
-
raise Exception(
|
957
|
+
raise Exception("REPL unavailable: Plugin not loaded.")
|
766
958
|
if self.replPort == 0:
|
767
|
-
raise Exception(
|
959
|
+
raise Exception("REPL unavailable: Python REPL not available.")
|
768
960
|
return self.replPort
|
769
|
-
raise Exception(f
|
961
|
+
raise Exception(f"unknown service {name}")
|
770
962
|
|
771
963
|
async def start_stats_runner(self):
|
772
964
|
pong = None
|
965
|
+
|
773
966
|
async def ping(time: int):
|
774
967
|
nonlocal pong
|
775
|
-
pong = pong or await self.peer.getParam(
|
968
|
+
pong = pong or await self.peer.getParam("pong")
|
776
969
|
await pong(time)
|
777
|
-
self.peer.params['ping'] = ping
|
778
970
|
|
779
|
-
|
971
|
+
self.peer.params["ping"] = ping
|
972
|
+
|
973
|
+
update_stats = await self.peer.getParam("updateStats")
|
780
974
|
if not update_stats:
|
781
|
-
print(
|
975
|
+
print("host did not provide update_stats")
|
782
976
|
return
|
783
977
|
|
784
978
|
def stats_runner():
|
785
979
|
ptime = round(time.process_time() * 1000000) + self.ptimeSum
|
786
980
|
try:
|
787
981
|
import psutil
|
982
|
+
|
788
983
|
process = psutil.Process(os.getpid())
|
789
984
|
heapTotal = process.memory_info().rss
|
790
985
|
except:
|
791
986
|
try:
|
792
987
|
import resource
|
793
|
-
|
794
|
-
|
988
|
+
|
989
|
+
heapTotal = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
795
990
|
except:
|
796
991
|
heapTotal = 0
|
797
992
|
|
798
993
|
for _, stats in self.allMemoryStats.items():
|
799
|
-
heapTotal += stats[
|
994
|
+
heapTotal += stats["memoryUsage"]["heapTotal"]
|
800
995
|
|
801
996
|
stats = {
|
802
|
-
|
803
|
-
|
804
|
-
|
997
|
+
"cpu": {
|
998
|
+
"user": ptime,
|
999
|
+
"system": 0,
|
805
1000
|
},
|
806
|
-
|
807
|
-
|
1001
|
+
"memoryUsage": {
|
1002
|
+
"heapTotal": heapTotal,
|
808
1003
|
},
|
809
1004
|
}
|
810
1005
|
asyncio.run_coroutine_threadsafe(update_stats(stats), self.loop)
|
@@ -813,11 +1008,14 @@ class PluginRemote:
|
|
813
1008
|
stats_runner()
|
814
1009
|
|
815
1010
|
|
816
|
-
async def plugin_async_main(
|
1011
|
+
async def plugin_async_main(
|
1012
|
+
loop: AbstractEventLoop, rpcTransport: rpc_reader.RpcTransport
|
1013
|
+
):
|
817
1014
|
peer, readLoop = await rpc_reader.prepare_peer_readloop(loop, rpcTransport)
|
818
|
-
peer.params[
|
819
|
-
peer.params[
|
820
|
-
peer, api, pluginId, hostInfo, loop
|
1015
|
+
peer.params["print"] = print
|
1016
|
+
peer.params["getRemote"] = lambda api, pluginId, hostInfo: PluginRemote(
|
1017
|
+
peer, api, pluginId, hostInfo, loop
|
1018
|
+
)
|
821
1019
|
|
822
1020
|
try:
|
823
1021
|
await readLoop()
|
@@ -831,6 +1029,7 @@ def main(rpcTransport: rpc_reader.RpcTransport):
|
|
831
1029
|
def gc_runner():
|
832
1030
|
gc.collect()
|
833
1031
|
loop.call_later(10, gc_runner)
|
1032
|
+
|
834
1033
|
gc_runner()
|
835
1034
|
|
836
1035
|
loop.run_until_complete(plugin_async_main(loop, rpcTransport))
|
@@ -850,8 +1049,10 @@ def plugin_main(rpcTransport: rpc_reader.RpcTransport):
|
|
850
1049
|
# if it does, try starting without it.
|
851
1050
|
try:
|
852
1051
|
import gi
|
853
|
-
|
1052
|
+
|
1053
|
+
gi.require_version("Gst", "1.0")
|
854
1054
|
from gi.repository import GLib, Gst
|
1055
|
+
|
855
1056
|
Gst.init(None)
|
856
1057
|
|
857
1058
|
# can't remember why starting the glib main loop is necessary.
|
@@ -859,8 +1060,9 @@ def plugin_main(rpcTransport: rpc_reader.RpcTransport):
|
|
859
1060
|
# seems optional on other platforms.
|
860
1061
|
loop = GLib.MainLoop()
|
861
1062
|
|
862
|
-
worker = threading.Thread(
|
863
|
-
rpcTransport,), name="asyncio-main"
|
1063
|
+
worker = threading.Thread(
|
1064
|
+
target=main, args=(rpcTransport,), name="asyncio-main"
|
1065
|
+
)
|
864
1066
|
worker.start()
|
865
1067
|
|
866
1068
|
loop.run()
|