From 93b42f2717d0ffed62b5f7548afaa18c1b8ecfd1 Mon Sep 17 00:00:00 2001 From: Brett Williams Date: Wed, 31 May 2023 17:20:00 -0500 Subject: [PATCH] New client work --- dashboard.py | 2 +- lib/client/client.py | 118 ++++++++++++++++++ .../client/new_job_window.py | 35 ++---- lib/client/server_proxy.py | 54 ++++++++ 4 files changed, 183 insertions(+), 26 deletions(-) create mode 100644 lib/client/client.py rename scheduler_gui.py => lib/client/new_job_window.py (92%) create mode 100644 lib/client/server_proxy.py diff --git a/dashboard.py b/dashboard.py index 6320193..b1f08c7 100755 --- a/dashboard.py +++ b/dashboard.py @@ -30,7 +30,7 @@ status_colors = {RenderStatus.ERROR: "red", RenderStatus.CANCELLED: 'orange1', R RenderStatus.RUNNING: 'cyan'} categories = [RenderStatus.RUNNING, RenderStatus.ERROR, RenderStatus.NOT_STARTED, RenderStatus.SCHEDULED, - RenderStatus.COMPLETED, RenderStatus.CANCELLED] + RenderStatus.COMPLETED, RenderStatus.CANCELLED, RenderStatus.UNDEFINED] renderer_colors = {'ffmpeg': '[magenta]', 'blender': '[orange1]', 'aerender': '[purple]'} diff --git a/lib/client/client.py b/lib/client/client.py new file mode 100644 index 0000000..53890ed --- /dev/null +++ b/lib/client/client.py @@ -0,0 +1,118 @@ +import requests +import tkinter as tk +import threading +import time +import os +from tkinter import ttk +from new_job_window import NewJobWindow +from server_proxy import RenderServerProxy + + +def request_data(server_ip, payload, server_port=8080, timeout=2): + try: + req = requests.get(f'http://{server_ip}:{server_port}/api/{payload}', timeout=timeout) + if req.ok: + return req.json() + except Exception as e: + pass + return None + + +def sort_column(tree, col, reverse=False): + data = [(tree.set(child, col), child) for child in tree.get_children('')] + data.sort(reverse=reverse) + for index, (_, child) in enumerate(data): + tree.move(child, '', index) + + +def make_sortable(tree): + for col in tree["columns"]: + tree.heading(col, text=col, command=lambda c=col: sort_column(tree, c)) + + +class ZordonClient: + + def __init__(self): + + # Create a Treeview widget + self.root = tk.Tk() + self.tree = ttk.Treeview(self.root, show="headings") + self.server_proxy = RenderServerProxy(hostname='localhost') + self.job_cache = [] + + # Define the columns + self.tree["columns"] = ("id", "Name", "Renderer", "Priority", "Status", "Time Elapsed", "Frames") + + # Format the columns + self.tree.column("id", width=0, stretch=False) + self.tree.column("Name", width=50) + self.tree.column("Renderer", width=100, stretch=False) + self.tree.column("Priority", width=50, stretch=False) + self.tree.column("Status", width=100, stretch=False) + self.tree.column("Time Elapsed", width=100, stretch=False) + self.tree.column("Frames", width=50, stretch=False) + + # Create the column headings + for name in self.tree['columns']: + self.tree.heading(name, text=name) + + # Pack the Treeview widget + self.tree.pack(fill=tk.BOTH, expand=False) + + new_job_button = tk.Button(self.root, text="New Job", command=self.show_new_job_window) + new_job_button.pack() + + # Start the Tkinter event loop + self.root.geometry("500x600+300+300") + self.root.maxsize(width=2000, height=1200) + self.root.minsize(width=600, height=600) + + make_sortable(self.tree) + self.start_update_thread() + + def mainloop(self): + self.root.mainloop() + + def start_update_thread(self): + x = threading.Thread(target=self.__background_update) + x.daemon = True + x.start() + + def __background_update(self): + while True: + self.update_jobs(clear_table=False) + time.sleep(1) + + def update_jobs(self, clear_table=False): + + def update_row(tree, id, new_values): + for item in tree.get_children(): + values = tree.item(item, "values") + if values[0] == id: + tree.item(item, values=new_values) + break + if clear_table: + self.tree.delete(*self.tree.get_children()) + self.job_cache = self.server_proxy.get_jobs() + all_jobs = self.job_cache + for job in all_jobs: + display_status = job['status'] if job['status'] != 'running' else job['percent_complete'] + values = (job['id'], job['name'] or os.path.basename(job['input_path']), job['renderer'] + "-" + job['renderer_version'], job['priority'], + display_status, job['time_elapsed'], job['total_frames']) + if self.tree.exists(job['id']): + update_row(self.tree, job['id'], new_values=values) + else: + self.tree.insert("", tk.END, iid=job['id'], values=values) + + def show_new_job_window(self): + new_window = tk.Toplevel(self.root) + new_window.title("New Window") + new_window.geometry("500x600+300+300") + new_window.resizable(False, False) + x = NewJobWindow(parent=new_window, hostname=self.server_proxy.hostname) + x.pack() + + +if __name__ == '__main__': + x = ZordonClient() + x.mainloop() diff --git a/scheduler_gui.py b/lib/client/new_job_window.py similarity index 92% rename from scheduler_gui.py rename to lib/client/new_job_window.py index 33c00ad..87f273e 100755 --- a/scheduler_gui.py +++ b/lib/client/new_job_window.py @@ -13,7 +13,7 @@ import psutil import requests from lib.render_workers.blender_worker import Blender -from lib.utilities.server_helper import post_job_to_server +from server_proxy import RenderServerProxy logger = logging.getLogger() @@ -61,12 +61,12 @@ class ChecklistBox(Frame): return values -class ScheduleJob(Frame): +class NewJobWindow(Frame): - def __init__(self): - super().__init__() + def __init__(self, parent=None, hostname=None): + super().__init__(parent) - self.server_hostname = None + self.server_proxy = RenderServerProxy(hostname=hostname) self.chosen_file = None self.clients = [] self.presets = {} @@ -76,9 +76,6 @@ class ScheduleJob(Frame): self.master.title("Schedule Job") self.pack(fill=BOTH, expand=True) - self.server_button = Button(self, text="", width=6, command=self.request_new_hostname) - self.server_button.pack(fill=X, padx=5, expand=False) - # project frame job_frame = LabelFrame(self, text="Job Settings") job_frame.pack(fill=X, padx=5, pady=5) @@ -156,24 +153,12 @@ class ScheduleJob(Frame): self.custom_args_entry = None self.submit_frame = None - if os.path.exists(prefs_name): - with open(prefs_name, 'r') as file: - hostname = file.read() - server_data = request_data(hostname, 'status', timeout=server_setup_timeout) - if server_data: - self.set_hostname(hostname) - if not self.server_hostname: - server_data = request_data('localhost', 'status', timeout=server_setup_timeout) - if server_data: - self.set_hostname(server_data['host_name']) - else: - self.request_new_hostname() self.fetch_server_data() def fetch_server_data(self): - self.clients = request_data(self.server_hostname, 'clients', timeout=3) or [] - self.renderer_info = request_data(self.server_hostname, 'renderer_info', timeout=3) or {} - self.presets = request_data(self.server_hostname, 'presets', timeout=3) or {} + self.clients = self.server_proxy.request_data('clients', timeout=3) or [] + self.renderer_info = self.server_proxy.request_data('renderer_info', timeout=3) or {} + self.presets = self.server_proxy.request_data('presets', timeout=3) or {} # update clients self.client_combo['values'] = self.clients @@ -388,7 +373,7 @@ class ScheduleJob(Frame): # Submit to server job_list = job_list or [job_json] - result = post_job_to_server(input_path=input_path, job_list=job_list, hostname=client) + result = self.server_proxy.post_job_to_server(input_path=input_path, job_list=job_list) if result.ok: messagebox.showinfo("Success", "Job successfully submitted to server.") else: @@ -405,7 +390,7 @@ def main(): root.geometry("500x600+300+300") root.maxsize(width=1000, height=2000) root.minsize(width=600, height=600) - app = ScheduleJob() + app = NewJobWindow(root) root.mainloop() diff --git a/lib/client/server_proxy.py b/lib/client/server_proxy.py new file mode 100644 index 0000000..91fc3c5 --- /dev/null +++ b/lib/client/server_proxy.py @@ -0,0 +1,54 @@ +import os +import json +import requests +from lib.render_workers.base_worker import RenderStatus + +status_colors = {RenderStatus.ERROR: "red", RenderStatus.CANCELLED: 'orange1', RenderStatus.COMPLETED: 'green', + RenderStatus.NOT_STARTED: "yellow", RenderStatus.SCHEDULED: 'purple', + RenderStatus.RUNNING: 'cyan'} + +categories = [RenderStatus.RUNNING, RenderStatus.ERROR, RenderStatus.NOT_STARTED, RenderStatus.SCHEDULED, + RenderStatus.COMPLETED, RenderStatus.CANCELLED, RenderStatus.UNDEFINED] + + +class RenderServerProxy: + + def __init__(self, hostname=None, server_port="8080"): + self.hostname = hostname + self.port = server_port + self.fetched_status_data = None + + def connect(self): + status = self.request_data('status') + return status + + def request_data(self, payload, timeout=5): + try: + req = requests.get(f'http://{self.hostname}:{self.port}/api/{payload}', timeout=timeout) + if req.ok: + return req.json() + except Exception as e: + pass + return None + + def get_jobs(self): + all_jobs = self.request_data('jobs') + sorted_jobs = [] + if all_jobs: + for status_category in categories: + found_jobs = [x for x in all_jobs if x['status'] == status_category.value] + if found_jobs: + sorted_jobs.extend(found_jobs) + return sorted_jobs + + def get_data(self, timeout=5): + all_data = self.request_data('full_status', timeout=timeout) + return all_data + + def post_job_to_server(self, input_path, job_list): + # Pack job data and submit to server + job_files = {'file': (os.path.basename(input_path), open(input_path, 'rb'), 'application/octet-stream'), + 'json': (None, json.dumps(job_list), 'application/json')} + + req = requests.post(f'http://{self.hostname}:{self.port}/api/add_job', files=job_files) + return req \ No newline at end of file