mirror of
https://github.com/blw1138/Zordon.git
synced 2025-12-17 08:48:13 +00:00
0
lib/client/__init__.py
Normal file
0
lib/client/__init__.py
Normal file
@@ -5,11 +5,13 @@ import tkinter as tk
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import socket
|
import socket
|
||||||
import os
|
import os, sys
|
||||||
from tkinter import ttk, messagebox
|
from tkinter import ttk, messagebox
|
||||||
from PIL import Image, ImageTk
|
from PIL import Image, ImageTk
|
||||||
from new_job_window import NewJobWindow
|
from new_job_window import NewJobWindow
|
||||||
from server_proxy import RenderServerProxy
|
from server_proxy import RenderServerProxy
|
||||||
|
sys.path.append("../")
|
||||||
|
from lib.server.zeroconf_server import ZeroconfServer
|
||||||
|
|
||||||
|
|
||||||
def request_data(server_ip, payload, server_port=8080, timeout=2):
|
def request_data(server_ip, payload, server_port=8080, timeout=2):
|
||||||
@@ -44,15 +46,17 @@ class ZordonClient:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
servers = available_servers()
|
|
||||||
|
|
||||||
# Create a Treeview widget
|
# Create a Treeview widget
|
||||||
self.root = tk.Tk()
|
self.root = tk.Tk()
|
||||||
self.root.title("Zordon Render Client")
|
self.root.title("Zordon Render Client")
|
||||||
self.local_host = socket.gethostname()
|
self.local_host = socket.gethostname()
|
||||||
self.server_proxy = RenderServerProxy(hostname=servers[0])
|
self.server_proxy = RenderServerProxy(hostname=self.local_host)
|
||||||
self.job_cache = []
|
self.job_cache = []
|
||||||
|
|
||||||
|
# Setup zeroconf
|
||||||
|
self.zeroconf = ZeroconfServer("_zordon._tcp.local.", socket.gethostname(), 8080)
|
||||||
|
self.zeroconf.start(listen_only=True)
|
||||||
|
|
||||||
# Setup photo preview
|
# Setup photo preview
|
||||||
photo_pad = tk.Frame(self.root, background="gray")
|
photo_pad = tk.Frame(self.root, background="gray")
|
||||||
photo_pad.pack(fill=tk.BOTH, pady=5, padx=5)
|
photo_pad.pack(fill=tk.BOTH, pady=5, padx=5)
|
||||||
@@ -129,7 +133,7 @@ class ZordonClient:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# update servers
|
# update servers
|
||||||
self.populate_server_tree()
|
self.update_servers()
|
||||||
try:
|
try:
|
||||||
selected_server = self.server_tree.get_children()[0]
|
selected_server = self.server_tree.get_children()[0]
|
||||||
self.server_tree.selection_set(selected_server)
|
self.server_tree.selection_set(selected_server)
|
||||||
@@ -141,15 +145,12 @@ class ZordonClient:
|
|||||||
x.daemon = True
|
x.daemon = True
|
||||||
x.start()
|
x.start()
|
||||||
|
|
||||||
def populate_server_tree(self):
|
|
||||||
servers = available_servers()
|
|
||||||
self.server_tree.delete(*self.server_tree.get_children())
|
|
||||||
for hostname in servers:
|
|
||||||
self.server_tree.insert("", tk.END, iid=hostname, values=(hostname,))
|
|
||||||
|
|
||||||
def server_picked(self, event):
|
def server_picked(self, event):
|
||||||
|
try:
|
||||||
new_hostname = self.server_tree.selection()[0]
|
new_hostname = self.server_tree.selection()[0]
|
||||||
self.server_proxy.hostname = new_hostname
|
self.server_proxy.hostname = new_hostname
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
self.job_cache.clear()
|
self.job_cache.clear()
|
||||||
self.update_jobs(clear_table=True)
|
self.update_jobs(clear_table=True)
|
||||||
|
|
||||||
@@ -218,10 +219,20 @@ class ZordonClient:
|
|||||||
def __background_update(self):
|
def __background_update(self):
|
||||||
while True:
|
while True:
|
||||||
self.update_jobs()
|
self.update_jobs()
|
||||||
time.sleep(1)
|
self.update_servers()
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
def update_servers(self):
|
||||||
|
servers = self.zeroconf.found_clients()
|
||||||
|
if len(servers) < len(self.server_tree.get_children()):
|
||||||
|
self.server_tree.delete(*self.server_tree.get_children())
|
||||||
|
for hostname in servers:
|
||||||
|
if hostname not in self.server_tree.get_children():
|
||||||
|
self.server_tree.insert("", tk.END, iid=hostname, values=(hostname,))
|
||||||
|
|
||||||
def update_jobs(self, clear_table=False):
|
def update_jobs(self, clear_table=False):
|
||||||
|
|
||||||
|
def update_jobs_inner():
|
||||||
def update_row(tree, id, new_values, tags=None):
|
def update_row(tree, id, new_values, tags=None):
|
||||||
for item in tree.get_children():
|
for item in tree.get_children():
|
||||||
values = tree.item(item, "values")
|
values = tree.item(item, "values")
|
||||||
@@ -231,10 +242,8 @@ class ZordonClient:
|
|||||||
job_fetch = self.server_proxy.get_jobs()
|
job_fetch = self.server_proxy.get_jobs()
|
||||||
if job_fetch is not None:
|
if job_fetch is not None:
|
||||||
if len(job_fetch) < len(self.job_cache) or len(job_fetch) == 0:
|
if len(job_fetch) < len(self.job_cache) or len(job_fetch) == 0:
|
||||||
clear_table = True
|
|
||||||
self.job_cache = job_fetch # update the cache only if its good data
|
|
||||||
if clear_table:
|
|
||||||
self.job_tree.delete(*self.job_tree.get_children())
|
self.job_tree.delete(*self.job_tree.get_children())
|
||||||
|
self.job_cache = job_fetch # update the cache only if its good data
|
||||||
for job in self.job_cache:
|
for job in self.job_cache:
|
||||||
display_status = job['status'] if job['status'] != 'running' else \
|
display_status = job['status'] if job['status'] != 'running' else \
|
||||||
('%.0f%%' % (job['percent_complete'] * 100)) # if running, show percentage, otherwise just show status
|
('%.0f%%' % (job['percent_complete'] * 100)) # if running, show percentage, otherwise just show status
|
||||||
@@ -246,10 +255,20 @@ class ZordonClient:
|
|||||||
display_status,
|
display_status,
|
||||||
job['time_elapsed'],
|
job['time_elapsed'],
|
||||||
job['total_frames'])
|
job['total_frames'])
|
||||||
|
try:
|
||||||
if self.job_tree.exists(job['id']):
|
if self.job_tree.exists(job['id']):
|
||||||
update_row(self.job_tree, job['id'], new_values=values, tags=tags)
|
update_row(self.job_tree, job['id'], new_values=values, tags=tags)
|
||||||
else:
|
else:
|
||||||
self.job_tree.insert("", tk.END, iid=job['id'], values=values, tags=tags)
|
self.job_tree.insert("", tk.END, iid=job['id'], values=values, tags=tags)
|
||||||
|
except tk.TclError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if clear_table:
|
||||||
|
self.job_tree.delete(*self.job_tree.get_children())
|
||||||
|
|
||||||
|
x = threading.Thread(target=update_jobs_inner)
|
||||||
|
x.daemon = True
|
||||||
|
x.start()
|
||||||
|
|
||||||
def show_new_job_window(self):
|
def show_new_job_window(self):
|
||||||
new_window = tk.Toplevel(self.root)
|
new_window = tk.Toplevel(self.root)
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ from tkinter.ttk import Frame, Label, Entry, Combobox
|
|||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
import requests
|
import requests
|
||||||
|
import sys
|
||||||
|
sys.path.append('../../')
|
||||||
from lib.render_workers.blender_worker import Blender
|
from lib.render_workers.blender_worker import Blender
|
||||||
from server_proxy import RenderServerProxy
|
from server_proxy import RenderServerProxy
|
||||||
|
|
||||||
|
|||||||
@@ -34,15 +34,16 @@ class RenderServerProxy:
|
|||||||
def request(self, payload, timeout=5):
|
def request(self, payload, timeout=5):
|
||||||
return requests.get(f'http://{self.hostname}:{self.port}/api/{payload}', timeout=timeout)
|
return requests.get(f'http://{self.hostname}:{self.port}/api/{payload}', timeout=timeout)
|
||||||
|
|
||||||
def get_jobs(self):
|
def get_jobs(self, timeout=5):
|
||||||
all_jobs = self.request_data('jobs')
|
all_jobs = self.request_data('jobs', timeout=timeout)
|
||||||
|
if all_jobs is not None:
|
||||||
sorted_jobs = []
|
sorted_jobs = []
|
||||||
if all_jobs:
|
|
||||||
for status_category in categories:
|
for status_category in categories:
|
||||||
found_jobs = [x for x in all_jobs if x['status'] == status_category.value]
|
found_jobs = [x for x in all_jobs if x['status'] == status_category.value]
|
||||||
if found_jobs:
|
if found_jobs:
|
||||||
sorted_jobs.extend(found_jobs)
|
sorted_jobs.extend(found_jobs)
|
||||||
return sorted_jobs
|
all_jobs = sorted_jobs
|
||||||
|
return all_jobs
|
||||||
|
|
||||||
def get_data(self, timeout=5):
|
def get_data(self, timeout=5):
|
||||||
all_data = self.request_data('full_status', timeout=timeout)
|
all_data = self.request_data('full_status', timeout=timeout)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import yaml
|
|||||||
from flask import Flask, request, render_template, send_file, after_this_request, Response, redirect, url_for, abort
|
from flask import Flask, request, render_template, send_file, after_this_request, Response, redirect, url_for, abort
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
|
from lib.server.zeroconf_server import ZeroconfServer
|
||||||
from lib.render_queue import RenderQueue, JobNotFoundError
|
from lib.render_queue import RenderQueue, JobNotFoundError
|
||||||
from lib.render_workers.worker_factory import RenderWorkerFactory
|
from lib.render_workers.worker_factory import RenderWorkerFactory
|
||||||
from lib.render_workers.base_worker import string_to_status, RenderStatus
|
from lib.render_workers.base_worker import string_to_status, RenderStatus
|
||||||
@@ -504,6 +505,9 @@ def start_server(background_thread=False):
|
|||||||
|
|
||||||
logging.info(f"Starting Zordon Render Server - Hostname: '{RenderQueue.hostname}'")
|
logging.info(f"Starting Zordon Render Server - Hostname: '{RenderQueue.hostname}'")
|
||||||
|
|
||||||
|
zeroconf_server = ZeroconfServer("_zordon._tcp.local.", RenderQueue.hostname, RenderQueue.port)
|
||||||
|
zeroconf_server.start()
|
||||||
|
|
||||||
if background_thread:
|
if background_thread:
|
||||||
server_thread = threading.Thread(
|
server_thread = threading.Thread(
|
||||||
target=lambda: server.run(host='0.0.0.0', port=RenderQueue.port, debug=False, use_reloader=False))
|
target=lambda: server.run(host='0.0.0.0', port=RenderQueue.port, debug=False, use_reloader=False))
|
||||||
|
|||||||
73
lib/server/zeroconf_server.py
Normal file
73
lib/server/zeroconf_server.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from zeroconf import Zeroconf, ServiceInfo, ServiceBrowser, ServiceStateChange
|
||||||
|
|
||||||
|
logger = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
class ZeroconfServer():
|
||||||
|
def __init__(self, service_type, server_name, server_port):
|
||||||
|
self.service_type = service_type
|
||||||
|
self.server_name = server_name
|
||||||
|
self.server_port = server_port
|
||||||
|
self.server_ip = None
|
||||||
|
self.zeroconf = Zeroconf()
|
||||||
|
self.service_info = None
|
||||||
|
self.client_cache = {}
|
||||||
|
self.properties = {}
|
||||||
|
|
||||||
|
def start(self, listen_only=False):
|
||||||
|
if not listen_only:
|
||||||
|
self._register_service()
|
||||||
|
self._browse_services()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self._unregister_service()
|
||||||
|
self.zeroconf.close()
|
||||||
|
|
||||||
|
def _register_service(self):
|
||||||
|
self.server_ip = socket.gethostbyname(socket.gethostname())
|
||||||
|
|
||||||
|
info = ServiceInfo(
|
||||||
|
self.service_type,
|
||||||
|
f"{self.server_name}.{self.service_type}",
|
||||||
|
addresses=[socket.inet_aton(self.server_ip)],
|
||||||
|
port=self.server_port,
|
||||||
|
properties=self.properties,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.service_info = info
|
||||||
|
self.zeroconf.register_service(info)
|
||||||
|
logger.info(f"Registered zeroconf service: {self.service_info.name}")
|
||||||
|
|
||||||
|
def _unregister_service(self):
|
||||||
|
if self.service_info:
|
||||||
|
self.zeroconf.unregister_service(self.service_info)
|
||||||
|
logger.info(f"Unregistered zeroconf service: {self.service_info.name}")
|
||||||
|
self.service_info = None
|
||||||
|
|
||||||
|
def _browse_services(self):
|
||||||
|
browser = ServiceBrowser(self.zeroconf, self.service_type, [self._on_service_discovered])
|
||||||
|
|
||||||
|
def _on_service_discovered(self, zeroconf, service_type, name, state_change):
|
||||||
|
info = zeroconf.get_service_info(service_type, name)
|
||||||
|
logger.debug(f"Zeroconf: {name} {state_change}")
|
||||||
|
if service_type == self.service_type:
|
||||||
|
if state_change == ServiceStateChange.Added or state_change == ServiceStateChange.Updated:
|
||||||
|
self.client_cache[name] = info
|
||||||
|
else:
|
||||||
|
self.client_cache.pop(name)
|
||||||
|
|
||||||
|
def found_clients(self):
|
||||||
|
return [x.split(f'.{self.service_type}')[0] for x in self.client_cache.keys()]
|
||||||
|
|
||||||
|
|
||||||
|
# Example usage:
|
||||||
|
if __name__ == "__main__":
|
||||||
|
server = ZeroconfServer("_zordon._tcp.local.", "foobar.local", 8080)
|
||||||
|
try:
|
||||||
|
server.start()
|
||||||
|
input("Server running - Press enter to end")
|
||||||
|
finally:
|
||||||
|
server.stop()
|
||||||
@@ -11,3 +11,4 @@ future==0.18.3
|
|||||||
json2html~=1.3.0
|
json2html~=1.3.0
|
||||||
SQLAlchemy~=2.0.15
|
SQLAlchemy~=2.0.15
|
||||||
Pillow~=9.3.0
|
Pillow~=9.3.0
|
||||||
|
zeroconf~=0.63.0
|
||||||
Reference in New Issue
Block a user