6
0

WebServer.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  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 'predict' in data.keys():
  66. app_status['projects'].predict(identifier, data['predict'])
  67. else:
  68. app_status['projects'].update_project(identifier, data)
  69. return response()
  70. @self.__flask.route('/projects/<identifier>/data', methods=['POST'])
  71. def upload_file(identifier):
  72. # abort if project id is not valid
  73. if identifier not in app_status['projects'].keys():
  74. # TODO return 404
  75. return make_response('project does not exist', 500)
  76. # get project and upload path
  77. project = app_status['projects'][identifier]
  78. upload_path, file_uuid = project.new_media_file_path()
  79. # prepare wrapper objects
  80. job = GenericWrapper()
  81. file_name = GenericWrapper()
  82. file_extension = GenericWrapper()
  83. file_size = GenericWrapper(0)
  84. # save upload to file
  85. def custom_stream_factory(total_content_length, filename, content_type, content_length=None):
  86. file_name.value, file_extension.value = path.splitext(filename)
  87. file_path = path.join(upload_path, f'{file_uuid}{file_extension.value}')
  88. # add job to app status
  89. project['jobs'][file_uuid] = {
  90. 'id': file_uuid,
  91. 'type': 'upload',
  92. 'progress': 0,
  93. 'filename': filename,
  94. 'created': int(time()),
  95. 'finished': None
  96. }
  97. job.value = project['jobs'][file_uuid]
  98. # create upload path if not exists
  99. if not path.exists(upload_path):
  100. mkdir(upload_path)
  101. # define progress callback
  102. length = content_length if content_length is not None and content_length != 0 else total_content_length
  103. def callback(progress):
  104. file_size.value += progress
  105. relative = progress / length
  106. if relative - job.value['progress'] > 0.02:
  107. job.value['progress'] = relative
  108. # open file handler
  109. return ProgressFileWriter(file_path, 'wb', callback)
  110. stream, form, files = formparser.parse_form_data(request.environ, stream_factory=custom_stream_factory)
  111. if 'file' not in files.keys():
  112. return make_response('no file uploaded', 500)
  113. # set progress to 1 after upload is done
  114. job = job.value
  115. job['progress'] = 1
  116. job['finished'] = int(time())
  117. # add to project files
  118. project.add_media_file(file_uuid, file_name.value, file_extension.value, file_size.value, job['created'])
  119. # return default success response
  120. return response()
  121. @self.__flask.route('/projects/<project_identifier>/data/<file_identifier>', defaults={'size': None}, methods=['GET'])
  122. @self.__flask.route('/projects/<project_identifier>/data/<file_identifier>/<size>', methods=['GET'])
  123. def get_file(project_identifier, file_identifier, size):
  124. # abort if project id is not valid
  125. if project_identifier not in app_status['projects'].keys():
  126. return make_response('project does not exist', 500)
  127. project = app_status['projects'][project_identifier]
  128. # abort if file id is not valid
  129. if file_identifier not in project['data'].keys():
  130. return make_response('file does not exist', 500)
  131. target_object = project['data'][file_identifier]
  132. # resize image to requested size
  133. if size is not None:
  134. target_object = target_object.resize(size)
  135. # construct directory and filename
  136. file_directory, file_name = target_object.get_file()
  137. # return data
  138. return send_from_directory(file_directory, file_name)
  139. @self.__flask.route('/projects/<project_identifier>/data/<file_identifier>', methods=['POST'])
  140. def add_result(project_identifier, file_identifier):
  141. # abort if project id is not valid
  142. if project_identifier not in app_status['projects'].keys():
  143. return make_response('project does not exist', 500)
  144. project = app_status['projects'][project_identifier]
  145. # abort if file id is not valid
  146. if file_identifier not in project['data'].keys():
  147. return make_response('file does not exist', 500)
  148. target_object = project['data'][file_identifier]
  149. # add result
  150. result = request.get_json(force=True)
  151. if result:
  152. if 'delete' in result:
  153. project.remove_media_file(file_identifier)
  154. elif 'x' not in result:
  155. if result['label']:
  156. target_object.add_global_result(result)
  157. else:
  158. target_object.remove_global_result()
  159. else:
  160. target_object.add_result(result)
  161. # return default success response
  162. return response()
  163. @self.__flask.route('/projects/<project_identifier>/data/<file_identifier>/<result_identifier>', methods=['POST'])
  164. def edit_result(project_identifier, file_identifier, result_identifier):
  165. # abort if project id is not valid
  166. if project_identifier not in app_status['projects'].keys():
  167. return make_response('project does not exist', 500)
  168. project = app_status['projects'][project_identifier]
  169. # abort if file id is not valid
  170. if file_identifier not in project['data'].keys():
  171. return make_response('file does not exist', 500)
  172. target_object = project['data'][file_identifier]
  173. # parse post data
  174. result = request.get_json(force=True)
  175. if result:
  176. # remove result
  177. if 'delete' in result.keys():
  178. target_object.remove_result(result_identifier)
  179. # update result
  180. else:
  181. target_object.update_result(result_identifier, result)
  182. # return default success response
  183. return response()
  184. @self.__flask.route('/projects/<project_identifier>/labels', methods=['POST'])
  185. def create_label(project_identifier):
  186. # abort if project id is not valid
  187. if project_identifier not in app_status['projects'].keys():
  188. return make_response('project does not exist', 500)
  189. project = app_status['projects'][project_identifier]
  190. # add result
  191. result = request.get_json(force=True)
  192. if result:
  193. project.add_label(result['name'])
  194. # return default success response
  195. return response()
  196. @self.__flask.route('/projects/<project_identifier>/labels/<label_identifier>', methods=['POST'])
  197. def edit_label(project_identifier, label_identifier):
  198. # abort if project id is not valid
  199. if project_identifier not in app_status['projects'].keys():
  200. return make_response('project does not exist', 500)
  201. project = app_status['projects'][project_identifier]
  202. # parse post data
  203. result = request.get_json(force=True)
  204. if result:
  205. # remove label
  206. if 'delete' in result.keys():
  207. project.remove_label(label_identifier)
  208. # update label
  209. else:
  210. project.update_label(label_identifier, result['name'])
  211. # return default success response
  212. return response()
  213. # finally start web server
  214. host = app_status['settings']['frontend']['host']
  215. port = app_status['settings']['frontend']['port']
  216. eventlet.wsgi.server(eventlet.listen((host, port)), self.__app)
  217. def __update_application_status(self, status, keys):
  218. value = status
  219. for key in keys[:-1]:
  220. value = value[key]
  221. self.__status = status
  222. self.__sio.emit('app_status', {
  223. 'keys': keys[:-1],
  224. 'value': value
  225. })