Cleanup
[carla.git] / source / frontend / modgui / webserver.py
blob1f89de2db04b8718e2338a13b66afeecb4563927
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
4 # Carla bridge for LV2 modguis
5 # Copyright (C) 2015-2020 Filipe Coelho <falktx@falktx.com>
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License as
9 # published by the Free Software Foundation; either version 2 of
10 # the License, or any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # For a full copy of the GNU General Public License see the doc/GPL.txt file.
19 # ------------------------------------------------------------------------------------------------------------
20 # Imports (Global)
22 import os
24 from PyQt5.QtCore import pyqtSignal, QThread
26 # ------------------------------------------------------------------------------------------------------------
27 # Generate a random port number between 9000 and 18000
29 from random import random
31 PORTn = 8998 + int(random()*9000)
33 # ------------------------------------------------------------------------------------------------------------
34 # Imports (asyncio)
36 try:
37 from asyncio import new_event_loop, set_event_loop
38 haveAsyncIO = True
39 except:
40 haveAsyncIO = False
42 # ------------------------------------------------------------------------------------------------------------
43 # Imports (tornado)
45 from tornado.log import enable_pretty_logging
46 from tornado.ioloop import IOLoop
47 from tornado.util import unicode_type
48 from tornado.web import HTTPError
49 from tornado.web import Application, RequestHandler, StaticFileHandler
51 # ------------------------------------------------------------------------------------------------------------
52 # Set up environment for the webserver
54 PORT = str(PORTn)
55 ROOT = "/usr/share/mod"
56 DATA_DIR = os.path.expanduser("~/.local/share/mod-data/")
57 HTML_DIR = os.path.join(ROOT, "html")
59 os.environ['MOD_DEV_HOST'] = "1"
60 os.environ['MOD_DEV_HMI'] = "1"
61 os.environ['MOD_DESKTOP'] = "1"
63 os.environ['MOD_DATA_DIR'] = DATA_DIR
64 os.environ['MOD_HTML_DIR'] = HTML_DIR
65 os.environ['MOD_KEY_PATH'] = os.path.join(DATA_DIR, "keys")
66 os.environ['MOD_CLOUD_PUB'] = os.path.join(ROOT, "keys", "cloud_key.pub")
67 os.environ['MOD_PLUGIN_LIBRARY_DIR'] = os.path.join(DATA_DIR, "lib")
69 os.environ['MOD_PHANTOM_BINARY'] = "/usr/bin/phantomjs"
70 os.environ['MOD_SCREENSHOT_JS'] = os.path.join(ROOT, "screenshot.js")
71 os.environ['MOD_DEVICE_WEBSERVER_PORT'] = PORT
73 # ------------------------------------------------------------------------------------------------------------
74 # Imports (MOD)
76 from modtools.utils import get_plugin_info, get_plugin_gui, get_plugin_gui_mini
78 # ------------------------------------------------------------------------------------------------------------
79 # MOD related classes
81 class JsonRequestHandler(RequestHandler):
82 def write(self, data):
83 if isinstance(data, (bytes, unicode_type, dict)):
84 RequestHandler.write(self, data)
85 self.finish()
86 return
88 elif data is True:
89 data = "true"
90 self.set_header("Content-Type", "application/json; charset=UTF-8")
92 elif data is False:
93 data = "false"
94 self.set_header("Content-Type", "application/json; charset=UTF-8")
96 else:
97 data = json.dumps(data)
98 self.set_header("Content-Type", "application/json; charset=UTF-8")
100 RequestHandler.write(self, data)
101 self.finish()
103 class EffectGet(JsonRequestHandler):
104 def get(self):
105 uri = self.get_argument('uri')
107 try:
108 data = get_plugin_info(uri)
109 except:
110 print("ERROR: get_plugin_info for '%s' failed" % uri)
111 raise HTTPError(404)
113 self.write(data)
115 class EffectFile(StaticFileHandler):
116 def initialize(self):
117 # return custom type directly. The browser will do the parsing
118 self.custom_type = None
120 uri = self.get_argument('uri')
122 try:
123 self.modgui = get_plugin_gui(uri)
124 except:
125 raise HTTPError(404)
127 try:
128 root = self.modgui['resourcesDirectory']
129 except:
130 raise HTTPError(404)
132 return StaticFileHandler.initialize(self, root)
134 def parse_url_path(self, prop):
135 try:
136 path = self.modgui[prop]
137 except:
138 raise HTTPError(404)
140 if prop in ("iconTemplate", "settingsTemplate", "stylesheet", "javascript"):
141 self.custom_type = "text/plain"
143 return path
145 def get_content_type(self):
146 if self.custom_type is not None:
147 return self.custom_type
148 return StaticFileHandler.get_content_type(self)
150 class EffectResource(StaticFileHandler):
152 def initialize(self):
153 # Overrides StaticFileHandler initialize
154 pass
156 def get(self, path):
157 try:
158 uri = self.get_argument('uri')
159 except:
160 return self.shared_resource(path)
162 try:
163 modgui = get_plugin_gui_mini(uri)
164 except:
165 raise HTTPError(404)
167 try:
168 root = modgui['resourcesDirectory']
169 except:
170 raise HTTPError(404)
172 try:
173 super(EffectResource, self).initialize(root)
174 return super(EffectResource, self).get(path)
175 except HTTPError as e:
176 if e.status_code != 404:
177 raise e
178 return self.shared_resource(path)
179 except IOError:
180 raise HTTPError(404)
182 def shared_resource(self, path):
183 super(EffectResource, self).initialize(os.path.join(HTML_DIR, 'resources'))
184 return super(EffectResource, self).get(path)
186 # ------------------------------------------------------------------------------------------------------------
187 # WebServer Thread
189 class WebServerThread(QThread):
190 # signals
191 running = pyqtSignal()
193 def __init__(self, parent=None):
194 QThread.__init__(self, parent)
196 self.fApplication = Application(
198 (r"/effect/get/?", EffectGet),
199 (r"/effect/file/(.*)", EffectFile),
200 (r"/resources/(.*)", EffectResource),
201 (r"/(.*)", StaticFileHandler, {"path": HTML_DIR}),
203 debug=True)
205 self.fPrepareWasCalled = False
206 self.fEventLoop = None
208 def run(self):
209 if not self.fPrepareWasCalled:
210 self.fPrepareWasCalled = True
211 if haveAsyncIO:
212 self.fEventLoop = new_event_loop()
213 set_event_loop(self.fEventLoop)
214 self.fApplication.listen(PORT, address="0.0.0.0")
215 if int(os.getenv("MOD_LOG", "0")):
216 enable_pretty_logging()
218 self.running.emit()
219 IOLoop.instance().start()
221 def stopWait(self):
222 IOLoop.instance().stop()
223 if self.fEventLoop is not None:
224 self.fEventLoop.call_soon_threadsafe(self.fEventLoop.stop)
225 return self.wait(5000)