From a8675479c699ab2d6f323b996f3e77dac582eeae Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Fri, 1 Jul 2022 13:22:36 -0700 Subject: [PATCH] server: fix python plugins on windows --- docker/install-scrypted-dependencies-win.ps1 | 53 ++++++++++++++++++++ server/python/plugin-remote.py | 45 ++++++++++------- server/src/plugin/plugin-npm-dependencies.ts | 6 ++- server/src/plugin/runtime/python-worker.ts | 4 +- 4 files changed, 88 insertions(+), 20 deletions(-) create mode 100644 docker/install-scrypted-dependencies-win.ps1 diff --git a/docker/install-scrypted-dependencies-win.ps1 b/docker/install-scrypted-dependencies-win.ps1 new file mode 100644 index 000000000..e4cc20046 --- /dev/null +++ b/docker/install-scrypted-dependencies-win.ps1 @@ -0,0 +1,53 @@ +winget install -h OpenJS.NodeJS +winget install -h Python.Python.3 + +npx -y scrypted@latest install-server + +$USER_HOME_ESCAPED = $env:HOME.replace('\', '\\') +$SCRYPTED_HOME = $env:HOME + '\.scrypted' +$SCRYPTED_HOME_ESCAPED_PATH = $SCRYPTED_HOME.replace('\', '\\') +npm install --prefix $SCRYPTED_HOME node-windows@1.0.0-beta.6 --save + +$SERVICE_JS = @" +const child_process = require('child_process'); +child_process.spawn('npx.cmd', ['-y', 'scrypted', 'serve'], { + stdio: 'inherit', +}); +"@ + +$SERVICE_JS_PATH = $SCRYPTED_HOME + '\service.js' +$SERVICE_JS_ESCAPED_PATH = $SERVICE_JS_PATH.replace('\', '\\') +$SERVICE_JS | Out-File -Encoding ASCII -FilePath $SERVICE_JS_PATH + +$INSTALL_SERVICE_JS = @" +var Service = require('node-windows').Service; + +var svc = new Service({ + name:'Scrypted', + description: 'Scrypted Home Automation', + script: '$($SERVICE_JS_ESCAPED_PATH)', + env: [ + { + name: "USERPROFILE", + value: '$($USER_HOME_ESCAPED)' + }, + ] +}); + +svc.on('alreadyuninstalled', () => { + svc.install(); +}); + +svc.on('uninstall', () => { + svc.install(); +}); + +svc.on('install', () => { + svc.start(); +}); + +svc.uninstall(); +"@ + +$INSTALL_SERVICE_JS_PATH = $SCRYPTED_HOME + '\install-service.js' +$INSTALL_SERVICE_JS | Out-File -Encoding ASCII -FilePath $INSTALL_SERVICE_JS_PATH diff --git a/server/python/plugin-remote.py b/server/python/plugin-remote.py index ec0ecdf4b..ff1b8a150 100644 --- a/server/python/plugin-remote.py +++ b/server/python/plugin-remote.py @@ -6,7 +6,6 @@ import gc import json import os import platform -import resource import shutil import subprocess import threading @@ -21,7 +20,6 @@ from os import sys from typing import Any, Optional, Set, Tuple import aiofiles -import gi import scrypted_python.scrypted_sdk.types from scrypted_python.scrypted_sdk.types import (Device, DeviceManifest, MediaManager, @@ -31,12 +29,6 @@ from typing_extensions import TypedDict import rpc -gi.require_version('Gst', '1.0') - -from gi.repository import GLib, Gst - -Gst.init(None) - class SystemDeviceState(TypedDict): lastEventTime: int stateTime: int @@ -227,9 +219,9 @@ class PluginRemote: if not os.path.exists(python_prefix): os.makedirs(python_prefix) - python = 'python%s' % str( + python_version = 'python%s' % str( sys.version_info[0])+"."+str(sys.version_info[1]) - print('python:', python) + print('python version:', python_version) if 'requirements.txt' in zip.namelist(): requirements = zip.open('requirements.txt').read() @@ -254,7 +246,7 @@ class PluginRemote: f.write(requirements) f.close() - p = subprocess.Popen([python, '-m', 'pip', 'install', '-r', requirementstxt, + p = subprocess.Popen([sys.executable, '-m', 'pip', 'install', '-r', requirementstxt, '--prefix', python_prefix], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while True: line = p.stdout.readline() @@ -275,8 +267,13 @@ class PluginRemote: print(str_requirements) sys.path.insert(0, zipPath) - site_packages = os.path.join( - python_prefix, 'lib/%s/site-packages' % python) + if platform.system() != 'Windows': + site_packages = os.path.join( + python_prefix, 'lib', python_version, 'site-packages') + else: + site_packages = os.path.join( + python_prefix, 'Lib', 'site-packages') + print('site-packages: %s' % site_packages) sys.path.insert(0, site_packages) from scrypted_sdk import sdk_init # type: ignore self.systemManager = SystemManager(self.api, self.systemState) @@ -366,7 +363,11 @@ async def async_main(loop: AbstractEventLoop): def stats_runner(): ptime = round(time.process_time() * 1000000) - heapTotal = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss + try: + import resource + heapTotal = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss + except: + heapTotal = 0 stats = { 'type': 'stats', 'cpuUsage': { @@ -399,8 +400,16 @@ def main(): if __name__ == "__main__": - worker = threading.Thread(target=main) - worker.start() + try: + import gi + gi.require_version('Gst', '1.0') + from gi.repository import GLib, Gst + Gst.init(None) - loop = GLib.MainLoop() - loop.run() + worker = threading.Thread(target=main) + worker.start() + + loop = GLib.MainLoop() + loop.run() + except: + main() diff --git a/server/src/plugin/plugin-npm-dependencies.ts b/server/src/plugin/plugin-npm-dependencies.ts index d3bf5b350..eb6246e3e 100644 --- a/server/src/plugin/plugin-npm-dependencies.ts +++ b/server/src/plugin/plugin-npm-dependencies.ts @@ -6,6 +6,7 @@ import { once } from 'events'; import process from 'process'; import mkdirp from "mkdirp"; import semver from 'semver'; +import os from 'os'; export function getPluginNodePath(name: string) { const pluginVolume = ensurePluginVolume(name); @@ -48,7 +49,10 @@ export async function installOptionalDependencies(console: Console, packageJson: mkdirp.sync(nodePrefix); fs.writeFileSync(packageJsonPath, JSON.stringify(reduced)); - const cp = child_process.spawn('npm', ['--prefix', nodePrefix, 'install'], { + let npm = 'npm'; + if (os.platform() === 'win32') + npm += '.cmd'; + const cp = child_process.spawn(npm, ['--prefix', nodePrefix, 'install'], { cwd: nodePrefix, stdio: 'inherit', }); diff --git a/server/src/plugin/runtime/python-worker.ts b/server/src/plugin/runtime/python-worker.ts index 8f145e444..780fa4454 100644 --- a/server/src/plugin/runtime/python-worker.ts +++ b/server/src/plugin/runtime/python-worker.ts @@ -45,7 +45,9 @@ export class PythonRuntimeWorker extends ChildProcessWorker { } } - this.worker = child_process.spawn('python3', args, { + const pythonPath = os.platform() === 'win32' ? 'py.exe' : 'python3'; + + this.worker = child_process.spawn(pythonPath, args, { // stdin, stdout, stderr, peer in, peer out stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe'], env: Object.assign({