Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

webserver.py 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Carla bridge for LV2 modguis
  4. # Copyright (C) 2015-2019 Filipe Coelho <falktx@falktx.com>
  5. #
  6. # This program is free software; you can redistribute it and/or
  7. # modify it under the terms of the GNU General Public License as
  8. # published by the Free Software Foundation; either version 2 of
  9. # the License, or any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # For a full copy of the GNU General Public License see the doc/GPL.txt file.
  17. # ------------------------------------------------------------------------------------------------------------
  18. # Imports (Global)
  19. import os
  20. from PyQt5.QtCore import pyqtSignal, QThread
  21. # ------------------------------------------------------------------------------------------------------------
  22. # Generate a random port number between 9000 and 18000
  23. from random import random
  24. PORTn = 8998 + int(random()*9000)
  25. # ------------------------------------------------------------------------------------------------------------
  26. # Imports (tornado)
  27. from tornado.log import enable_pretty_logging
  28. from tornado.ioloop import IOLoop
  29. from tornado.util import unicode_type
  30. from tornado.web import HTTPError
  31. from tornado.web import Application, RequestHandler, StaticFileHandler
  32. # ------------------------------------------------------------------------------------------------------------
  33. # Set up environment for the webserver
  34. PORT = str(PORTn)
  35. ROOT = "/usr/share/mod"
  36. DATA_DIR = os.path.expanduser("~/.local/share/mod-data/")
  37. HTML_DIR = os.path.join(ROOT, "html")
  38. os.environ['MOD_DEV_HOST'] = "1"
  39. os.environ['MOD_DEV_HMI'] = "1"
  40. os.environ['MOD_DESKTOP'] = "1"
  41. os.environ['MOD_DATA_DIR'] = DATA_DIR
  42. os.environ['MOD_HTML_DIR'] = HTML_DIR
  43. os.environ['MOD_KEY_PATH'] = os.path.join(DATA_DIR, "keys")
  44. os.environ['MOD_CLOUD_PUB'] = os.path.join(ROOT, "keys", "cloud_key.pub")
  45. os.environ['MOD_PLUGIN_LIBRARY_DIR'] = os.path.join(DATA_DIR, "lib")
  46. os.environ['MOD_PHANTOM_BINARY'] = "/usr/bin/phantomjs"
  47. os.environ['MOD_SCREENSHOT_JS'] = os.path.join(ROOT, "screenshot.js")
  48. os.environ['MOD_DEVICE_WEBSERVER_PORT'] = PORT
  49. # ------------------------------------------------------------------------------------------------------------
  50. # Imports (MOD)
  51. from modtools.utils import get_plugin_info, get_plugin_gui, get_plugin_gui_mini
  52. # ------------------------------------------------------------------------------------------------------------
  53. # MOD related classes
  54. class JsonRequestHandler(RequestHandler):
  55. def write(self, data):
  56. if isinstance(data, (bytes, unicode_type, dict)):
  57. RequestHandler.write(self, data)
  58. self.finish()
  59. return
  60. elif data is True:
  61. data = "true"
  62. self.set_header("Content-Type", "application/json; charset=UTF-8")
  63. elif data is False:
  64. data = "false"
  65. self.set_header("Content-Type", "application/json; charset=UTF-8")
  66. else:
  67. data = json.dumps(data)
  68. self.set_header("Content-Type", "application/json; charset=UTF-8")
  69. RequestHandler.write(self, data)
  70. self.finish()
  71. class EffectGet(JsonRequestHandler):
  72. def get(self):
  73. uri = self.get_argument('uri')
  74. try:
  75. data = get_plugin_info(uri)
  76. except:
  77. print("ERROR: get_plugin_info for '%s' failed" % uri)
  78. raise HTTPError(404)
  79. self.write(data)
  80. class EffectFile(StaticFileHandler):
  81. def initialize(self):
  82. # return custom type directly. The browser will do the parsing
  83. self.custom_type = None
  84. uri = self.get_argument('uri')
  85. try:
  86. self.modgui = get_plugin_gui(uri)
  87. except:
  88. raise HTTPError(404)
  89. try:
  90. root = self.modgui['resourcesDirectory']
  91. except:
  92. raise HTTPError(404)
  93. return StaticFileHandler.initialize(self, root)
  94. def parse_url_path(self, prop):
  95. try:
  96. path = self.modgui[prop]
  97. except:
  98. raise HTTPError(404)
  99. if prop in ("iconTemplate", "settingsTemplate", "stylesheet", "javascript"):
  100. self.custom_type = "text/plain"
  101. return path
  102. def get_content_type(self):
  103. if self.custom_type is not None:
  104. return self.custom_type
  105. return StaticFileHandler.get_content_type(self)
  106. class EffectResource(StaticFileHandler):
  107. def initialize(self):
  108. # Overrides StaticFileHandler initialize
  109. pass
  110. def get(self, path):
  111. try:
  112. uri = self.get_argument('uri')
  113. except:
  114. return self.shared_resource(path)
  115. try:
  116. modgui = get_plugin_gui_mini(uri)
  117. except:
  118. raise HTTPError(404)
  119. try:
  120. root = modgui['resourcesDirectory']
  121. except:
  122. raise HTTPError(404)
  123. try:
  124. super(EffectResource, self).initialize(root)
  125. return super(EffectResource, self).get(path)
  126. except HTTPError as e:
  127. if e.status_code != 404:
  128. raise e
  129. return self.shared_resource(path)
  130. except IOError:
  131. raise HTTPError(404)
  132. def shared_resource(self, path):
  133. super(EffectResource, self).initialize(os.path.join(HTML_DIR, 'resources'))
  134. return super(EffectResource, self).get(path)
  135. # ------------------------------------------------------------------------------------------------------------
  136. # WebServer Thread
  137. class WebServerThread(QThread):
  138. # signals
  139. running = pyqtSignal()
  140. def __init__(self, parent=None):
  141. QThread.__init__(self, parent)
  142. self.fApplication = Application(
  143. [
  144. (r"/effect/get/?", EffectGet),
  145. (r"/effect/file/(.*)", EffectFile),
  146. (r"/resources/(.*)", EffectResource),
  147. (r"/(.*)", StaticFileHandler, {"path": HTML_DIR}),
  148. ],
  149. debug=True)
  150. self.fPrepareWasCalled = False
  151. def run(self):
  152. if not self.fPrepareWasCalled:
  153. self.fPrepareWasCalled = True
  154. self.fApplication.listen(PORT, address="0.0.0.0")
  155. if int(os.getenv("MOD_LOG", "0")):
  156. enable_pretty_logging()
  157. self.running.emit()
  158. IOLoop.instance().start()
  159. def stopWait(self):
  160. IOLoop.instance().stop()
  161. return self.wait(5000)