WebServer.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  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):
  17. # initialize web server
  18. if exists('webui/index.html'):
  19. print('production build')
  20. # find svg icons and add them as separate static files to
  21. # set their correct mime type / content_type
  22. static_files = {'/': 'webui/'}
  23. for svg_path in glob('webui/img/*.svg'):
  24. svg_path = svg_path.replace('\\', '/')
  25. static_files[svg_path[5:]] = {'content_type': 'image/svg+xml', 'filename': svg_path}
  26. self.__sio = socketio.Server()
  27. self.__flask = Flask(__name__)
  28. self.__app = socketio.WSGIApp(self.__sio, self.__flask, static_files=static_files)
  29. def response():
  30. rsp = make_response()
  31. return rsp
  32. else:
  33. print('development build')
  34. self.__sio = socketio.Server(cors_allowed_origins='*')
  35. self.__flask = Flask(__name__)
  36. self.__app = socketio.WSGIApp(self.__sio, self.__flask)
  37. def response():
  38. rsp = make_response()
  39. rsp.headers['Access-Control-Allow-Origin'] = '*'
  40. return rsp
  41. # save every change in application status and send it to the client
  42. app_status.subscribe(self.__update_application_status, immediate=True)
  43. # define events
  44. @self.__sio.event
  45. def connect(id, msg):
  46. self.__sio.emit('app_status', self.__status, to=id)
  47. @self.__flask.route('/settings', methods=['POST'])
  48. def edit_settings():
  49. data = request.get_json(force=True)
  50. set_recursive(data, app_status['settings'])
  51. return response()
  52. @self.__flask.route('/projects', methods=['POST'])
  53. def create_project():
  54. data = request.get_json(force=True)
  55. app_status['projects'].create_project(data['name'], data['description'], data['model'])
  56. return response()
  57. @self.__flask.route('/projects/<identifier>', methods=['POST'])
  58. def edit_project(identifier):
  59. data = request.get_json(force=True)
  60. if 'delete' in data.keys():
  61. app_status['projects'].delete_project(identifier)
  62. elif 'predict' in data.keys():
  63. app_status['projects'].predict(identifier, data['predict'])
  64. else:
  65. app_status['projects'].update_project(identifier, data)
  66. return response()
  67. @self.__flask.route('/projects/<identifier>/data', methods=['POST'])
  68. def upload_file(identifier):
  69. # abort if project id is not valid
  70. if identifier not in app_status['projects'].keys():
  71. # TODO return 404
  72. return make_response('project does not exist', 500)
  73. # get project and upload path
  74. project = app_status['projects'][identifier]
  75. upload_path, file_uuid = project.new_media_file_path()
  76. # prepare wrapper objects
  77. job = GenericWrapper()
  78. file_name = GenericWrapper()
  79. file_extension = GenericWrapper()
  80. file_size = GenericWrapper(0)
  81. # save upload to file
  82. def custom_stream_factory(total_content_length, filename, content_type, content_length=None):
  83. file_name.value, file_extension.value = path.splitext(filename)
  84. file_path = path.join(upload_path, f'{file_uuid}{file_extension.value}')
  85. # add job to app status
  86. job.value = app_status['jobs'].append({
  87. 'id': file_uuid,
  88. 'type': 'upload',
  89. 'progress': 0,
  90. 'filename': filename,
  91. 'created': int(time()),
  92. 'finished': None
  93. })
  94. # create upload path if not exists
  95. if not path.exists(upload_path):
  96. mkdir(upload_path)
  97. # define progress callback
  98. length = content_length if content_length is not None and content_length != 0 else total_content_length
  99. def callback(progress):
  100. file_size.value += progress
  101. relative = progress / length
  102. if relative - job.value['progress'] > 0.02:
  103. job.value['progress'] = relative
  104. # open file handler
  105. return ProgressFileWriter(file_path, 'wb', callback)
  106. stream, form, files = formparser.parse_form_data(request.environ, stream_factory=custom_stream_factory)
  107. if 'file' not in files.keys():
  108. return make_response('no file uploaded', 500)
  109. # set progress to 1 after upload is done
  110. job = job.value
  111. job['progress'] = 1
  112. job['finished'] = int(time())
  113. # add to project files
  114. project.add_media_file({
  115. 'id': file_uuid,
  116. 'name': file_name.value,
  117. 'extension': file_extension.value,
  118. 'size': file_size.value,
  119. 'created': job['created']
  120. })
  121. # return default success response
  122. return response()
  123. @self.__flask.route('/projects/<project_identifier>/data/<file_identifier>', methods=['GET'])
  124. def get_file(project_identifier, file_identifier):
  125. # abort if project id is not valid
  126. if project_identifier not in app_status['projects'].keys():
  127. return make_response('project does not exist', 500)
  128. project = app_status['projects'][project_identifier]
  129. # abort if file id is not valid
  130. if file_identifier not in project['data'].keys():
  131. return make_response('file does not exist', 500)
  132. target_object = project['data'][file_identifier]
  133. # construct directory and filename
  134. file_directory = path.join(getcwd(), 'projects', project['id'], 'data')
  135. file_name = target_object['id'] + target_object['extension']
  136. # return data
  137. return send_from_directory(file_directory, file_name)
  138. # finally start web server
  139. host = app_status['settings']['frontend']['host']
  140. port = app_status['settings']['frontend']['port']
  141. eventlet.wsgi.server(eventlet.listen((host, port)), self.__app)
  142. def __update_application_status(self, status):
  143. self.__status = status
  144. self.__sio.emit('app_status', status)