@scrypted/server 0.0.82 → 0.0.86

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.

@@ -1,10 +1,13 @@
1
1
  from __future__ import annotations
2
+ from asyncio.futures import Future
3
+ from asyncio.streams import StreamReader, StreamWriter
2
4
  import os
3
5
  from os import sys
6
+ from sys import stderr, stdout
4
7
  more = os.path.join(os.getcwd(), 'node_modules/@scrypted/sdk')
5
8
  sys.path.insert(0, more)
6
9
  import scrypted_python.scrypted_sdk
7
- from scrypted_python.scrypted_sdk.types import MediaManager, MediaObject, ScryptedInterfaceProperty
10
+ from scrypted_python.scrypted_sdk.types import DeviceManifest, MediaManager, MediaObject, ScryptedInterfaceProperty
8
11
 
9
12
  from collections.abc import Mapping
10
13
  from genericpath import exists
@@ -13,7 +16,7 @@ import asyncio
13
16
  from asyncio.events import AbstractEventLoop
14
17
  import json
15
18
  import aiofiles
16
- from typing import TypedDict
19
+ from typing import Tuple, TypedDict
17
20
  import base64
18
21
  import time
19
22
  import zipfile
@@ -50,9 +53,9 @@ class DeviceState(scrypted_python.scrypted_sdk.DeviceState):
50
53
 
51
54
  def setScryptedProperty(self, property: str, value: Any):
52
55
  if property == ScryptedInterfaceProperty.id.value:
53
- raise Exception("id is read only");
56
+ raise Exception("id is read only")
54
57
  if property == ScryptedInterfaceProperty.mixins.value:
55
- raise Exception("mixins is read only");
58
+ raise Exception("mixins is read only")
56
59
  if property == ScryptedInterfaceProperty.interfaces.value:
57
60
  raise Exception("interfaces is a read only post-mixin computed property, use providedInterfaces");
58
61
 
@@ -66,6 +69,11 @@ class DeviceState(scrypted_python.scrypted_sdk.DeviceState):
66
69
  self.systemManager.api.setState(self.nativeId, property, value)
67
70
 
68
71
 
72
+ class DeviceStorage:
73
+ id: str
74
+ nativeId: str
75
+ storage: Mapping[str, str] = {}
76
+
69
77
  class DeviceManager(scrypted_python.scrypted_sdk.DeviceManager):
70
78
  def __init__(self, nativeIds: Mapping[str, DeviceStorage], systemManager: SystemManager) -> None:
71
79
  super().__init__()
@@ -80,6 +88,9 @@ class DeviceManager(scrypted_python.scrypted_sdk.DeviceManager):
80
88
  async def onDeviceEvent(self, nativeId: str, eventInterface: str, eventData: Any = None) -> None:
81
89
  await self.systemManager.api.onDeviceEvent(nativeId, eventInterface, eventData)
82
90
 
91
+ async def onDevicesChanged(self, devices: DeviceManifest) -> None:
92
+ return await self.systemManager.api.onDevicesChanged(devices)
93
+
83
94
 
84
95
  class BufferSerializer(rpc.RpcSerializer):
85
96
  def serialize(self, value):
@@ -88,20 +99,13 @@ class BufferSerializer(rpc.RpcSerializer):
88
99
  def deserialize(self, value):
89
100
  return base64.b64decode(value)
90
101
 
91
-
92
- class DeviceStorage:
93
- id: str
94
- nativeId: str
95
- storage: Mapping[str, str] = {}
96
-
97
-
98
102
  class PluginRemote:
99
103
  systemState: Mapping[str, Mapping[str, SystemDeviceState]] = {}
100
104
  nativeIds: Mapping[str, DeviceStorage] = {}
101
105
  pluginId: str
102
106
  mediaManager: MediaManager
103
107
  loop: AbstractEventLoop
104
- consoles: Mapping[str, StringIO] = {}
108
+ consoles: Mapping[str, Future[Tuple[StreamReader, StreamWriter]]] = {}
105
109
 
106
110
  def __init__(self, api, pluginId, loop: AbstractEventLoop):
107
111
  self.api = api
@@ -115,14 +119,36 @@ class PluginRemote:
115
119
  'setNativeId',
116
120
  ]
117
121
 
118
- def print(self, nativeId: str, *values: object, sep: Optional[str] = ...,
119
- end: Optional[str] = ...,
120
- flush: bool = ...,):
121
- console = self.consoles.get(nativeId)
122
- if not console:
123
- console = StringIO()
124
- self.consoles[nativeId] = console
125
- print(*values, sep = sep, end = end, file = console, flush = flush)
122
+ async def print_async(self, nativeId: str, *values: object, sep: Optional[str] = ' ',
123
+ end: Optional[str] = '\n',
124
+ flush: bool = False,):
125
+ consoleFuture = self.consoles.get(nativeId)
126
+ if not consoleFuture:
127
+ consoleFuture = Future()
128
+ self.consoles[nativeId] = consoleFuture
129
+ plugins = await self.api.getComponent('plugins')
130
+ port = await plugins.getRemoteServicePort(self.pluginId, 'console-writer')
131
+ connection = await asyncio.open_connection(port = port)
132
+ _, writer = connection
133
+ if not nativeId:
134
+ nid = 'undefined'
135
+ else:
136
+ nid = nativeId
137
+ nid += '\n'
138
+ writer.write(nid.encode('utf8'))
139
+ consoleFuture.set_result(connection)
140
+ _, writer = await consoleFuture
141
+ strio = StringIO()
142
+ print(*values, sep=sep, end=end, flush=flush, file=strio)
143
+ strio.seek(0)
144
+ b = strio.read().encode('utf8')
145
+ writer.write(b)
146
+
147
+
148
+ def print(self, nativeId: str, *values: object, sep: Optional[str] = ' ',
149
+ end: Optional[str] = '\n',
150
+ flush: bool = False,):
151
+ asyncio.run_coroutine_threadsafe(self.print_async(nativeId, *values, sep=sep, end=end, flush=flush), self.loop)
126
152
 
127
153
  async def loadZip(self, packageJson, zipData, options=None):
128
154
  zipPath = options['filename']
@@ -139,13 +165,14 @@ class PluginRemote:
139
165
 
140
166
  if 'requirements.txt' in zip.namelist():
141
167
  requirements = zip.open('requirements.txt').read()
142
- str_requirements = requirements.decode('utf8');
168
+ str_requirements = requirements.decode('utf8')
143
169
 
144
170
  requirementstxt = os.path.join(python_modules, 'requirements.txt')
171
+ installed_requirementstxt = os.path.join(python_modules, 'installed-requirements.txt')
145
172
 
146
173
  need_pip = True
147
174
  try:
148
- existing = open(requirementstxt).read()
175
+ existing = open(installed_requirementstxt).read()
149
176
  need_pip = existing != str_requirements
150
177
  except:
151
178
  pass
@@ -159,8 +186,21 @@ class PluginRemote:
159
186
  f.close()
160
187
 
161
188
  # os.system('pip install -r %s --target %s' % (requirementstxt, python_modules))
162
- result = subprocess.check_output(['pip', 'install', '-r', requirementstxt, '--target', python_modules], stderr=subprocess.STDOUT, text=True)
163
- print(result)
189
+ p = subprocess.Popen(['pip', 'install', '-r', requirementstxt, '--target', python_modules], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
190
+ while True:
191
+ line = p.stdout.readline()
192
+ if not line:
193
+ break
194
+ line = line.decode('utf8').rstrip('\r\n')
195
+ print(line)
196
+ result = p.wait()
197
+ print('pip install result %s' % result)
198
+ if result:
199
+ raise Exception('non-zero result from pip %s' % result)
200
+
201
+ f = open(installed_requirementstxt, 'wb')
202
+ f.write(requirements)
203
+ f.close()
164
204
  else:
165
205
  print('requirements.txt (up to date)')
166
206
  print(str_requirements)
package/python/rpc.py CHANGED
@@ -276,7 +276,7 @@ class RpcPeer:
276
276
  print('failure', method, e)
277
277
  tb = traceback.format_exc()
278
278
  self.createErrorResult(
279
- result, type(e).__name, str(e), tb)
279
+ result, type(e).__name__, str(e), tb)
280
280
 
281
281
  if not message.get('oneway', False):
282
282
  self.send(result)
@@ -0,0 +1,115 @@
1
+ import { ScryptedNativeId } from '@scrypted/sdk/types'
2
+ import { EventEmitter } from 'ws';
3
+ import { listenZero } from './listen-zero';
4
+ import { Server } from 'net';
5
+ import { once } from 'events';
6
+ import net from 'net'
7
+ import { Readable } from 'stream';
8
+
9
+ export interface ConsoleServer {
10
+ readPort: number,
11
+ writePort: number,
12
+ readServer: net.Server,
13
+ writeServer: net.Server,
14
+ sockets: Set<net.Socket>;
15
+ }
16
+ export async function createConsoleServer(stdout: Readable, stderr: Readable) {
17
+ const outputs = new Map<string, Buffer[]>();
18
+ const appendOutput = (data: Buffer, nativeId: ScryptedNativeId) => {
19
+ if (!nativeId)
20
+ nativeId = undefined;
21
+ let buffers = outputs.get(nativeId);
22
+ if (!buffers) {
23
+ buffers = [];
24
+ outputs.set(nativeId, buffers);
25
+ }
26
+ buffers.push(data);
27
+ // when we're over 4000 lines or whatever these buffer are,
28
+ // truncate down to 2000.
29
+ if (buffers.length > 4000)
30
+ outputs.set(nativeId, buffers.slice(buffers.length - 2000))
31
+ };
32
+
33
+ const sockets = new Set<net.Socket>();
34
+
35
+ const events = new EventEmitter();
36
+ events.on('stdout', appendOutput);
37
+ events.on('stderr', appendOutput);
38
+
39
+ stdout.on('data', data => events.emit('stdout', data));
40
+ stderr.on('data', data => events.emit('stderr', data));
41
+
42
+ const readServer = new Server(async (socket) => {
43
+ sockets.add(socket);
44
+
45
+ let [filter] = await once(socket, 'data');
46
+ filter = filter.toString().trim();
47
+ if (filter === 'undefined')
48
+ filter = undefined;
49
+
50
+ const buffers = outputs.get(filter);
51
+ if (buffers) {
52
+ const concat = Buffer.concat(buffers);
53
+ outputs.set(filter, [concat]);
54
+ socket.write(concat);
55
+ }
56
+
57
+ const cb = (data: Buffer, nativeId: ScryptedNativeId) => {
58
+ if (nativeId !== filter)
59
+ return;
60
+ socket.write(data);
61
+ };
62
+ events.on('stdout', cb)
63
+ events.on('stderr', cb)
64
+
65
+ const cleanup = () => {
66
+ events.removeListener('stdout', cb);
67
+ events.removeListener('stderr', cb);
68
+ socket.destroy();
69
+ socket.removeAllListeners();
70
+ sockets.delete(socket);
71
+ };
72
+
73
+ socket.on('close', cleanup);
74
+ socket.on('error', cleanup);
75
+ socket.on('end', cleanup);
76
+ });
77
+
78
+ const writeServer = new Server(async (socket) => {
79
+ sockets.add(socket);
80
+ const [data] = await once(socket, 'data');
81
+ let filter: string = data.toString();
82
+ const newline = filter.indexOf('\n');
83
+ if (newline !== -1) {
84
+ socket.unshift(Buffer.from(filter.substring(newline + 1)));
85
+ }
86
+ filter = filter.substring(0, newline);
87
+
88
+ if (filter === 'undefined')
89
+ filter = undefined;
90
+
91
+ const cb = (data: Buffer) => events.emit('stdout', data, filter);
92
+
93
+ socket.on('data', cb);
94
+
95
+ const cleanup = () => {
96
+ socket.destroy();
97
+ socket.removeAllListeners();
98
+ sockets.delete(socket);
99
+ };
100
+
101
+ socket.once('close', cleanup);
102
+ socket.once('error', cleanup);
103
+ socket.once('end', cleanup);
104
+ });
105
+ const readPort = await listenZero(readServer);
106
+ const writePort = await listenZero(writeServer);
107
+
108
+ return {
109
+ readPort,
110
+ writePort,
111
+ readServer,
112
+ writeServer,
113
+ sockets,
114
+ };
115
+ }
@@ -16,7 +16,6 @@ interface MixinTable {
16
16
  export class PluginDeviceProxyHandler implements ProxyHandler<any>, ScryptedDevice {
17
17
  scrypted: ScryptedRuntime;
18
18
  id: string;
19
- // proxy: Promise<any>;
20
19
  mixinTable: Promise<MixinTable[]>;
21
20
 
22
21
  constructor(scrypted: ScryptedRuntime, id: string) {
@@ -24,6 +23,7 @@ export class PluginDeviceProxyHandler implements ProxyHandler<any>, ScryptedDevi
24
23
  this.id = id;
25
24
  }
26
25
 
26
+ // should this be async?
27
27
  invalidate() {
28
28
  const mixinTable = this.mixinTable;
29
29
  this.mixinTable = undefined;