from glob import glob from os import path, mkdir, getcwd from os.path import exists from time import time from uuid import uuid1 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.pipeline.PipelineManager import PipelineManager 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, pipeline_manager: PipelineManager): # initialize web server if exists('webui/index.html'): print('production build') # TODO update file upload # files = FileProvider(app_status) # 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') # TODO update file upload # files = FileProvider(app_status, cors=True) 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): print('connect') 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']) response = make_response() response.headers['Access-Control-Allow-Origin'] = '*' return response @self.__flask.route('/projects', methods=['POST']) def create_project(): data = request.get_json(force=True) # TODO move to project manager app_status['projects'].append({ 'status': 'create', 'name': data['name'], 'description': data['description'], 'model': data['model'] }) response = make_response() response.headers['Access-Control-Allow-Origin'] = '*' return response @self.__flask.route('/projects/', methods=['POST']) def edit_project(identifier): data = request.get_json(force=True) # TODO move to project manager for project in app_status['projects']: if project['id'] == identifier: # delete if 'delete' in data.keys(): project['action'] = 'delete' # update else: set_recursive(data, project) project['action'] = 'update' return response() @self.__flask.route('/projects//data', methods=['POST']) def upload_file(identifier): # TODO move to project manager file_uuid = str(uuid1()) # get current project path opened_projects = list(filter(lambda x: x['id'] == identifier, app_status['projects'])) if len(opened_projects) == 0: return make_response('no open project available', 500) current_project = opened_projects[0] upload_path = path.join('projects', current_project['id'], 'data') 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 job.value = app_status['jobs'].append({ 'id': file_uuid, 'type': 'upload', 'progress': 0, 'filename': filename, 'created': int(time()), 'finished': None }) # 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 if 'data' not in current_project: current_project['data'] = [] current_project['data'].append({ '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/', methods=['GET']) def get_file(project_identifier, file_identifier): # get current project opened_projects = list(filter(lambda x: x['id'] == project_identifier, app_status['projects'])) if len(opened_projects) == 0: return make_response('no open project available', 500) current_project = opened_projects[0] file_directory = path.join(getcwd(), 'projects', current_project['id'], 'data') print(current_project) # get object data_objects = list(filter(lambda x: x['id'] == file_identifier, current_project['data'])) if len(data_objects) == 0: return make_response('data object not avilable', 500) target_object = data_objects[0] # return data file_name = target_object['id'] + target_object['extension'] return send_from_directory(file_directory, file_name) @self.__flask.route('/projects//data/', methods=['POST']) def run_prediction(project_identifier, file_identifier): pipeline_manager.run(project_identifier, file_identifier) 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)