Communication between GUI and client back to multiprocessing, since Popen yielded 100% CPU :/
This commit is contained in:
parent
1f1c2c4f1e
commit
5468b39bc1
3 changed files with 52 additions and 24 deletions
|
@ -15,6 +15,8 @@ be one of:
|
||||||
import asyncio
|
import asyncio
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
from logging.handlers import QueueHandler
|
||||||
|
from multiprocessing import Queue
|
||||||
import secrets
|
import secrets
|
||||||
import string
|
import string
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -192,7 +194,7 @@ async def handle_connect() -> None:
|
||||||
|
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
logging.info("Connected to server")
|
logger.info("Connected to server")
|
||||||
data = {
|
data = {
|
||||||
"queue": state.queue,
|
"queue": state.queue,
|
||||||
"waiting_room": state.waiting_room,
|
"waiting_room": state.waiting_room,
|
||||||
|
@ -309,6 +311,7 @@ async def handle_search(data: dict[str, Any]) -> None:
|
||||||
:type data: dict[str, Any]
|
:type data: dict[str, Any]
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
|
logger.info(f"Searching for: {data['query']}")
|
||||||
query = data["query"]
|
query = data["query"]
|
||||||
sid = data["sid"]
|
sid = data["sid"]
|
||||||
results_list = await asyncio.gather(*[source.search(query) for source in sources.values()])
|
results_list = await asyncio.gather(*[source.search(query) for source in sources.values()])
|
||||||
|
@ -343,7 +346,7 @@ async def handle_client_registered(data: dict[str, Any]) -> None:
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
if data["success"]:
|
if data["success"]:
|
||||||
logging.info("Registered")
|
logger.info("Registered")
|
||||||
print(f"Join here: {state.config['server']}/{data['room']}")
|
print(f"Join here: {state.config['server']}/{data['room']}")
|
||||||
qr = QRCode(box_size=20, border=2)
|
qr = QRCode(box_size=20, border=2)
|
||||||
qr.add_data(f"{state.config['server']}/{data['room']}")
|
qr.add_data(f"{state.config['server']}/{data['room']}")
|
||||||
|
@ -354,7 +357,7 @@ async def handle_client_registered(data: dict[str, Any]) -> None:
|
||||||
if state.current_source is None: # A possible race condition can occur here
|
if state.current_source is None: # A possible race condition can occur here
|
||||||
await sio.emit("get-first")
|
await sio.emit("get-first")
|
||||||
else:
|
else:
|
||||||
logging.warning("Registration failed")
|
logger.warning("Registration failed")
|
||||||
await sio.disconnect()
|
await sio.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
@ -451,14 +454,24 @@ async def start_client(config: dict[str, Any]) -> None:
|
||||||
state.current_source.player.kill()
|
state.current_source.player.kill()
|
||||||
|
|
||||||
|
|
||||||
def create_async_and_start_client(config: dict[str, Any]) -> None:
|
def create_async_and_start_client(
|
||||||
|
config: dict[str, Any], queue: Optional[Queue[logging.LogRecord]] = None
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Create an asyncio event loop and start the client.
|
Create an asyncio event loop and start the client.
|
||||||
|
|
||||||
|
If a multiprocessing queue is given, the client will log to the queue.
|
||||||
|
|
||||||
:param config: Config options for the client
|
:param config: Config options for the client
|
||||||
:type config: dict[str, Any]
|
:type config: dict[str, Any]
|
||||||
|
:param queue: A multiprocessing queue to log to
|
||||||
|
:type queue: Optional[Queue[LogRecord]]
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if queue is not None:
|
||||||
|
logger.addHandler(QueueHandler(queue))
|
||||||
|
|
||||||
asyncio.run(start_client(config))
|
asyncio.run(start_client(config))
|
||||||
|
|
||||||
|
|
||||||
|
|
53
syng/gui.py
53
syng/gui.py
|
@ -1,6 +1,8 @@
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from multiprocessing import Process
|
import logging
|
||||||
|
from logging.handlers import QueueListener
|
||||||
|
from multiprocessing import Process, Queue
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import os
|
import os
|
||||||
|
@ -12,7 +14,6 @@ import webbrowser
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import secrets
|
import secrets
|
||||||
import string
|
import string
|
||||||
import subprocess
|
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
from PyQt6.QtCore import QTimer
|
from PyQt6.QtCore import QTimer
|
||||||
|
@ -38,7 +39,7 @@ from yaml import dump, load, Loader, Dumper
|
||||||
from qrcode.main import QRCode
|
from qrcode.main import QRCode
|
||||||
import platformdirs
|
import platformdirs
|
||||||
|
|
||||||
from .client import default_config
|
from .client import create_async_and_start_client, default_config
|
||||||
|
|
||||||
from .sources import available_sources
|
from .sources import available_sources
|
||||||
|
|
||||||
|
@ -286,7 +287,7 @@ class SyngGui(QMainWindow):
|
||||||
|
|
||||||
if self.syng_client is not None:
|
if self.syng_client is not None:
|
||||||
self.syng_client.terminate()
|
self.syng_client.terminate()
|
||||||
self.syng_client.wait(1.0)
|
self.syng_client.join(1.0)
|
||||||
self.syng_client.kill()
|
self.syng_client.kill()
|
||||||
|
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
@ -367,7 +368,9 @@ class SyngGui(QMainWindow):
|
||||||
self.setWindowIcon(self.qt_icon)
|
self.setWindowIcon(self.qt_icon)
|
||||||
|
|
||||||
self.syng_server: Optional[Process] = None
|
self.syng_server: Optional[Process] = None
|
||||||
self.syng_client: Optional[subprocess.Popen[bytes]] = None
|
# self.syng_client: Optional[subprocess.Popen[bytes]] = None
|
||||||
|
self.syng_client: Optional[Process] = None
|
||||||
|
self.syng_client_logging_listener: Optional[QueueListener] = None
|
||||||
|
|
||||||
self.configfile = os.path.join(platformdirs.user_config_dir("syng"), "config.yaml")
|
self.configfile = os.path.join(platformdirs.user_config_dir("syng"), "config.yaml")
|
||||||
|
|
||||||
|
@ -416,7 +419,7 @@ class SyngGui(QMainWindow):
|
||||||
|
|
||||||
self.setCentralWidget(self.central_widget)
|
self.setCentralWidget(self.central_widget)
|
||||||
|
|
||||||
# check every 100 ms if client is running
|
# check every 500 ms if client is running
|
||||||
self.timer = QTimer()
|
self.timer = QTimer()
|
||||||
self.timer.timeout.connect(self.check_if_client_is_running)
|
self.timer.timeout.connect(self.check_if_client_is_running)
|
||||||
|
|
||||||
|
@ -440,15 +443,7 @@ class SyngGui(QMainWindow):
|
||||||
self.timer.stop()
|
self.timer.stop()
|
||||||
return
|
return
|
||||||
|
|
||||||
ret = self.syng_client.poll()
|
if not self.syng_client.is_alive():
|
||||||
if ret is not None:
|
|
||||||
_, stderr = self.syng_client.communicate()
|
|
||||||
stderr_lines = stderr.decode("utf-8").strip().split("\n")
|
|
||||||
if stderr_lines and stderr_lines[-1].startswith("Warning"):
|
|
||||||
self.notification_label.setText(stderr_lines[-1])
|
|
||||||
else:
|
|
||||||
self.notification_label.setText("")
|
|
||||||
self.syng_client.wait()
|
|
||||||
self.syng_client = None
|
self.syng_client = None
|
||||||
self.set_client_button_start()
|
self.set_client_button_start()
|
||||||
else:
|
else:
|
||||||
|
@ -461,15 +456,26 @@ class SyngGui(QMainWindow):
|
||||||
self.startbutton.setText("Save and Start")
|
self.startbutton.setText("Save and Start")
|
||||||
|
|
||||||
def start_syng_client(self) -> None:
|
def start_syng_client(self) -> None:
|
||||||
if self.syng_client is None or self.syng_client.poll() is not None:
|
if self.syng_client is None or not self.syng_client.is_alive():
|
||||||
self.save_config()
|
self.save_config()
|
||||||
self.syng_client = subprocess.Popen(["syng", "client"], stderr=subprocess.PIPE)
|
config = self.gather_config()
|
||||||
|
queue: Queue[logging.LogRecord] = multiprocessing.Queue()
|
||||||
|
|
||||||
|
self.syng_client_logging_listener = QueueListener(
|
||||||
|
queue, LoggingLabelHandler(self.notification_label)
|
||||||
|
)
|
||||||
|
self.syng_client_logging_listener.start()
|
||||||
|
|
||||||
|
self.syng_client = multiprocessing.Process(
|
||||||
|
target=create_async_and_start_client, args=[config, queue]
|
||||||
|
)
|
||||||
|
self.syng_client.start()
|
||||||
self.notification_label.setText("")
|
self.notification_label.setText("")
|
||||||
self.timer.start()
|
self.timer.start(500)
|
||||||
self.set_client_button_stop()
|
self.set_client_button_stop()
|
||||||
else:
|
else:
|
||||||
self.syng_client.terminate()
|
self.syng_client.terminate()
|
||||||
self.syng_client.wait(1.0)
|
self.syng_client.join(1.0)
|
||||||
self.syng_client.kill()
|
self.syng_client.kill()
|
||||||
self.set_client_button_start()
|
self.set_client_button_start()
|
||||||
|
|
||||||
|
@ -527,6 +533,15 @@ class SyngGui(QMainWindow):
|
||||||
self.change_qr(syng_server + room)
|
self.change_qr(syng_server + room)
|
||||||
|
|
||||||
|
|
||||||
|
class LoggingLabelHandler(logging.Handler):
|
||||||
|
def __init__(self, label: QLabel):
|
||||||
|
super().__init__()
|
||||||
|
self.label = label
|
||||||
|
|
||||||
|
def emit(self, record: logging.LogRecord) -> None:
|
||||||
|
self.label.setText(self.format(record))
|
||||||
|
|
||||||
|
|
||||||
def run_gui() -> None:
|
def run_gui() -> None:
|
||||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
|
|
||||||
|
|
|
@ -352,7 +352,7 @@ class YoutubeSource(Source):
|
||||||
title=result.title,
|
title=result.title,
|
||||||
artist=result.author,
|
artist=result.author,
|
||||||
album="YouTube",
|
album="YouTube",
|
||||||
duration=result.length,
|
duration=str(result.length),
|
||||||
)
|
)
|
||||||
for result in results
|
for result in results
|
||||||
]
|
]
|
||||||
|
|
Loading…
Add table
Reference in a new issue