6
0

WebServer.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. import os
  2. import glob
  3. import threading
  4. from logging.config import dictConfig
  5. from flask import send_from_directory
  6. from flask_socketio import SocketIO
  7. from pycs import app
  8. from pycs.database.Database import Database
  9. from pycs.frontend.endpoints.ListJobs import ListJobs
  10. from pycs.frontend.endpoints.ListLabelProviders import ListLabelProviders
  11. from pycs.frontend.endpoints.ListModels import ListModels
  12. from pycs.frontend.endpoints.ListProjects import ListProjects
  13. from pycs.frontend.endpoints.additional.FolderInformation import FolderInformation
  14. from pycs.frontend.endpoints.data.GetFile import GetFile
  15. from pycs.frontend.endpoints.data.GetPreviousAndNextFile import GetPreviousAndNextFile
  16. from pycs.frontend.endpoints.data.GetResizedFile import GetResizedFile
  17. from pycs.frontend.endpoints.data.RemoveFile import RemoveFile
  18. from pycs.frontend.endpoints.data.UploadFile import UploadFile
  19. from pycs.frontend.endpoints.jobs.RemoveJob import RemoveJob
  20. from pycs.frontend.endpoints.labels.CreateLabel import CreateLabel
  21. from pycs.frontend.endpoints.labels.EditLabelName import EditLabelName
  22. from pycs.frontend.endpoints.labels.EditLabelParent import EditLabelParent
  23. from pycs.frontend.endpoints.labels.ListLabels import ListLabels
  24. from pycs.frontend.endpoints.labels.RemoveLabel import RemoveLabel
  25. from pycs.frontend.endpoints.pipelines.FitModel import FitModel
  26. from pycs.frontend.endpoints.pipelines.PredictFile import PredictFile
  27. from pycs.frontend.endpoints.pipelines.PredictModel import PredictModel
  28. from pycs.frontend.endpoints.projects.CreateProject import CreateProject
  29. from pycs.frontend.endpoints.projects.EditProjectDescription import EditProjectDescription
  30. from pycs.frontend.endpoints.projects.EditProjectName import EditProjectName
  31. from pycs.frontend.endpoints.projects.ExecuteExternalStorage import ExecuteExternalStorage
  32. from pycs.frontend.endpoints.projects.ExecuteLabelProvider import ExecuteLabelProvider
  33. from pycs.frontend.endpoints.projects.GetProjectModel import GetProjectModel
  34. from pycs.frontend.endpoints.projects.ListCollections import ListCollections
  35. from pycs.frontend.endpoints.projects.ListFiles import ListFiles
  36. from pycs.frontend.endpoints.projects.RemoveProject import RemoveProject
  37. from pycs.frontend.endpoints.results.ConfirmResult import ConfirmResult
  38. from pycs.frontend.endpoints.results.CreateResult import CreateResult
  39. from pycs.frontend.endpoints.results.EditResultData import EditResultData
  40. from pycs.frontend.endpoints.results.EditResultLabel import EditResultLabel
  41. from pycs.frontend.endpoints.results.GetProjectResults import GetProjectResults
  42. from pycs.frontend.endpoints.results.GetResults import GetResults
  43. from pycs.frontend.endpoints.results.RemoveResult import RemoveResult
  44. from pycs.frontend.endpoints.results.ResetResults import ResetResults
  45. from pycs.frontend.notifications.NotificationManager import NotificationManager
  46. from pycs.frontend.util.JSONEncoder import JSONEncoder
  47. from pycs.jobs.JobRunner import JobRunner
  48. from pycs.util.PipelineCache import PipelineCache
  49. class WebServer:
  50. """
  51. wrapper class for flask and socket.io which initializes most networking
  52. """
  53. # pylint: disable=line-too-long
  54. def __init__(self, app, settings: dict):
  55. dictConfig(settings["logging"])
  56. # initialize flask app instance
  57. self.app = app
  58. # initialize database
  59. self.db = Database()
  60. # start job runner
  61. self.logger.info('Starting job runner... ')
  62. self.jobs = JobRunner()
  63. # create pipeline cache
  64. self.logger.info('Creating pipeline cache')
  65. self.pipelines = PipelineCache(self.jobs)
  66. PRODUCTION = os.path.exists('webui/index.html')
  67. init_func = self.production_init if PRODUCTION else self.development_init
  68. kwargs, static_files = init_func()
  69. self.sio = SocketIO(self.app, **kwargs)#socketio.Server(**kwargs)
  70. # self.__app = socketio.WSGIApp(self.sio, self.app, static_files=static_files)
  71. self.host, self.port = settings['host'], settings['port']
  72. # set json encoder so database objects are serialized correctly
  73. self.app.json_encoder = JSONEncoder
  74. self.start_runner()
  75. self.init_notifications()
  76. self.define_routes()
  77. self.logger.info("Server initialized")
  78. def start_runner(self):
  79. app.logger.info(f"Main Thread ID: {threading.get_ident()}")
  80. self.jobs.start()
  81. self.pipelines.start()
  82. def stop_runner(self):
  83. self.jobs.stop()
  84. self.pipelines.stop()
  85. def wait_for_runner(self):
  86. self.jobs.wait_for_empty_queue()
  87. self.pipelines.wait_for_empty_queue()
  88. @property
  89. def logger(self):
  90. return self.app.logger
  91. def init_notifications(self):
  92. # create notification manager
  93. self.notifications = n = NotificationManager(self.sio)
  94. self.jobs.on_create(n.create_job)
  95. self.jobs.on_start(n.edit_job)
  96. self.jobs.on_progress(n.edit_job)
  97. self.jobs.on_finish(n.edit_job)
  98. self.jobs.on_remove(n.remove_job)
  99. def development_init(self):
  100. self.logger.info('Initializing development build')
  101. # set access control header to allow requests from Vue.js development server
  102. @self.app.after_request
  103. def after_request(response):
  104. # pylint: disable=unused-variable
  105. response.headers['Access-Control-Allow-Origin'] = '*'
  106. return response
  107. return dict(cors_allowed_origins='*', async_mode='eventlet'), None
  108. def production_init(self):
  109. self.logger.info('Initializing production build')
  110. kwargs = dict(async_mode='eventlet')
  111. if len(settings['allowedOrigins']) > 0:
  112. origins = settings['allowedOrigins']
  113. kwargs["cors_allowed_origins"] = origins
  114. # overwrite root path to serve index.html
  115. @self.app.route('/', methods=['GET'])
  116. def index():
  117. # pylint: disable=unused-variable
  118. return send_from_directory(os.path.join(os.getcwd(), 'webui'), 'index.html')
  119. return kwargs, self.static_files
  120. @property
  121. def static_files(self) -> dict:
  122. # find static files and folders
  123. static_files = {}
  124. for file_path in glob.glob('webui/*'):
  125. file_path = file_path.replace('\\', '/')
  126. static_files[file_path[5:]] = file_path
  127. # separately add svg files and set their correct mime type
  128. for svg_path in glob.glob('webui/img/*.svg'):
  129. svg_path = svg_path.replace('\\', '/')
  130. static_files[svg_path[5:]] = {'content_type': 'image/svg+xml', 'filename': svg_path}
  131. return static_files
  132. def define_routes(self):
  133. # additional
  134. self.app.add_url_rule(
  135. '/folder',
  136. view_func=FolderInformation.as_view('folder_information')
  137. )
  138. # jobs
  139. self.app.add_url_rule(
  140. '/jobs',
  141. view_func=ListJobs.as_view('list_jobs', self.jobs)
  142. )
  143. self.app.add_url_rule(
  144. '/jobs/<identifier>/remove',
  145. view_func=RemoveJob.as_view('remove_job', self.jobs)
  146. )
  147. # models
  148. self.app.add_url_rule(
  149. '/models',
  150. view_func=ListModels.as_view('list_models', self.db)
  151. )
  152. self.app.add_url_rule(
  153. '/projects/<int:identifier>/model',
  154. view_func=GetProjectModel.as_view('get_project_model', self.db)
  155. )
  156. # labels
  157. self.app.add_url_rule(
  158. '/label_providers',
  159. view_func=ListLabelProviders.as_view('label_providers', self.db)
  160. )
  161. self.app.add_url_rule(
  162. '/projects/<int:identifier>/labels',
  163. view_func=ListLabels.as_view('list_labels', self.db)
  164. )
  165. self.app.add_url_rule(
  166. '/projects/<int:identifier>/labels',
  167. view_func=CreateLabel.as_view('create_label', self.db, self.notifications)
  168. )
  169. self.app.add_url_rule(
  170. '/projects/<int:project_id>/labels/<int:label_id>/remove',
  171. view_func=RemoveLabel.as_view('remove_label', self.db, self.notifications)
  172. )
  173. self.app.add_url_rule(
  174. '/projects/<int:project_id>/labels/<int:label_id>/name',
  175. view_func=EditLabelName.as_view('edit_label_name', self.db, self.notifications)
  176. )
  177. self.app.add_url_rule(
  178. '/projects/<int:project_id>/labels/<int:label_id>/parent',
  179. view_func=EditLabelParent.as_view('edit_label_parent', self.db, self.notifications)
  180. )
  181. # collections
  182. self.app.add_url_rule(
  183. '/projects/<int:project_id>/collections',
  184. view_func=ListCollections.as_view('list_collections', self.db)
  185. )
  186. self.app.add_url_rule(
  187. '/projects/<int:project_id>/data/<int:collection_id>/<int:start>/<int:length>',
  188. view_func=ListFiles.as_view('list_collection_files', self.db)
  189. )
  190. # data
  191. self.app.add_url_rule(
  192. '/projects/<int:identifier>/data',
  193. view_func=UploadFile.as_view('upload_file', self.db, self.notifications)
  194. )
  195. self.app.add_url_rule(
  196. '/projects/<int:project_id>/data/<int:start>/<int:length>',
  197. view_func=ListFiles.as_view('list_files', self.db)
  198. )
  199. self.app.add_url_rule(
  200. '/data/<int:identifier>/remove',
  201. view_func=RemoveFile.as_view('remove_file', self.db, self.notifications)
  202. )
  203. self.app.add_url_rule(
  204. '/data/<int:file_id>',
  205. view_func=GetFile.as_view('get_file', self.db)
  206. )
  207. self.app.add_url_rule(
  208. '/data/<int:file_id>/<resolution>',
  209. view_func=GetResizedFile.as_view('get_resized_file', self.db)
  210. )
  211. self.app.add_url_rule(
  212. '/data/<int:file_id>/previous_next',
  213. view_func=GetPreviousAndNextFile.as_view('get_previous_and_next_file', self.db)
  214. )
  215. # results
  216. self.app.add_url_rule(
  217. '/projects/<int:project_id>/results',
  218. view_func=GetProjectResults.as_view('get_project_results')
  219. )
  220. self.app.add_url_rule(
  221. '/data/<int:file_id>/results',
  222. view_func=GetResults.as_view('get_results', self.db)
  223. )
  224. self.app.add_url_rule(
  225. '/data/<int:file_id>/results',
  226. view_func=CreateResult.as_view('create_result', self.db, self.notifications)
  227. )
  228. self.app.add_url_rule(
  229. '/data/<int:file_id>/reset',
  230. view_func=ResetResults.as_view('reset_results', self.db, self.notifications)
  231. )
  232. self.app.add_url_rule(
  233. '/results/<int:result_id>/remove',
  234. view_func=RemoveResult.as_view('remove_result', self.db, self.notifications)
  235. )
  236. self.app.add_url_rule(
  237. '/results/<int:result_id>/confirm',
  238. view_func=ConfirmResult.as_view('confirm_result', self.db, self.notifications)
  239. )
  240. self.app.add_url_rule(
  241. '/results/<int:result_id>/label',
  242. view_func=EditResultLabel.as_view('edit_result_label', self.db, self.notifications)
  243. )
  244. self.app.add_url_rule(
  245. '/results/<int:result_id>/data',
  246. view_func=EditResultData.as_view('edit_result_data', self.db, self.notifications)
  247. )
  248. # projects
  249. self.app.add_url_rule(
  250. '/projects',
  251. view_func=ListProjects.as_view('list_projects', self.db)
  252. )
  253. self.app.add_url_rule(
  254. '/projects',
  255. view_func=CreateProject.as_view('create_project', self.db, self.notifications, self.jobs)
  256. )
  257. self.app.add_url_rule(
  258. '/projects/<int:identifier>/label_provider',
  259. view_func=ExecuteLabelProvider.as_view('execute_label_provider', self.db,
  260. self.notifications, self.jobs)
  261. )
  262. self.app.add_url_rule(
  263. '/projects/<int:identifier>/external_storage',
  264. view_func=ExecuteExternalStorage.as_view('execute_external_storage', self.db,
  265. self.notifications, self.jobs)
  266. )
  267. self.app.add_url_rule(
  268. '/projects/<int:identifier>/remove',
  269. view_func=RemoveProject.as_view('remove_project', self.db, self.notifications)
  270. )
  271. self.app.add_url_rule(
  272. '/projects/<int:identifier>/name',
  273. view_func=EditProjectName.as_view('edit_project_name', self.db, self.notifications)
  274. )
  275. self.app.add_url_rule(
  276. '/projects/<int:identifier>/description',
  277. view_func=EditProjectDescription.as_view('edit_project_description', self.db,
  278. self.notifications)
  279. )
  280. # pipelines
  281. self.app.add_url_rule(
  282. '/projects/<int:project_id>/pipelines/fit',
  283. view_func=FitModel.as_view('fit_model', self.db, self.jobs, self.pipelines)
  284. )
  285. self.app.add_url_rule(
  286. '/projects/<int:project_id>/pipelines/predict',
  287. view_func=PredictModel.as_view('predict_model', self.db, self.notifications,
  288. self.jobs, self.pipelines)
  289. )
  290. self.app.add_url_rule(
  291. '/data/<int:file_id>/predict',
  292. view_func=PredictFile.as_view('predict_file', self.db, self.notifications,
  293. self.jobs, self.pipelines)
  294. )
  295. def run(self):
  296. self.logger.info("Starting server...")
  297. return self.sio.run(self.app, host=self.host, port=self.port)
  298. # eventlet.wsgi.server(eventlet.listen((self.__host, self.__port)), self.__app)