def getComicPage(self, comic_id, page_number, max_height=None): (path, page_count) = self.getSession().query(Comic.path, Comic.page_count) \ .filter(Comic.id == int(comic_id)).first() image_data = None default_img_file = AppFolders.imagePath("default.jpg") if path is not None: if int(page_number) < page_count: ca = self.getComicArchive(path) image_data = ca.getPage(int(page_number)) if image_data is None: with open(default_img_file, 'rb') as fd: image_data = fd.read() return image_data # resize image if max_height is not None: try: image_data = comicstreamerlib.utils.resizeImage( int(max_height), image_data) except Exception as e: logging.exception(e) pass return image_data
def getComicPage(self, comic_id, page_number, max_height=None): (path, page_count) = self.getSession().query(Comic.path, Comic.page_count) \ .filter(Comic.id == int(comic_id)).first() image_data = None default_img_file = AppFolders.imagePath("default.jpg") if path is not None: if int(page_number) < page_count: ca = self.getComicArchive(path) image_data = ca.getPage(int(page_number)) if image_data is None: with open(default_img_file, 'rb') as fd: image_data = fd.read() return image_data # resize image if max_height is not None: try: image_data = comicstreamerlib.utils.resizeImage( int(max_height), image_data) except: pass return image_data
def __init__(self): self.dbfile = os.path.join(AppFolders.appData(), "comicdb.sqlite") self.engine = create_engine('sqlite:///' + self.dbfile, echo=False) session_factory = sessionmaker(bind=self.engine) self.Session = scoped_session(session_factory)
def __init__(self): self.dbfile = os.path.join(AppFolders.appData(), "comicdb.sqlite") self.engine = create_engine('sqlite:///'+ self.dbfile, echo=False) session_factory = sessionmaker(bind=self.engine) self.Session = scoped_session(session_factory)
def get(self): log_file = os.path.join(AppFolders.logs(), "ComicStreamer.log") logtxt = "" for line in reversed(open(log_file).readlines()): logtxt += line.rstrip() + '\n' self.render("log.html", logtxt=logtxt)
def __init__(self): super(ComicStreamerConfig, self).__init__() self.csfolder = AppFolders.settings() # make sure folder exisits if not os.path.exists(self.csfolder): os.makedirs(self.csfolder) # set up initial values self.filename = os.path.join(self.csfolder, "settings") self.configspec = io.StringIO(ComicStreamerConfig.configspec) self.encoding = "UTF8" # since some stuff in the configobj has to happen during object initialization, # use a temporary delegate, and them merge it into self tmp = ConfigObj(self.filename, configspec=self.configspec, encoding=self.encoding) validator = Validator() tmp.validate(validator, copy=True) # set up the install ID if tmp['general']['install_id'] == '': tmp['general']['install_id'] = uuid.uuid4().hex #set up the cookie secret if tmp['security']['cookie_secret'] == '': tmp['security']['cookie_secret'] = base64.b64encode( uuid.uuid4().bytes + uuid.uuid4().bytes) # normalize the folder list tmp['general']['folder_list'] = [ os.path.abspath(os.path.normpath(unicode(a))) for a in tmp['general']['folder_list'] ] self.merge(tmp) if not os.path.exists(self.filename): self.write() # not sure if this belongs here: # if mac app, and no unrar in path, add the one from the app bundle if getattr(sys, 'frozen', None) and platform.system() == "Darwin": if which("unrar") is None: addtopath(AppFolders.appBase())
def __init__(self): super(ComicStreamerConfig, self).__init__() self.csfolder = AppFolders.settings() # make sure folder exisits if not os.path.exists(self.csfolder): os.makedirs(self.csfolder) # set up initial values self.filename = os.path.join(self.csfolder, "settings") self.configspec = io.StringIO(ComicStreamerConfig.configspec) self.encoding = "UTF8" # since some stuff in the configobj has to happen during object initialization, # use a temporary delegate, and them merge it into self tmp = ConfigObj( self.filename, configspec=self.configspec, encoding=self.encoding) validator = Validator() tmp.validate(validator, copy=True) # set up the install ID if tmp['general']['install_id'] == '': tmp['general']['install_id'] = uuid.uuid4().hex #set up the cookie secret if tmp['security']['cookie_secret'] == '': tmp['security']['cookie_secret'] = base64.b64encode( uuid.uuid4().bytes + uuid.uuid4().bytes) # normalize the folder list tmp['general']['folder_list'] = [ os.path.abspath(os.path.normpath(str(a))) for a in tmp['general']['folder_list'] ] self.merge(tmp) if not os.path.exists(self.filename): self.write() # not sure if this belongs here: # if mac app, and no unrar in path, add the one from the app bundle if getattr(sys, 'frozen', None) and platform.system() == "Darwin": if which("unrar") is None: addtopath(AppFolders.appBase())
def __init__(self, apiServer): self.apiServer = apiServer self.app = QtGui.QApplication(sys.argv) pixmap = QtGui.QPixmap(AppFolders.imagePath("trout.png")) icon = QtGui.QIcon(pixmap.scaled(16, 16)) self.trayIcon = SystemTrayIcon(icon, self) self.trayIcon.show()
def __init__(self, apiServer): self.apiServer = apiServer self.icon = AppFolders.imagePath("trout.ico") self.hover_text = "ComicStreamer" self.on_quit = self.bye menu_options = ( ('Show ComicStreamer UI', None, self.show), ) menu_options = menu_options + (('Quit', None, self.QUIT),) self._next_action_id = self.FIRST_ID self.menu_actions_by_id = set() self.menu_options = self._add_ids_to_menu_options(list(menu_options)) self.menu_actions_by_id = dict(self.menu_actions_by_id) del self._next_action_id self.default_menu_index = 1 self.window_class_name = "ComicStreamerTrayIcon" message_map = {win32gui.RegisterWindowMessage("TaskbarCreated"): self.restart, win32con.WM_DESTROY: self.destroy, win32con.WM_COMMAND: self.command, win32con.WM_USER+20 : self.notify,} # Register the Window class. window_class = win32gui.WNDCLASS() hinst = window_class.hInstance = win32gui.GetModuleHandle(None) window_class.lpszClassName = self.window_class_name window_class.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW; window_class.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW) window_class.hbrBackground = win32con.COLOR_WINDOW window_class.lpfnWndProc = message_map # could also specify a wndproc. classAtom = win32gui.RegisterClass(window_class) # Create the Window. style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU self.hwnd = win32gui.CreateWindow(classAtom, self.window_class_name, style, 0, 0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, 0, 0, hinst, None) win32gui.UpdateWindow(self.hwnd) self.notify_id = None self.refresh_icon()
def __init__(self, apiServer): super(MacGui, self).__init__("ComicStreamer", icon=AppFolders.imagePath("trout.png")) self.apiServer = apiServer self.menu = [ #rumps.MenuItem('About'), 'Show ComicStreamer UI', #None, # None functions as a separator in your menu #{'Arbitrary': # {"Depth": ["Menus", "It's pretty easy"], # "And doesn't": ["Even look like Objective C", rumps.MenuItem("One bit", callback=self.onebitcallback)]}}, None ]
def get(self, comic_id): self.validateAPIKey() thumbnail = self.library.getComicThumbnail(comic_id) if thumbnail != None: self.setContentType('image/jpg') self.write(thumbnail) else: default_img_file = AppFolders.imagePath("default.jpg") with open(default_img_file, 'rb') as fd: image_data = fd.read() self.setContentType('image/jpg') self.write(image_data)
def __init__(self, apiServer): super(MacGui, self).__init__("ComicStreamer", icon=AppFolders.imagePath("trout.png")) self.apiServer = apiServer self.menu = [ # rumps.MenuItem('About'), 'Show ComicStreamer UI', # None, # None functions as a separator in your menu # {'Arbitrary': # {"Depth": ["Menus", "It's pretty easy"], # "And doesn't": ["Even look like Objective C", rumps.MenuItem("One bit", callback=self.onebitcallback)]}}, None ]
def getComicArchive(self, path): # should also look at modified time of file for ca in self.comicArchiveList: if ca.path == path: # remove from list and put at end self.comicArchiveList.remove(ca) self.comicArchiveList.append(ca) return ca else: ca = ComicArchive( path, default_image_path=AppFolders.imagePath("default.jpg")) self.comicArchiveList.append(ca) if len(self.comicArchiveList) > 10: self.comicArchiveList.pop(0) return ca
def resizeImage(max, image_data): # disable WebP for now, due a memory leak in python library imtype = imghdr.what(StringIO(image_data)) if imtype == "webp": with open(AppFolders.imagePath("default.jpg"), 'rb') as fd: image_data = fd.read() im = Image.open(StringIO(image_data)).convert('RGB') w, h = im.size if max < h: im.thumbnail((w, max), Image.ANTIALIAS) output = StringIO() im.save(output, format="JPEG") return output.getvalue() else: return image_data
def resizeImage(max, image_data): # disable WebP for now, due a memory leak in python library imtype = imghdr.what(StringIO.StringIO(image_data)) if imtype == "webp": with open(AppFolders.imagePath("default.jpg"), 'rb') as fd: image_data = fd.read() im = Image.open(StringIO.StringIO(image_data)).convert('RGB') w,h = im.size if max < h: im.thumbnail((w,max), Image.ANTIALIAS) output = StringIO.StringIO() im.save(output, format="JPEG") return output.getvalue() else: return image_data
def resize(img, box, out, fit=False): '''Downsample the image. @param img: Image - an Image-object @param box: tuple(x, y) - the bounding box of the result image @param fix: boolean - crop the image to fill the box @param out: file-like-object - save the image into the output stream ''' if type(img) != Image and type(img) == str: try: img = Image.open(StringIO.StringIO(img)) except: img = Image.open( StringIO(open(AppFolders.imagePath("default.jpg")).read())) #preresize image with factor 2, 4, 8 and fast algorithm factor = 1 while img.size[0] / factor > 2 * box[0] and img.size[ 1] * 2 / factor > 2 * box[1]: factor *= 2 if factor > 1: img.thumbnail((img.size[0] / factor, img.size[1] / factor), Image.NEAREST) #calculate the cropping box and get the cropped part if fit: x1 = y1 = 0 x2, y2 = img.size wRatio = 1.0 * x2 / box[0] hRatio = 1.0 * y2 / box[1] if hRatio > wRatio: y1 = int(y2 / 2 - box[1] * wRatio / 2) y2 = int(y2 / 2 + box[1] * wRatio / 2) else: x1 = int(x2 / 2 - box[0] * hRatio / 2) x2 = int(x2 / 2 + box[0] * hRatio / 2) img = img.crop((x1, y1, x2, y2)) #Resize the image with best quality algorithm ANTI-ALIAS img.thumbnail(box, Image.ANTIALIAS) img = img.convert('RGB') #save it into a file-like object img.save(out, "JPEG", quality=65)
def __init__(self, apiServer): self.apiServer = apiServer self.icon = AppFolders.imagePath("trout.ico") self.hover_text = "ComicStreamer" self.on_quit = self.bye menu_options = (('Show ComicStreamer UI', None, self.show), ) menu_options = menu_options + (('Quit', None, self.QUIT), ) self._next_action_id = self.FIRST_ID self.menu_actions_by_id = set() self.menu_options = self._add_ids_to_menu_options(list(menu_options)) self.menu_actions_by_id = dict(self.menu_actions_by_id) del self._next_action_id self.default_menu_index = 1 self.window_class_name = "ComicStreamerTrayIcon" message_map = { win32gui.RegisterWindowMessage("TaskbarCreated"): self.restart, win32con.WM_DESTROY: self.destroy, win32con.WM_COMMAND: self.command, win32con.WM_USER + 20: self.notify, } # Register the Window class. window_class = win32gui.WNDCLASS() hinst = window_class.hInstance = win32gui.GetModuleHandle(None) window_class.lpszClassName = self.window_class_name window_class.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW window_class.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW) window_class.hbrBackground = win32con.COLOR_WINDOW window_class.lpfnWndProc = message_map # could also specify a wndproc. classAtom = win32gui.RegisterClass(window_class) # Create the Window. style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU self.hwnd = win32gui.CreateWindow(classAtom, self.window_class_name, style, 0, 0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, 0, 0, hinst, None) win32gui.UpdateWindow(self.hwnd) self.notify_id = None self.refresh_icon()
def __init__(self, config, opts): utils.fix_output_encoding() self.config = config self.opts = opts self.port = self.config['general']['port'] self.webroot = self.config['general']['webroot'] self.comicArchiveList = [] #if len(self.config['general']['folder_list']) == 0: # logging.error("No folders on either command-line or config file. Quitting.") # sys.exit(-1) self.dm = DataManager() self.library = Library(self.dm.Session) if opts.reset or opts.reset_and_run: logging.info( "Deleting any existing database!") self.dm.delete() # quit on a standard reset if opts.reset: sys.exit(0) try: self.dm.create() except SchemaVersionException as e: msg = "Couldn't open database. Probably the schema has changed." logging.error(msg) utils.alert("Schema change", msg) sys.exit(-1) try: self.listen(self.port, no_keep_alive = True) except Exception as e: logging.error(e) msg = "Couldn't open socket on port {0}. (Maybe ComicStreamer is already running?) Quitting.".format(self.port) logging.error(msg) utils.alert("Port not available", msg) sys.exit(-1) logging.info( "Stream server running on port {0}...".format(self.port)) #http_server = tornado.httpserver.HTTPServer(self, no_keep_alive = True, ssl_options={ # "certfile": "server.crt", # "keyfile": "server.key", #}) #http_server.listen(port+1) self.version = csversion.version handlers = [ # Web Pages (self.webroot + r"/", MainHandler), (self.webroot + r"/(.*)\.html", GenericPageHandler), (self.webroot + r"/about", AboutPageHandler), (self.webroot + r"/control", ControlPageHandler), (self.webroot + r"/configure", ConfigPageHandler), (self.webroot + r"/log", LogPageHandler), (self.webroot + r"/comiclist/browse", ComicListBrowserHandler), (self.webroot + r"/folders/browse(/.*)*", FoldersBrowserHandler), (self.webroot + r"/entities/browse(/.*)*", EntitiesBrowserHandler), (self.webroot + r"/comic/([0-9]+)/reader", ReaderHandler), (self.webroot + r"/login", LoginHandler), # Data (self.webroot + r"/dbinfo", DBInfoAPIHandler), (self.webroot + r"/version", VersionAPIHandler), (self.webroot + r"/deleted", DeletedAPIHandler), (self.webroot + r"/comic/([0-9]+)", ComicAPIHandler), (self.webroot + r"/comiclist", ComicListAPIHandler), (self.webroot + r"/comic/([0-9]+)/page/([0-9]+|clear)/bookmark", ComicBookmarkAPIHandler ), (self.webroot + r"/comic/([0-9]+)/page/([0-9]+)", ComicPageAPIHandler ), (self.webroot + r"/comic/([0-9]+)/thumbnail", ThumbnailAPIHandler), (self.webroot + r"/comic/([0-9]+)/file", FileAPIHandler), (self.webroot + r"/entities(/.*)*", EntityAPIHandler), (self.webroot + r"/folders(/.*)*", FolderAPIHandler), (self.webroot + r"/command", CommandAPIHandler), (self.webroot + r"/scanstatus", ScanStatusAPIHandler), #(r'/favicon.ico', tornado.web.StaticFileHandler, {'path': os.path.join(AppFolders.appBase(), "static","images")}), (self.webroot + r'/.*', UnknownHandler), ] settings = dict( template_path=os.path.join(AppFolders.appBase(), "templates"), static_path=os.path.join(AppFolders.appBase(), "static"), static_url_prefix=self.webroot + "/static/", debug=True, #autoreload=False, login_url=self.webroot + "/login", cookie_secret=self.config['security']['cookie_secret'], xsrf_cookies=True, ) tornado.web.Application.__init__(self, handlers, **settings) if not opts.no_monitor: logging.debug("Going to scan the following folders:") for l in self.config['general']['folder_list']: logging.debug(u" {0}".format(repr(l))) self.monitor = Monitor(self.dm, self.config['general']['folder_list']) self.monitor.start() self.monitor.scan() self.bookmarker = Bookmarker(self.dm) self.bookmarker.start() if opts.launch_browser and self.config['general']['launch_browser']: if ((platform.system() == "Linux" and os.environ.has_key('DISPLAY')) or (platform.system() == "Darwin" and not os.environ.has_key('SSH_TTY')) or platform.system() == "Windows"): webbrowser.open("http://localhost:{0}".format(self.port), new=0)
def go(self): utils.fix_output_encoding() self.apiServer = None #Configure logging # root level logger = logging.getLogger() logger.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') log_file = os.path.join(AppFolders.logs(), "ComicStreamer.log") if not os.path.exists(os.path.dirname(log_file)): os.makedirs(os.path.dirname(log_file)) fh = logging.handlers.RotatingFileHandler(log_file, maxBytes=1048576, backupCount=4, encoding="UTF8") fh.setLevel(logging.DEBUG) fh.setFormatter(formatter) logger.addHandler(fh) # By default only do info level to console sh = logging.StreamHandler(sys.stdout) sh.setLevel(logging.INFO) sh.setFormatter(formatter) logger.addHandler(sh) config = ComicStreamerConfig() opts = Options() opts.parseCmdLineArgs() # set file logging according to config file #fh.setLevel(config['general']['loglevel']) # turn up the log level, if requested if opts.debug: sh.setLevel(logging.DEBUG) elif opts.quiet: sh.setLevel(logging.CRITICAL) config.applyOptions(opts) self.apiServer = APIServer(config, opts) self.apiServer.logFileHandler = fh self.apiServer.logConsoleHandler = sh signal.signal(signal.SIGINT, self.signal_handler) bonjour = BonjourThread(self.apiServer.port) bonjour.start() if getattr(sys, 'frozen', None): # A frozen app will run a GUI self.apiServer.runInThread() logging.info("starting GUI loop") if platform.system() == "Darwin": from gui_mac import MacGui MacGui(self.apiServer).run() elif platform.system() == "Windows": from gui_win import WinGui WinGui(self.apiServer).run() self.apiServer.shutdown() else: #from gui_qt import QtBasedGui #self.apiServer.runInThread() #QtBasedGui(self.apiServer).run() #self.apiServer.shutdown() self.apiServer.run() logging.info("gui shoudld be done now")
def __init__(self, config, opts): utils.fix_output_encoding() self.config = config self.opts = opts self.port = self.config['general']['port'] self.webroot = self.config['general']['webroot'] self.comicArchiveList = [] #if len(self.config['general']['folder_list']) == 0: # logging.error("No folders on either command-line or config file. Quitting.") # sys.exit(-1) self.dm = DataManager() self.library = Library(self.dm.Session) if opts.reset or opts.reset_and_run: logging.info("Deleting any existing database!") self.dm.delete() # quit on a standard reset if opts.reset: sys.exit(0) try: self.dm.create() except SchemaVersionException as e: msg = "Couldn't open database. Probably the schema has changed." logging.error(msg) utils.alert("Schema change", msg) sys.exit(-1) try: self.listen(self.port, no_keep_alive=True) except Exception as e: logging.error(e) msg = "Couldn't open socket on port {0}. (Maybe ComicStreamer is already running?) Quitting.".format( self.port) logging.error(msg) utils.alert("Port not available", msg) sys.exit(-1) logging.info("Stream server running on port {0}...".format(self.port)) #http_server = tornado.httpserver.HTTPServer(self, no_keep_alive = True, ssl_options={ # "certfile": "server.crt", # "keyfile": "server.key", #}) #http_server.listen(port+1) self.version = csversion.version handlers = [ # Web Pages (self.webroot + r"/", MainHandler), (self.webroot + r"/(.*)\.html", GenericPageHandler), (self.webroot + r"/about", AboutPageHandler), (self.webroot + r"/control", ControlPageHandler), (self.webroot + r"/configure", ConfigPageHandler), (self.webroot + r"/log", LogPageHandler), (self.webroot + r"/comiclist/browse", ComicListBrowserHandler), (self.webroot + r"/folders/browse(/.*)*", FoldersBrowserHandler), (self.webroot + r"/entities/browse(/.*)*", EntitiesBrowserHandler), (self.webroot + r"/comic/([0-9]+)/reader", ReaderHandler), (self.webroot + r"/login", LoginHandler), # Data (self.webroot + r"/dbinfo", DBInfoAPIHandler), (self.webroot + r"/version", VersionAPIHandler), (self.webroot + r"/deleted", DeletedAPIHandler), (self.webroot + r"/comic/([0-9]+)", ComicAPIHandler), (self.webroot + r"/comiclist", ComicListAPIHandler), (self.webroot + r"/comic/([0-9]+)/page/([0-9]+|clear)/bookmark", ComicBookmarkAPIHandler), (self.webroot + r"/comic/([0-9]+)/page/([0-9]+)", ComicPageAPIHandler), (self.webroot + r"/comic/([0-9]+)/thumbnail", ThumbnailAPIHandler), (self.webroot + r"/comic/([0-9]+)/file", FileAPIHandler), (self.webroot + r"/entities(/.*)*", EntityAPIHandler), (self.webroot + r"/folders(/.*)*", FolderAPIHandler), (self.webroot + r"/command", CommandAPIHandler), (self.webroot + r"/scanstatus", ScanStatusAPIHandler), #(r'/favicon.ico', tornado.web.StaticFileHandler, {'path': os.path.join(AppFolders.appBase(), "static","images")}), (self.webroot + r'/.*', UnknownHandler), ] settings = dict( template_path=os.path.join(AppFolders.appBase(), "templates"), static_path=os.path.join(AppFolders.appBase(), "static"), static_url_prefix=self.webroot + "/static/", debug=True, #autoreload=False, login_url=self.webroot + "/login", cookie_secret=self.config['security']['cookie_secret'], xsrf_cookies=True, ) tornado.web.Application.__init__(self, handlers, **settings) if not opts.no_monitor: logging.debug("Going to scan the following folders:") for l in self.config['general']['folder_list']: logging.debug(u" {0}".format(repr(l))) self.monitor = Monitor(self.dm, self.config['general']['folder_list']) self.monitor.start() self.monitor.scan() self.bookmarker = Bookmarker(self.dm) self.bookmarker.start() if opts.launch_browser and self.config['general']['launch_browser']: if ((platform.system() == "Linux" and os.environ.has_key('DISPLAY')) or (platform.system() == "Darwin" and not os.environ.has_key('SSH_TTY')) or platform.system() == "Windows"): webbrowser.open("http://localhost:{0}".format(self.port), new=0)
def __init__(self): self.dbfile = os.path.join(AppFolders.appData(), "comicdb.sqlite")
def go(self): utils.fix_output_encoding() self.apiServer = None opts = Options() opts.parseCmdLineArgs() #Configure logging # root level logger = logging.getLogger() logger.setLevel(logging.DEBUG) formatter = logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s') log_file = os.path.join(AppFolders.logs(), "ComicStreamer.log") if not os.path.exists(os.path.dirname(log_file)): os.makedirs(os.path.dirname(log_file)) fh = logging.handlers.RotatingFileHandler(log_file, maxBytes=1048576, backupCount=4, encoding="UTF8") fh.setLevel(logging.DEBUG) fh.setFormatter(formatter) logger.addHandler(fh) # By default only do info level to console sh = logging.StreamHandler(sys.stdout) sh.setLevel(logging.INFO) sh.setFormatter(formatter) logger.addHandler(sh) # set file logging according to config file #fh.setLevel(config['general']['loglevel']) # turn up the log level, if requested if opts.debug: sh.setLevel(logging.DEBUG) elif opts.quiet: sh.setLevel(logging.CRITICAL) config = ComicStreamerConfig() config.applyOptions(opts) self.apiServer = APIServer(config, opts) self.apiServer.logFileHandler = fh self.apiServer.logConsoleHandler = sh signal.signal(signal.SIGINT, self.signal_handler) if getattr(sys, 'frozen', None): # A frozen app will run a GUI self.apiServer.runInThread() logging.debug("UI: Started") if platform.system() == "Darwin": from gui_mac import MacGui MacGui(self.apiServer).run() elif platform.system() == "Windows": from gui_win import WinGui WinGui(self.apiServer).run() self.apiServer.shutdown() else: #from gui_qt import QtBasedGui #self.apiServer.runInThread() #QtBasedGui(self.apiServer).run() #self.apiServer.shutdown() self.apiServer.run() logging.info("UI: Stopped")