Skip to content
This repository has been archived by the owner on Nov 2, 2024. It is now read-only.

Commit

Permalink
Make damaku distribution driven by signals
Browse files Browse the repository at this point in the history
Also fixed test on headless environment like CI
  • Loading branch information
arenekosreal committed Dec 1, 2023
1 parent 3a36074 commit f908298
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 53 deletions.
28 changes: 27 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pytest = "^7.4.3"
pytest-cov = "^4.1.0"
tomli-w = "^1.0.0"
pytest-qt = "^4.2.0"
pytest-xvfb = "^3.0.0"

[tool.poetry.group.dev]
optional=true
Expand Down
108 changes: 62 additions & 46 deletions qdamakuengine/app.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import json
import time
import random
from typing import Any
from PySide6.QtCore import QObject, QThread, Qt, Signal, Slot
from PySide6.QtCore import QObject, QThread, QTimerEvent, Qt, Signal, Slot
from PySide6.QtWidgets import QFrame, QGraphicsDropShadowEffect, QMenu, QVBoxLayout, QWidget
from PySide6.QtGui import QMouseEvent, QContextMenuEvent
from PySide6.QtNetwork import QHostAddress, QLocalServer, QTcpServer, QTcpSocket
Expand All @@ -17,28 +16,42 @@


class _DamakuDistributionSmoothly(QObject):
_damakus: list[tuple[str, str]] = []
_running = False
damakuupdated = Signal(str, str)
add = Signal(str, str)
start = Signal()
started = Signal()
stop = Signal()
stopped = Signal()

def __init__(self) -> None:
super().__init__()
self._damaku_send_wait_secs = get_config().damaku.damaku_send_wait_secs
self._damakus: list[tuple[str, str]] = []
self.start.connect(self._start)
self.stop.connect(self._stop)
self.add.connect(self._add)
self._timer_id = 0

def add(self, damaku: str, sender: str):
@Slot(str, str)
def _add(self, damaku: str, sender: str):
self._damakus.append((damaku, sender))

def start(self):
self._running = True
while self._running:
if len(self._damakus) > 0:
self.damakuupdated.emit(
*self._damakus.pop(0)
)
time.sleep(self._damaku_send_wait_secs)
@Slot()
def _start(self):
self._timer_id = self.startTimer(int(1000*self._damaku_send_wait_secs))
self.started.emit()

def stop(self):
self._running = False
@Slot()
def _stop(self):
self.killTimer(self._timer_id)
self.stopped.emit()

def timerEvent(self, event: QTimerEvent) -> None:
if event.timerId() == self._timer_id and len(self._damakus) > 0:
self.damakuupdated.emit(
*self._damakus.pop(0)
)
return super().timerEvent(event)


class _FramelessWindowWidget(QWidget):
Expand Down Expand Up @@ -86,37 +99,17 @@ def __init__(self):

self.dthread = QThread(self)
self.djob = _DamakuDistributionSmoothly()
self.dthread.started.connect(self.djob.start)
self.dthread.started.connect(self.djob.start.emit)
self.djob.damakuupdated.connect(self.play_damaku)
self.djob.moveToThread(self.dthread)
info("Starting distribution thread...")
self.dthread.start()
try:
info("Starting socket server...")
if self._config.network.address.startswith("local://"):
self.socket = QLocalServer(self)
self.socket.listen(
self._config.network.address.removeprefix("local://"))
address = self.socket.serverName()
elif self._config.network.address.startswith("tcp://"):
self.socket = QTcpServer(self)
self.socket.listen(
QHostAddress(
self._config.network.address.removeprefix("tcp://")),
self._config.network.port
)
address = "tcp://%s:%s" % (
self.socket.serverAddress().toString(), self.socket.serverPort())
else:
raise RuntimeError("Unable to listen %s:%d" %
(self._config.network.address, self._config.network.port))

except:
error("Failed to listen socket.")
raise
if self._config.network.address.startswith("local://"):
self.socket = QLocalServer(self)
elif self._config.network.address.startswith("tcp://"):
self.socket = QTcpServer(self)
else:
self.socket.newConnection.connect(self.handle)
info("Bind to %s successfully." % address)
raise RuntimeError(
"Unable to listen to socket, please check your config.")

@Slot(str, str)
def play_damaku(self, text: str, sender: str):
Expand Down Expand Up @@ -163,7 +156,7 @@ def read():
sender = client.localAddress().toString()
else:
sender = "localhost"
self.djob.add(damaku, sender)
self.djob.add.emit(damaku, sender)
else:
info("No damaku in data")
response["result"] = _err_no_damaku
Expand All @@ -176,24 +169,47 @@ def read():
client.readyRead.connect(read)

def show(self) -> None:
info("Starting distribution thread...")
self.dthread.start()
info("Starting socket server...")
try:
if isinstance(self.socket, QLocalServer):
self.socket.listen(
self._config.network.address.removeprefix("local://"))
address = self.socket.serverName()
else:
self.socket.listen(
QHostAddress(
self._config.network.address.removeprefix("tcp://")),
self._config.network.port
)
address = "tcp://%s:%s" % (
self.socket.serverAddress().toString(), self.socket.serverPort())

except:
error("Failed to listen socket.")
raise
else:
self.socket.newConnection.connect(self.handle)
info("Bind to %s successfully." % address)
self.container.show()
return self.showFullScreen() if self._config.ui.fullscreen else super().show()

def close(self) -> bool:
info("Closing socket...")
self.socket.close()
info("Closing distribution thread...")
self.djob.stop()
self.djob.stop.emit()
self.dthread.quit()
while not self.dthread.isFinished():
if not self.dthread.isFinished():
info("Waiting distribution to be closed...")
self.dthread.wait()
info("Closing App...")
return super().close()

def contextMenuEvent(self, event: QContextMenuEvent) -> None:
menu = QMenu(self)
menu.addAction("&Exit", self.close) # type: ignore
menu.addAction(self.tr("&Exit"), self.close) # type: ignore
menu.move(event.pos())
menu.show()
return super().contextMenuEvent(event)
29 changes: 23 additions & 6 deletions tests/test_app.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
import json
import socket
from PySide6.QtCore import QThread
from PySide6.QtWidgets import QWidget
import pytest
from pytestqt.qtbot import QtBot
from qdamakuengine.app import App, _DamakuDistributionSmoothly
from qdamakuengine.app import App, _DamakuDistributionSmoothly # type:ignore


def test_distribution(qtbot: QtBot):
widget = QWidget()
thread = QThread(widget)
job = _DamakuDistributionSmoothly()
thread.started.connect(job.start)
thread.started.connect(job.start.emit)
job.moveToThread(thread)
thread.start()
widget.show()
with qtbot.waitSignal(job.damakuupdated, timeout=10000):
job.add("Test-Damaku", "Pytest")
job.stop()
qtbot.addWidget(widget)
with qtbot.waitSignal(job.damakuupdated, timeout=10000):
job.add.emit("Test-Damaku", "Pytest")
job.stop.emit()
thread.quit()
while not thread.isFinished():
if not thread.isFinished():
thread.wait()


@pytest.mark.skip(reason="Need more work to know why s.recv hangs.")
def test_app(qtbot: QtBot):
app = App()
app.show()
qtbot.addWidget(app)
# app.close()
with socket.socket() as s:
s.connect(("127.0.0.1", 2333))
s.sendall(json.dumps({"text": "Test-Damaku"}).encode())
data = s.recv(1024).decode()
assert json.loads(data)["result"] == 0
app.dthread.quit()

0 comments on commit f908298

Please sign in to comment.