6
0

WebServer.py 7.9 KB

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