from glob import glob from os import path, mkdir from os.path import exists from time import time import eventlet import socketio from flask import Flask, make_response, send_from_directory, request from werkzeug import formparser from pycs.ApplicationStatus import ApplicationStatus from pycs.util.GenericWrapper import GenericWrapper from pycs.util.ProgressFileWriter import ProgressFileWriter from pycs.util.RecursiveDictionary import set_recursive class WebServer: def __init__(self, app_status: ApplicationStatus): # initialize web server if exists('webui/index.html'): print('production build') # find svg icons and add them as separate static files to # set their correct mime type / content_type static_files = {'/': 'webui/'} for svg_path in glob('webui/img/*.svg'): svg_path = svg_path.replace('\\', '/') static_files[svg_path[5:]] = {'content_type': 'image/svg+xml', 'filename': svg_path} self.__sio = socketio.Server() self.__flask = Flask(__name__) self.__app = socketio.WSGIApp(self.__sio, self.__flask, static_files=static_files) def response(): rsp = make_response() return rsp else: print('development build') self.__sio = socketio.Server(cors_allowed_origins='*') self.__flask = Flask(__name__) self.__app = socketio.WSGIApp(self.__sio, self.__flask) def response(): rsp = make_response() rsp.headers['Access-Control-Allow-Origin'] = '*' return rsp # save every change in application status and send it to the client app_status.subscribe(self.__update_application_status, immediate=True) # define events @self.__sio.event def connect(id, msg): self.__sio.emit('app_status', self.__status, to=id) @self.__flask.route('/settings', methods=['POST']) def edit_settings(): data = request.get_json(force=True) set_recursive(data, app_status['settings']) return response() @self.__flask.route('/projects', methods=['POST']) def create_project(): data = request.get_json(force=True) app_status['projects'].create_project(data['name'], data['description'], data['model']) return response() @self.__flask.route('/projects/', methods=['POST']) def edit_project(identifier): data = request.get_json(force=True) if 'delete' in data.keys(): app_status['projects'].delete_project(identifier) elif 'predict' in data.keys(): app_status['projects'].predict(identifier, data['predict']) else: app_status['projects'].update_project(identifier, data) return response() @self.__flask.route('/projects//data', methods=['POST']) def upload_file(identifier): # abort if project id is not valid if identifier not in app_status['projects'].keys(): # TODO return 404 return make_response('project does not exist', 500) # get project and upload path project = app_status['projects'][identifier] upload_path, file_uuid = project.new_media_file_path() # prepare wrapper objects job = GenericWrapper() file_name = GenericWrapper() file_extension = GenericWrapper() file_size = GenericWrapper(0) # save upload to file def custom_stream_factory(total_content_length, filename, content_type, content_length=None): file_name.value, file_extension.value = path.splitext(filename) file_path = path.join(upload_path, f'{file_uuid}{file_extension.value}') # add job to app status project['jobs'][file_uuid] = { 'id': file_uuid, 'type': 'upload', 'progress': 0, 'filename': filename, 'created': int(time()), 'finished': None } job.value = project['jobs'][file_uuid] # create upload path if not exists if not path.exists(upload_path): mkdir(upload_path) # define progress callback length = content_length if content_length is not None and content_length != 0 else total_content_length def callback(progress): file_size.value += progress relative = progress / length if relative - job.value['progress'] > 0.02: job.value['progress'] = relative # open file handler return ProgressFileWriter(file_path, 'wb', callback) stream, form, files = formparser.parse_form_data(request.environ, stream_factory=custom_stream_factory) if 'file' not in files.keys(): return make_response('no file uploaded', 500) # set progress to 1 after upload is done job = job.value job['progress'] = 1 job['finished'] = int(time()) # add to project files project.add_media_file({ 'id': file_uuid, 'name': file_name.value, 'extension': file_extension.value, 'size': file_size.value, 'created': job['created'] }) # return default success response return response() @self.__flask.route('/projects//data/', defaults={'size': None}, methods=['GET']) @self.__flask.route('/projects//data//', methods=['GET']) def get_file(project_identifier, file_identifier, size): # abort if project id is not valid if project_identifier not in app_status['projects'].keys(): return make_response('project does not exist', 500) project = app_status['projects'][project_identifier] # abort if file id is not valid if file_identifier not in project['data'].keys(): return make_response('file does not exist', 500) target_object = project['data'][file_identifier] # resize image to requested size if size is not None: target_object = target_object.resize(size) # construct directory and filename file_directory, file_name = target_object.get_file() # return data return send_from_directory(file_directory, file_name) @self.__flask.route('/projects//data/', methods=['POST']) def add_result(project_identifier, file_identifier): # abort if project id is not valid if project_identifier not in app_status['projects'].keys(): return make_response('project does not exist', 500) project = app_status['projects'][project_identifier] # abort if file id is not valid if file_identifier not in project['data'].keys(): return make_response('file does not exist', 500) target_object = project['data'][file_identifier] # add result result = request.get_json(force=True) if result: if 'delete' in result: project.remove_media_file(file_identifier) elif 'x' not in result: if result['label']: target_object.add_global_result(result) else: target_object.remove_global_result() else: target_object.add_result(result) # return default success response return response() @self.__flask.route('/projects//data//', methods=['POST']) def edit_result(project_identifier, file_identifier, result_identifier): # abort if project id is not valid if project_identifier not in app_status['projects'].keys(): return make_response('project does not exist', 500) project = app_status['projects'][project_identifier] # abort if file id is not valid if file_identifier not in project['data'].keys(): return make_response('file does not exist', 500) target_object = project['data'][file_identifier] # parse post data result = request.get_json(force=True) if result: # remove result if 'delete' in result.keys(): target_object.remove_result(result_identifier) # update result else: target_object.update_result(result_identifier, result) # return default success response return response() @self.__flask.route('/projects//labels', methods=['POST']) def create_label(project_identifier): # abort if project id is not valid if project_identifier not in app_status['projects'].keys(): return make_response('project does not exist', 500) project = app_status['projects'][project_identifier] # add result result = request.get_json(force=True) if result: project.add_label(result['name']) # return default success response return response() @self.__flask.route('/projects//labels/', methods=['POST']) def edit_label(project_identifier, label_identifier): # abort if project id is not valid if project_identifier not in app_status['projects'].keys(): return make_response('project does not exist', 500) project = app_status['projects'][project_identifier] # parse post data result = request.get_json(force=True) if result: # remove label if 'delete' in result.keys(): project.remove_label(label_identifier) # update label else: project.update_label(label_identifier, result['name']) # return default success response return response() # finally start web server host = app_status['settings']['frontend']['host'] port = app_status['settings']['frontend']['port'] eventlet.wsgi.server(eventlet.listen((host, port)), self.__app) def __update_application_status(self, status): self.__status = status self.__sio.emit('app_status', status)