From 3e87737ee6fd1aabc20fab928c52efbe611d4408 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 30 Dec 2019 16:34:44 -0800 Subject: [PATCH] updates to use tk after for backgrounded tasks, also added background task convenience class for running something in the background and running a callback using tk.after when done --- daemon/core/gui/app.py | 7 +++ daemon/core/gui/coreclient.py | 69 ++++++++++------------------- daemon/core/gui/dialogs/marker.py | 16 ++----- daemon/core/gui/dialogs/sessions.py | 8 ++-- daemon/core/gui/menuaction.py | 46 +++++++------------ daemon/core/gui/task.py | 29 ++++++++++++ daemon/core/gui/toolbar.py | 62 +++++++++++++++++++------- 7 files changed, 127 insertions(+), 110 deletions(-) create mode 100644 daemon/core/gui/task.py diff --git a/daemon/core/gui/app.py b/daemon/core/gui/app.py index 969292fe..dba22068 100644 --- a/daemon/core/gui/app.py +++ b/daemon/core/gui/app.py @@ -93,5 +93,12 @@ class Application(tk.Frame): def save_config(self): appconfig.save(self.guiconfig) + def joined_session_update(self): + self.statusbar.progress_bar.stop() + if self.core.is_runtime(): + self.toolbar.set_runtime() + else: + self.toolbar.set_design() + def close(self): self.master.destroy() diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 2f1488fd..f30dcc94 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -4,9 +4,7 @@ Incorporate grpc into python tkinter GUI import json import logging import os -import time from pathlib import Path -from tkinter import messagebox import grpc @@ -294,16 +292,10 @@ class CoreClient: response = self.client.get_session_metadata(self.session_id) self.parse_metadata(response.config) except grpc.RpcError as e: - show_grpc_error(e) + self.app.after(0, show_grpc_error, e) # update ui to represent current state - if self.is_runtime(): - self.app.toolbar.runtime_frame.tkraise() - self.app.toolbar.click_runtime_selection() - else: - self.app.toolbar.design_frame.tkraise() - self.app.toolbar.click_selection() - self.app.statusbar.progress_bar.stop() + self.app.after(0, self.app.joined_session_update) def is_runtime(self): return self.state == core_pb2.SessionState.RUNTIME @@ -390,7 +382,7 @@ class CoreClient: ) self.join_session(response.session_id, query_location=False) except grpc.RpcError as e: - show_grpc_error(e) + self.app.after(0, show_grpc_error, e) def delete_session(self, session_id=None): if session_id is None: @@ -399,7 +391,7 @@ class CoreClient: response = self.client.delete_session(session_id) logging.info("deleted session result: %s", response) except grpc.RpcError as e: - show_grpc_error(e) + self.app.after(0, show_grpc_error, e) def set_up(self): """ @@ -431,7 +423,7 @@ class CoreClient: x.node_type: set(x.services) for x in response.defaults } except grpc.RpcError as e: - show_grpc_error(e) + self.app.after(0, show_grpc_error, e) self.app.close() def edit_node(self, core_node): @@ -440,7 +432,7 @@ class CoreClient: self.session_id, core_node.id, core_node.position, source=GUI_SOURCE ) except grpc.RpcError as e: - show_grpc_error(e) + self.app.after(0, show_grpc_error, e) def start_session(self): nodes = [x.core_node for x in self.canvas_nodes.values()] @@ -459,7 +451,7 @@ class CoreClient: else: emane_config = None - start = time.perf_counter() + response = core_pb2.StartSessionResponse(result=False) try: response = self.client.start_session( self.session_id, @@ -478,45 +470,30 @@ class CoreClient: logging.debug( "start session(%s), result: %s", self.session_id, response.result ) - process_time = time.perf_counter() - start - - # stop progress bar and update status - self.app.statusbar.progress_bar.stop() - message = f"Start ran for {process_time:.3f} seconds" - self.app.statusbar.set_status(message) if response.result: self.set_metadata() - self.app.toolbar.set_runtime() - - # display mobility players - for node_id, config in self.mobility_configs.items(): - canvas_node = self.canvas_nodes[node_id] - mobility_player = MobilityPlayer( - self.app, self.app, canvas_node, config - ) - mobility_player.show() - self.mobility_players[node_id] = mobility_player - else: - message = "\n".join(response.exceptions) - messagebox.showerror("Start Error", message) except grpc.RpcError as e: - show_grpc_error(e) + self.app.after(0, show_grpc_error, e) + return response def stop_session(self, session_id=None): if not session_id: session_id = self.session_id - start = time.perf_counter() + response = core_pb2.StopSessionResponse(result=False) try: response = self.client.stop_session(session_id) - self.app.canvas.stopped_session() - logging.debug( - "stopped session(%s), result: %s", session_id, response.result - ) - process_time = time.perf_counter() - start - self.app.statusbar.stop_session_callback(process_time) + logging.debug("stopped session(%s), result: %s", session_id, response) except grpc.RpcError as e: - show_grpc_error(e) + self.app.after(0, show_grpc_error, e) + return response + + def show_mobility_players(self): + for node_id, config in self.mobility_configs.items(): + canvas_node = self.canvas_nodes[node_id] + mobility_player = MobilityPlayer(self.app, self.app, canvas_node, config) + mobility_player.show() + self.mobility_players[node_id] = mobility_player def set_metadata(self): # create canvas data @@ -549,7 +526,7 @@ class CoreClient: logging.info("get terminal %s", response.terminal) os.system(f"{terminal} {response.terminal} &") except grpc.RpcError as e: - show_grpc_error(e) + self.app.after(0, show_grpc_error, e) def save_xml(self, file_path): """ @@ -567,7 +544,7 @@ class CoreClient: response = self.client.save_xml(self.session_id, file_path) logging.info("saved xml(%s): %s", file_path, response) except grpc.RpcError as e: - show_grpc_error(e) + self.app.after(0, show_grpc_error, e) def open_xml(self, file_path): """ @@ -581,7 +558,7 @@ class CoreClient: logging.debug("open xml: %s", response) self.join_session(response.session_id) except grpc.RpcError as e: - show_grpc_error(e) + self.app.after(0, show_grpc_error, e) def get_node_service(self, node_id, service_name): response = self.client.get_node_service(self.session_id, node_id, service_name) diff --git a/daemon/core/gui/dialogs/marker.py b/daemon/core/gui/dialogs/marker.py index 7e432305..159abd7f 100644 --- a/daemon/core/gui/dialogs/marker.py +++ b/daemon/core/gui/dialogs/marker.py @@ -2,7 +2,6 @@ marker dialog """ -import logging import tkinter as tk from tkinter import ttk @@ -20,7 +19,6 @@ class MarkerDialog(Dialog): self.radius = MARKER_THICKNESS[0] self.marker_thickness = tk.IntVar(value=MARKER_THICKNESS[0]) self.draw() - self.top.bind("", self.close_marker) def draw(self): button = ttk.Button(self.top, text="clear", command=self.clear_marker) @@ -64,16 +62,8 @@ class MarkerDialog(Dialog): def change_thickness(self, event): self.radius = self.marker_thickness.get() - def close_marker(self, event): - logging.debug("destroy marker dialog") - self.app.toolbar.marker_tool = None - - def position(self): - print(self.winfo_width(), self.winfo_height()) - # print(self.app.master.winfo_x(), self.app.master.winfo_y()) - print(self.app.canvas.winfo_rootx()) + def show(self): + super().show() self.geometry( - "+{}+{}".format( - self.app.canvas.winfo_rootx(), self.app.canvas.master.winfo_rooty() - ) + f"+{self.app.canvas.winfo_rootx()}+{self.app.canvas.master.winfo_rooty()}" ) diff --git a/daemon/core/gui/dialogs/sessions.py b/daemon/core/gui/dialogs/sessions.py index e86f5351..ad348280 100644 --- a/daemon/core/gui/dialogs/sessions.py +++ b/daemon/core/gui/dialogs/sessions.py @@ -1,5 +1,4 @@ import logging -import threading import tkinter as tk from tkinter import ttk @@ -9,6 +8,7 @@ from core.api.grpc import core_pb2 from core.gui.dialogs.dialog import Dialog from core.gui.errors import show_grpc_error from core.gui.images import ImageEnum, Images +from core.gui.task import BackgroundTask from core.gui.themes import PADX, PADY @@ -164,10 +164,8 @@ class SessionsDialog(Dialog): def join_session(self, session_id): self.app.statusbar.progress_bar.start(5) - thread = threading.Thread( - target=self.app.core.join_session, args=([session_id]) - ) - thread.start() + task = BackgroundTask(self.app, self.app.core.join_session, args=(session_id,)) + task.start() self.destroy() def on_selected(self, event): diff --git a/daemon/core/gui/menuaction.py b/daemon/core/gui/menuaction.py index 18e511e6..c48f82ff 100644 --- a/daemon/core/gui/menuaction.py +++ b/daemon/core/gui/menuaction.py @@ -3,13 +3,9 @@ The actions taken when each menubar option is clicked """ import logging -import threading -import time import webbrowser from tkinter import filedialog, messagebox -import grpc - from core.gui.appconfig import XMLS_PATH from core.gui.dialogs.about import AboutDialog from core.gui.dialogs.canvassizeandscale import SizeAndScaleDialog @@ -21,6 +17,7 @@ from core.gui.dialogs.servers import ServersDialog from core.gui.dialogs.sessionoptions import SessionOptionsDialog from core.gui.dialogs.sessions import SessionsDialog from core.gui.dialogs.throughput import ThroughputDialog +from core.gui.task import BackgroundTask class MenuAction: @@ -35,13 +32,10 @@ class MenuAction: def cleanup_old_session(self, quitapp=False): logging.info("cleaning up old session") - start = time.perf_counter() self.app.core.stop_session() self.app.core.delete_session() - process_time = time.perf_counter() - start - self.app.statusbar.stop_session_callback(process_time) - if quitapp: - self.app.quit() + # if quitapp: + # self.app.quit() def prompt_save_running_session(self, quitapp=False): """ @@ -49,26 +43,18 @@ class MenuAction: :return: nothing """ - try: - if not self.app.core.is_runtime(): - self.app.core.delete_session() - if quitapp: - self.app.quit() - else: - result = messagebox.askyesnocancel("Exit", "Stop the running session?") - if result is True: - self.app.statusbar.progress_bar.start(5) - thread = threading.Thread( - target=self.cleanup_old_session, args=([quitapp]) - ) - thread.daemon = True - thread.start() - elif result is False and quitapp: - self.app.quit() - except grpc.RpcError: - logging.exception("error deleting session") + result = True + if self.app.core.is_runtime(): + result = messagebox.askyesnocancel("Exit", "Stop the running session?") + + if result: + callback = None if quitapp: - self.app.quit() + callback = self.app.quit + task = BackgroundTask(self.app, self.cleanup_old_session, callback) + task.start() + elif quitapp: + self.app.quit() def on_quit(self, event=None): """ @@ -100,8 +86,8 @@ class MenuAction: logging.info("opening xml: %s", file_path) self.prompt_save_running_session() self.app.statusbar.progress_bar.start(5) - thread = threading.Thread(target=self.app.core.open_xml, args=([file_path])) - thread.start() + task = BackgroundTask(self.app, self.app.core.open_xml, args=(file_path,)) + task.start() def gui_preferences(self): dialog = PreferencesDialog(self.app, self.app) diff --git a/daemon/core/gui/task.py b/daemon/core/gui/task.py new file mode 100644 index 00000000..bf731dd4 --- /dev/null +++ b/daemon/core/gui/task.py @@ -0,0 +1,29 @@ +import logging +import threading + + +class BackgroundTask: + def __init__(self, master, task, callback=None, args=()): + self.master = master + self.args = args + self.task = task + self.callback = callback + self.thread = None + + def start(self): + logging.info("starting task") + self.thread = threading.Thread(target=self.run, daemon=True) + self.thread.start() + + def run(self): + result = self.task(*self.args) + logging.info("task completed") + if self.callback: + if result is None: + args = () + elif isinstance(result, (list, tuple)): + args = result + else: + args = (result,) + logging.info("calling callback: %s", args) + self.master.after(0, self.callback, *args) diff --git a/daemon/core/gui/toolbar.py b/daemon/core/gui/toolbar.py index 9229f661..5404d9e5 100644 --- a/daemon/core/gui/toolbar.py +++ b/daemon/core/gui/toolbar.py @@ -1,8 +1,8 @@ import logging -import threading +import time import tkinter as tk from functools import partial -from tkinter import ttk +from tkinter import messagebox, ttk from tkinter.font import Font from core.gui.dialogs.customnodes import CustomNodesDialog @@ -11,6 +11,7 @@ from core.gui.graph.enums import GraphMode from core.gui.graph.shapeutils import ShapeType, is_marker from core.gui.images import ImageEnum, Images from core.gui.nodeutils import NodeUtils +from core.gui.task import BackgroundTask from core.gui.themes import Styles from core.gui.tooltip import Tooltip @@ -36,6 +37,7 @@ class Toolbar(ttk.Frame): super().__init__(master, **kwargs) self.app = app self.master = app.master + self.time = None # picker data self.picker_font = Font(size=8) @@ -237,13 +239,32 @@ class Toolbar(ttk.Frame): self.app.canvas.hide_context() self.app.statusbar.progress_bar.start(5) self.app.canvas.mode = GraphMode.SELECT - thread = threading.Thread(target=self.app.core.start_session) - thread.start() + self.time = time.perf_counter() + task = BackgroundTask(self, self.app.core.start_session, self.start_callback) + task.start() + + def start_callback(self, response): + self.app.statusbar.progress_bar.stop() + total = time.perf_counter() - self.time + message = f"Start ran for {total:.3f} seconds" + self.app.statusbar.set_status(message) + self.time = None + if response.result: + self.set_runtime() + self.app.core.set_metadata() + self.app.core.show_mobility_players() + else: + message = "\n".join(response.exceptions) + messagebox.showerror("Start Error", message) def set_runtime(self): self.runtime_frame.tkraise() self.click_runtime_selection() + def set_design(self): + self.design_frame.tkraise() + self.click_selection() + def click_link(self): logging.debug("Click LINK button") self.design_select(self.link_button) @@ -401,10 +422,19 @@ class Toolbar(ttk.Frame): """ self.app.canvas.hide_context() self.app.statusbar.progress_bar.start(5) - thread = threading.Thread(target=self.app.core.stop_session) - thread.start() - self.design_frame.tkraise() - self.click_selection() + self.time = time.perf_counter() + task = BackgroundTask(self, self.app.core.stop_session, self.stop_callback) + task.start() + + def stop_callback(self, response): + self.app.statusbar.progress_bar.stop() + self.set_design() + total = time.perf_counter() - self.time + message = f"Stopped in {total:.3f} seconds" + self.app.statusbar.set_status(message) + self.app.canvas.stopped_session() + if not response.result: + messagebox.showerror("Stop Error", "Errors stopping session") def update_annotation(self, image, shape_type): logging.info("clicked annotation: ") @@ -414,10 +444,10 @@ class Toolbar(ttk.Frame): self.app.canvas.mode = GraphMode.ANNOTATION self.app.canvas.annotation_type = shape_type if is_marker(shape_type): - if not self.marker_tool: - self.marker_tool = MarkerDialog(self.master, self.app) - self.marker_tool.show() - self.marker_tool.position() + if self.marker_tool: + self.marker_tool.destroy() + self.marker_tool = MarkerDialog(self.master, self.app) + self.marker_tool.show() def click_run_button(self): logging.debug("Click on RUN button") @@ -430,10 +460,10 @@ class Toolbar(ttk.Frame): self.runtime_select(self.runtime_marker_button) self.app.canvas.mode = GraphMode.ANNOTATION self.app.canvas.annotation_type = ShapeType.MARKER - if not self.marker_tool: - self.marker_tool = MarkerDialog(self.master, self.app) - self.marker_tool.show() - self.marker_tool.position() + if self.marker_tool: + self.marker_tool.destroy() + self.marker_tool = MarkerDialog(self.master, self.app) + self.marker_tool.show() def click_two_node_button(self): logging.debug("Click TWONODE button")