6
0

WebServer.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. from glob import glob
  2. from os import path, mkdir
  3. from os.path import exists
  4. from time import time
  5. import eventlet
  6. import socketio
  7. from flask import Flask, make_response, send_from_directory, request
  8. from werkzeug import formparser
  9. from pycs.ApplicationStatus import ApplicationStatus
  10. from pycs.util.GenericWrapper import GenericWrapper
  11. from pycs.util.ProgressFileWriter import ProgressFileWriter
  12. from pycs.util.RecursiveDictionary import set_recursive
  13. class WebServer:
  14. def __init__(self, app_status: ApplicationStatus):
  15. # initialize web server
  16. if exists('webui/index.html'):
  17. print('production build')
  18. # find svg icons and add them as separate static files to
  19. # set their correct mime type / content_type
  20. static_files = {'/': 'webui/'}
  21. for svg_path in glob('webui/img/*.svg'):
  22. svg_path = svg_path.replace('\\', '/')
  23. static_files[svg_path[5:]] = {'content_type': 'image/svg+xml', 'filename': svg_path}
  24. self.__sio = socketio.Server()
  25. self.__flask = Flask(__name__)
  26. self.__app = socketio.WSGIApp(self.__sio, self.__flask, static_files=static_files)
  27. def response():
  28. rsp = make_response()
  29. return rsp
  30. else:
  31. print('development build')
  32. self.__sio = socketio.Server(cors_allowed_origins='*')
  33. self.__flask = Flask(__name__)
  34. self.__app = socketio.WSGIApp(self.__sio, self.__flask)
  35. def response():
  36. rsp = make_response()
  37. rsp.headers['Access-Control-Allow-Origin'] = '*'
  38. return rsp
  39. # save every change in application status and send it to the client
  40. app_status.subscribe(self.__update_application_status, immediate=True)
  41. # define events
  42. @self.__sio.event
  43. def connect(id, msg):
  44. self.__sio.emit('app_status', {
  45. 'keys': [],
  46. 'value': self.__status
  47. }, to=id)
  48. @self.__flask.route('/settings', methods=['POST'])
  49. def edit_settings():
  50. data = request.get_json(force=True)
  51. set_recursive(data, app_status['settings'])
  52. return response()
  53. @self.__flask.route('/projects', methods=['POST'])
  54. def create_project():
  55. data = request.get_json(force=True)
  56. app_status['projects'].create_project(data['name'], data['description'], data['model'])
  57. return response()
  58. @self.__flask.route('/projects/<identifier>', methods=['POST'])
  59. def edit_project(identifier):
  60. data = request.get_json(force=True)
  61. if 'delete' in data.keys():
  62. app_status['projects'].delete_project(identifier)
  63. elif 'fit' in data.keys():
  64. app_status['projects'].fit(identifier)
  65. elif 'predictAll' in data.keys():
  66. app_status['projects'].predict(identifier)
  67. elif 'predict' in data.keys():
  68. app_status['projects'].predict(identifier, data['predict'])
  69. else:
  70. app_status['projects'].update_project(identifier, data)
  71. return response()
  72. @self.__flask.route('/projects/<identifier>/data', methods=['POST'])
  73. def upload_file(identifier):
  74. # abort if project id is not valid
  75. if identifier not in app_status['projects'].keys():
  76. # TODO return 404
  77. return make_response('project does not exist', 500)
  78. # get project and upload path
  79. project = app_status['projects'][identifier]
  80. upload_path, file_uuid = project.new_media_file_path()
  81. # prepare wrapper objects
  82. job = GenericWrapper()
  83. file_name = GenericWrapper()
  84. file_extension = GenericWrapper()
  85. file_size = GenericWrapper(0)
  86. # save upload to file
  87. def custom_stream_factory(total_content_length, filename, content_type, content_length=None):
  88. file_name.value, file_extension.value = path.splitext(filename)
  89. file_path = path.join(upload_path, f'{file_uuid}{file_extension.value}')
  90. # add job to app status
  91. project['jobs'][file_uuid] = {
  92. 'id': file_uuid,
  93. 'type': 'upload',
  94. 'progress': 0,
  95. 'filename': filename,
  96. 'created': int(time()),
  97. 'finished': None
  98. }
  99. job.value = project['jobs'][file_uuid]
  100. # create upload path if not exists
  101. if not path.exists(upload_path):
  102. mkdir(upload_path)
  103. # define progress callback
  104. length = content_length if content_length is not None and content_length != 0 else total_content_length
  105. def callback(progress):
  106. file_size.value += progress
  107. relative = progress / length
  108. if relative - job.value['progress'] > 0.02:
  109. job.value['progress'] = relative
  110. # open file handler
  111. return ProgressFileWriter(file_path, 'wb', callback)
  112. stream, form, files = formparser.parse_form_data(request.environ, stream_factory=custom_stream_factory)
  113. if 'file' not in files.keys():
  114. return make_response('no file uploaded', 500)
  115. # set progress to 1 after upload is done
  116. job = job.value
  117. job['progress'] = 1
  118. job['finished'] = int(time())
  119. # add to project files
  120. project.add_media_file(file_uuid, file_name.value, file_extension.value, file_size.value, job['created'])
  121. # return default success response
  122. return response()
  123. @self.__flask.route('/projects/<project_identifier>/data/<file_identifier>', defaults={'size': None}, methods=['GET'])
  124. @self.__flask.route('/projects/<project_identifier>/data/<file_identifier>/<size>', methods=['GET'])
  125. def get_file(project_identifier, file_identifier, size):
  126. # abort if project id is not valid
  127. if project_identifier not in app_status['projects'].keys():
  128. return make_response('project does not exist', 500)
  129. project = app_status['projects'][project_identifier]
  130. # abort if file id is not valid
  131. if file_identifier not in project['data'].keys():
  132. return make_response('file does not exist', 500)
  133. target_object = project['data'][file_identifier]
  134. # resize image to requested size
  135. if size is not None:
  136. target_object = target_object.resize(size)
  137. # construct directory and filename
  138. file_directory, file_name = target_object.get_file()
  139. # return data
  140. return send_from_directory(file_directory, file_name)
  141. @self.__flask.route('/projects/<project_identifier>/data/<file_identifier>', methods=['POST'])
  142. def add_result(project_identifier, file_identifier):
  143. # abort if project id is not valid
  144. if project_identifier not in app_status['projects'].keys():
  145. return make_response('project does not exist', 500)
  146. project = app_status['projects'][project_identifier]
  147. # abort if file id is not valid
  148. if file_identifier not in project['data'].keys():
  149. return make_response('file does not exist', 500)
  150. target_object = project['data'][file_identifier]
  151. # add result
  152. result = request.get_json(force=True)
  153. if result:
  154. if 'delete' in result:
  155. project.remove_media_file(file_identifier)
  156. elif 'reset' in result:
  157. target_object.remove_results()
  158. elif 'x' not in result:
  159. if result['label']:
  160. target_object.add_global_result(result)
  161. else:
  162. target_object.remove_global_result()
  163. else:
  164. target_object.add_result(result)
  165. # return default success response
  166. return response()
  167. @self.__flask.route('/projects/<project_identifier>/data/<file_identifier>/<result_identifier>', methods=['POST'])
  168. def edit_result(project_identifier, file_identifier, result_identifier):
  169. # abort if project id is not valid
  170. if project_identifier not in app_status['projects'].keys():
  171. return make_response('project does not exist', 500)
  172. project = app_status['projects'][project_identifier]
  173. # abort if file id is not valid
  174. if file_identifier not in project['data'].keys():
  175. return make_response('file does not exist', 500)
  176. target_object = project['data'][file_identifier]
  177. # parse post data
  178. result = request.get_json(force=True)
  179. if result:
  180. # remove result
  181. if 'delete' in result.keys():
  182. target_object.remove_result(result_identifier)
  183. # update result
  184. else:
  185. target_object.update_result(result_identifier, result)
  186. # return default success response
  187. return response()
  188. @self.__flask.route('/projects/<project_identifier>/labels', methods=['POST'])
  189. def create_label(project_identifier):
  190. # abort if project id is not valid
  191. if project_identifier not in app_status['projects'].keys():
  192. return make_response('project does not exist', 500)
  193. project = app_status['projects'][project_identifier]
  194. # add result
  195. result = request.get_json(force=True)
  196. if result:
  197. project.add_label(result['name'])
  198. # return default success response
  199. return response()
  200. @self.__flask.route('/projects/<project_identifier>/labels/<label_identifier>', methods=['POST'])
  201. def edit_label(project_identifier, label_identifier):
  202. # abort if project id is not valid
  203. if project_identifier not in app_status['projects'].keys():
  204. return make_response('project does not exist', 500)
  205. project = app_status['projects'][project_identifier]
  206. # parse post data
  207. result = request.get_json(force=True)
  208. if result:
  209. # remove label
  210. if 'delete' in result.keys():
  211. project.remove_label(label_identifier)
  212. # update label
  213. else:
  214. project.update_label(label_identifier, result['name'])
  215. # return default success response
  216. return response()
  217. # finally start web server
  218. host = app_status['settings']['frontend']['host']
  219. port = app_status['settings']['frontend']['port']
  220. eventlet.wsgi.server(eventlet.listen((host, port)), self.__app)
  221. def __update_application_status(self, status, keys):
  222. value = status
  223. for key in keys[:-1]:
  224. value = value[key]
  225. self.__status = status
  226. self.__sio.emit('app_status', {
  227. 'keys': keys[:-1],
  228. 'value': value
  229. })