WebServer.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. from glob import glob
  2. from os import path, mkdir, getcwd
  3. from os.path import exists
  4. from time import time
  5. from uuid import uuid1
  6. import eventlet
  7. import socketio
  8. from flask import Flask, make_response, send_from_directory, request
  9. from werkzeug import formparser
  10. from pycs.ApplicationStatus import ApplicationStatus
  11. from pycs.pipeline.PipelineManager import PipelineManager
  12. from pycs.util.GenericWrapper import GenericWrapper
  13. from pycs.util.ProgressFileWriter import ProgressFileWriter
  14. from pycs.util.RecursiveDictionary import set_recursive
  15. class WebServer:
  16. def __init__(self, app_status: ApplicationStatus, pipeline_manager: PipelineManager):
  17. # initialize web server
  18. if exists('webui/index.html'):
  19. print('production build')
  20. # TODO update file upload
  21. # files = FileProvider(app_status)
  22. # find svg icons and add them as separate static files to
  23. # set their correct mime type / content_type
  24. static_files = {'/': 'webui/'}
  25. for svg_path in glob('webui/img/*.svg'):
  26. svg_path = svg_path.replace('\\', '/')
  27. static_files[svg_path[5:]] = {'content_type': 'image/svg+xml', 'filename': svg_path}
  28. self.__sio = socketio.Server()
  29. self.__flask = Flask(__name__)
  30. self.__app = socketio.WSGIApp(self.__sio, self.__flask, static_files=static_files)
  31. def response():
  32. rsp = make_response()
  33. return rsp
  34. else:
  35. print('development build')
  36. # TODO update file upload
  37. # files = FileProvider(app_status, cors=True)
  38. self.__sio = socketio.Server(cors_allowed_origins='*')
  39. self.__flask = Flask(__name__)
  40. self.__app = socketio.WSGIApp(self.__sio, self.__flask)
  41. def response():
  42. rsp = make_response()
  43. rsp.headers['Access-Control-Allow-Origin'] = '*'
  44. return rsp
  45. # save every change in application status and send it to the client
  46. app_status.subscribe(self.__update_application_status, immediate=True)
  47. # define events
  48. @self.__sio.event
  49. def connect(id, msg):
  50. print('connect')
  51. self.__sio.emit('app_status', self.__status, to=id)
  52. @self.__flask.route('/settings', methods=['POST'])
  53. def edit_settings():
  54. data = request.get_json(force=True)
  55. set_recursive(data, app_status['settings'])
  56. response = make_response()
  57. response.headers['Access-Control-Allow-Origin'] = '*'
  58. return response
  59. @self.__flask.route('/projects', methods=['POST'])
  60. def create_project():
  61. data = request.get_json(force=True)
  62. # TODO move to project manager
  63. app_status['projects'].append({
  64. 'status': 'create',
  65. 'name': data['name'],
  66. 'description': data['description'],
  67. 'model': data['model']
  68. })
  69. response = make_response()
  70. response.headers['Access-Control-Allow-Origin'] = '*'
  71. return response
  72. @self.__flask.route('/projects/<identifier>', methods=['POST'])
  73. def edit_project(identifier):
  74. data = request.get_json(force=True)
  75. # TODO move to project manager
  76. for project in app_status['projects']:
  77. if project['id'] == identifier:
  78. # delete
  79. if 'delete' in data.keys():
  80. project['action'] = 'delete'
  81. # update
  82. else:
  83. set_recursive(data, project)
  84. project['action'] = 'update'
  85. return response()
  86. @self.__flask.route('/projects/<identifier>/data', methods=['POST'])
  87. def upload_file(identifier):
  88. # TODO move to project manager
  89. file_uuid = str(uuid1())
  90. # get current project path
  91. opened_projects = list(filter(lambda x: x['id'] == identifier, app_status['projects']))
  92. if len(opened_projects) == 0:
  93. return make_response('no open project available', 500)
  94. current_project = opened_projects[0]
  95. upload_path = path.join('projects', current_project['id'], 'data')
  96. job = GenericWrapper()
  97. file_name = GenericWrapper()
  98. file_extension = GenericWrapper()
  99. file_size = GenericWrapper(0)
  100. # save upload to file
  101. def custom_stream_factory(total_content_length, filename, content_type, content_length=None):
  102. file_name.value, file_extension.value = path.splitext(filename)
  103. file_path = path.join(upload_path, f'{file_uuid}{file_extension.value}')
  104. # add job to app status
  105. job.value = app_status['jobs'].append({
  106. 'id': file_uuid,
  107. 'type': 'upload',
  108. 'progress': 0,
  109. 'filename': filename,
  110. 'created': int(time()),
  111. 'finished': None
  112. })
  113. # create upload path if not exists
  114. if not path.exists(upload_path):
  115. mkdir(upload_path)
  116. # define progress callback
  117. length = content_length if content_length is not None and content_length != 0 else total_content_length
  118. def callback(progress):
  119. file_size.value += progress
  120. relative = progress / length
  121. if relative - job.value['progress'] > 0.02:
  122. job.value['progress'] = relative
  123. # open file handler
  124. return ProgressFileWriter(file_path, 'wb', callback)
  125. stream, form, files = formparser.parse_form_data(request.environ, stream_factory=custom_stream_factory)
  126. if 'file' not in files.keys():
  127. return make_response('no file uploaded', 500)
  128. # set progress to 1 after upload is done
  129. job = job.value
  130. job['progress'] = 1
  131. job['finished'] = int(time())
  132. # add to project files
  133. if 'data' not in current_project:
  134. current_project['data'] = []
  135. current_project['data'].append({
  136. 'id': file_uuid,
  137. 'name': file_name.value,
  138. 'extension': file_extension.value,
  139. 'size': file_size.value,
  140. 'created': job['created']
  141. })
  142. # return default success response
  143. return response()
  144. @self.__flask.route('/projects/<project_identifier>/data/<file_identifier>', methods=['GET'])
  145. def get_file(project_identifier, file_identifier):
  146. # get current project
  147. opened_projects = list(filter(lambda x: x['id'] == project_identifier, app_status['projects']))
  148. if len(opened_projects) == 0:
  149. return make_response('no open project available', 500)
  150. current_project = opened_projects[0]
  151. file_directory = path.join(getcwd(), 'projects', current_project['id'], 'data')
  152. print(current_project)
  153. # get object
  154. data_objects = list(filter(lambda x: x['id'] == file_identifier, current_project['data']))
  155. if len(data_objects) == 0:
  156. return make_response('data object not avilable', 500)
  157. target_object = data_objects[0]
  158. # return data
  159. file_name = target_object['id'] + target_object['extension']
  160. return send_from_directory(file_directory, file_name)
  161. @self.__flask.route('/projects/<project_identifier>/data/<file_identifier>', methods=['POST'])
  162. def run_prediction(project_identifier, file_identifier):
  163. pipeline_manager.run(project_identifier, file_identifier)
  164. return response()
  165. # finally start web server
  166. host = app_status['settings']['frontend']['host']
  167. port = app_status['settings']['frontend']['port']
  168. eventlet.wsgi.server(eventlet.listen((host, port)), self.__app)
  169. def __update_application_status(self, status):
  170. self.__status = status
  171. self.__sio.emit('app_status', status)