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

This commit is contained in:
Blake Harnden 2019-12-30 16:34:44 -08:00
parent dd43fae62a
commit 3e87737ee6
7 changed files with 127 additions and 110 deletions

View file

@ -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()

View file

@ -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)

View file

@ -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("<Destroy>", 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()}"
)

View file

@ -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):

View file

@ -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)

29
daemon/core/gui/task.py Normal file
View file

@ -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)

View file

@ -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")