Cleaning up the event dispatcher system.
This commit is contained in:
parent
84f623f2e4
commit
09c5d7794d
13 changed files with 275 additions and 188 deletions
|
|
@ -492,10 +492,10 @@ class Download:
|
||||||
if "error" == self.info.status and "error" in status:
|
if "error" == self.info.status and "error" in status:
|
||||||
self.info.error = status.get("error")
|
self.info.error = status.get("error")
|
||||||
await self._notify.emit(
|
await self._notify.emit(
|
||||||
Events.ERROR,
|
Events.LOG_ERROR,
|
||||||
data={"message": self.info.error, "data": self.info},
|
data=self.info,
|
||||||
title="Download Error",
|
title="Download Error",
|
||||||
message=f"Error in download task '{self.info.title}': {self.info.error}",
|
message=f"'{self.info.title}' failed to download: {self.info.error}",
|
||||||
)
|
)
|
||||||
|
|
||||||
if "downloaded_bytes" in status and status.get("downloaded_bytes") > 0:
|
if "downloaded_bytes" in status and status.get("downloaded_bytes") > 0:
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,6 @@ from .config import Config
|
||||||
from .DataStore import DataStore, StoreType
|
from .DataStore import DataStore, StoreType
|
||||||
from .Download import Download
|
from .Download import Download
|
||||||
from .Events import EventBus, Events
|
from .Events import EventBus, Events
|
||||||
from .Events import info as event_info
|
|
||||||
from .Events import warning as event_warning
|
|
||||||
from .ItemDTO import Item, ItemDTO
|
from .ItemDTO import Item, ItemDTO
|
||||||
from .Presets import Presets
|
from .Presets import Presets
|
||||||
from .Scheduler import Scheduler
|
from .Scheduler import Scheduler
|
||||||
|
|
@ -452,39 +450,48 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dlInfo: Download = Download(info=dl, info_dict=entry if item.auto_start else None, logs=logs)
|
dlInfo: Download = Download(info=dl, info_dict=entry if item.auto_start else None, logs=logs)
|
||||||
|
notifyTitle: str | None = None
|
||||||
|
notifyMessage: str | None = None
|
||||||
|
|
||||||
text_logs: str = ""
|
text_logs: str = ""
|
||||||
if filtered_logs := extract_ytdlp_logs(logs):
|
if filtered_logs := extract_ytdlp_logs(logs):
|
||||||
text_logs = f" Logs: {', '.join(filtered_logs)}"
|
text_logs = f" Logs: {', '.join(filtered_logs)}"
|
||||||
|
|
||||||
if "is_upcoming" == entry.get("live_status"):
|
if "is_upcoming" == entry.get("live_status"):
|
||||||
NotifyEvent = Events.COMPLETED
|
notifyEvent = Events.COMPLETED
|
||||||
|
notifyTitle = "Upcoming Premiere" if is_premiere else "Upcoming Live Stream"
|
||||||
|
notifyMessage = f"{'Premiere video' if is_premiere else 'Stream' } '{dlInfo.info.title}' is not available yet. {text_logs}"
|
||||||
|
|
||||||
dlInfo.info.status = "not_live"
|
dlInfo.info.status = "not_live"
|
||||||
dlInfo.info.msg = f"{'Premiere video' if is_premiere else 'Stream' } is not available yet." + text_logs
|
dlInfo.info.msg = notifyMessage.replace(f" '{dlInfo.info.title}'", "")
|
||||||
|
|
||||||
await self._notify.emit(
|
await self._notify.emit(
|
||||||
Events.LOG_INFO,
|
Events.LOG_INFO,
|
||||||
data=event_info(dlInfo.info.msg, {"lowPriority": True}),
|
data={"lowPriority": True},
|
||||||
title="Premiere video" if is_premiere else "Live Stream",
|
title=notifyTitle,
|
||||||
message=f"Item '{dlInfo.info.title}' is not available yet. {dlInfo.info.msg}",
|
message=notifyMessage,
|
||||||
)
|
)
|
||||||
|
|
||||||
itemDownload: Download = self.done.put(dlInfo)
|
itemDownload: Download = self.done.put(dlInfo)
|
||||||
elif len(entry.get("formats", [])) < 1:
|
elif len(entry.get("formats", [])) < 1:
|
||||||
availability: str = entry.get("availability", "public")
|
ava: str = entry.get("availability", "public")
|
||||||
msg: str = "No formats found."
|
notifyTitle = "Download Error"
|
||||||
if availability and availability not in ("public",):
|
notifyMessage: str = f"No formats for '{dl.title}'."
|
||||||
msg += f" Availability is set for '{availability}'."
|
if ava and ava not in ("public",):
|
||||||
|
notifyMessage += f" Availability is set for '{ava}'."
|
||||||
|
|
||||||
dlInfo.info.error = msg + text_logs
|
dlInfo.info.error = notifyMessage.replace(f" for '{dl.title}'.", ".") + text_logs
|
||||||
dlInfo.info.status = "error"
|
dlInfo.info.status = "error"
|
||||||
itemDownload = self.done.put(dlInfo)
|
itemDownload = self.done.put(dlInfo)
|
||||||
NotifyEvent = Events.COMPLETED
|
notifyEvent = Events.COMPLETED
|
||||||
await self._notify.emit(
|
await self._notify.emit(
|
||||||
Events.LOG_WARNING,
|
Events.LOG_WARNING,
|
||||||
data=event_warning(f"No formats found for '{dl.title}'."),
|
data={"logs": text_logs},
|
||||||
title="No Formats Found",
|
title=notifyTitle,
|
||||||
message=f"No formats found for '{dl.title}'. {dlInfo.info.error}",
|
message=notifyMessage,
|
||||||
)
|
)
|
||||||
elif is_premiere and self.config.prevent_live_premiere:
|
elif is_premiere and self.config.prevent_live_premiere:
|
||||||
|
notifyTitle = "Premiere Video"
|
||||||
dlInfo.info.error = "Premiering right now."
|
dlInfo.info.error = "Premiering right now."
|
||||||
|
|
||||||
_requeue = True
|
_requeue = True
|
||||||
|
|
@ -496,6 +503,7 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
)
|
)
|
||||||
starts_in = starts_in + timedelta(minutes=5, seconds=dl.extras.get("duration", 0))
|
starts_in = starts_in + timedelta(minutes=5, seconds=dl.extras.get("duration", 0))
|
||||||
dlInfo.info.error += f" Download will start at {starts_in.astimezone().isoformat()}."
|
dlInfo.info.error += f" Download will start at {starts_in.astimezone().isoformat()}."
|
||||||
|
notifyMessage = dlInfo.info.error.strip()
|
||||||
_requeue = False
|
_requeue = False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(f"Failed to parse live_in date '{release_in}'. {e!s}")
|
LOG.error(f"Failed to parse live_in date '{release_in}'. {e!s}")
|
||||||
|
|
@ -503,25 +511,24 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
else:
|
else:
|
||||||
dlInfo.info.error += f" Delaying download by '{300+dl.extras.get('duration',0)}' seconds."
|
dlInfo.info.error += f" Delaying download by '{300+dl.extras.get('duration',0)}' seconds."
|
||||||
|
|
||||||
|
notifyMessage = dlInfo.info.error.strip()
|
||||||
|
|
||||||
if _requeue:
|
if _requeue:
|
||||||
NotifyEvent = Events.ADDED
|
notifyEvent = Events.ADDED
|
||||||
itemDownload = self.queue.put(dlInfo)
|
itemDownload = self.queue.put(dlInfo)
|
||||||
if item.auto_start:
|
if item.auto_start:
|
||||||
self.event.set()
|
self.event.set()
|
||||||
else:
|
|
||||||
LOG.debug(f"Item {itemDownload.info.name()} is not set to auto-start.")
|
|
||||||
else:
|
else:
|
||||||
dlInfo.info.status = "not_live"
|
dlInfo.info.status = "not_live"
|
||||||
itemDownload = self.done.put(dlInfo)
|
itemDownload = self.done.put(dlInfo)
|
||||||
NotifyEvent = Events.COMPLETED
|
notifyEvent = Events.COMPLETED
|
||||||
await self._notify.emit(
|
notifyTitle = "Item Not Live"
|
||||||
Events.LOG_INFO,
|
notifyMessage = f"Item '{dlInfo.info.title}' is not live."
|
||||||
data=event_info(f"'{dl.title}' is {dlInfo.info.error}.", {"lowPriority": True}),
|
await self._notify.emit(Events.LOG_INFO, title=notifyTitle, message=notifyMessage)
|
||||||
title="Item Not Live",
|
|
||||||
message=f"Item '{dl.title}' is not live. {dlInfo.info.error}",
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
NotifyEvent = Events.ADDED
|
notifyEvent = Events.ADDED
|
||||||
|
notifyTitle = "Item Added"
|
||||||
|
notifyMessage = f"Item '{dlInfo.info.title}' has been added to the download queue."
|
||||||
itemDownload = self.queue.put(dlInfo)
|
itemDownload = self.queue.put(dlInfo)
|
||||||
if item.auto_start:
|
if item.auto_start:
|
||||||
self.event.set()
|
self.event.set()
|
||||||
|
|
@ -529,10 +536,10 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
LOG.debug(f"Item {itemDownload.info.name()} is not set to auto-start.")
|
LOG.debug(f"Item {itemDownload.info.name()} is not set to auto-start.")
|
||||||
|
|
||||||
await self._notify.emit(
|
await self._notify.emit(
|
||||||
NotifyEvent,
|
notifyEvent,
|
||||||
data=itemDownload.info.serialize(),
|
data=itemDownload.info.serialize(),
|
||||||
title="Item Added" if Events.ADDED == NotifyEvent else None,
|
title=notifyTitle,
|
||||||
message=f"Added '{itemDownload.info.title}'." if NotifyEvent == Events.ADDED else None,
|
message=notifyMessage,
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|
@ -740,14 +747,18 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
self.queue.delete(id)
|
self.queue.delete(id)
|
||||||
await self._notify.emit(
|
await self._notify.emit(
|
||||||
Events.CANCELLED,
|
Events.CANCELLED,
|
||||||
data=item.info.serialize(),
|
data=item.info,
|
||||||
title="Download Cancelled",
|
title="Download Cancelled",
|
||||||
message=f"Download '{item.info.title}' has been cancelled.",
|
message=f"Download '{item.info.title}' has been cancelled.",
|
||||||
)
|
)
|
||||||
item.info.status = "cancelled"
|
item.info.status = "cancelled"
|
||||||
# item.info.error = "Cancelled by user."
|
|
||||||
self.done.put(item)
|
self.done.put(item)
|
||||||
await self._notify.emit(Events.COMPLETED, data=item.info.serialize())
|
await self._notify.emit(
|
||||||
|
Events.COMPLETED,
|
||||||
|
data=item.info,
|
||||||
|
title="Download Cancelled",
|
||||||
|
message=f"Download '{item.info.title}' has been cancelled.",
|
||||||
|
)
|
||||||
LOG.info(f"Deleted from queue {item_ref}")
|
LOG.info(f"Deleted from queue {item_ref}")
|
||||||
|
|
||||||
status[id] = "ok"
|
status[id] = "ok"
|
||||||
|
|
@ -815,11 +826,13 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
LOG.error(f"Unable to remove '{itemRef}' local file '{filename}'. {e!s}")
|
LOG.error(f"Unable to remove '{itemRef}' local file '{filename}'. {e!s}")
|
||||||
|
|
||||||
self.done.delete(id)
|
self.done.delete(id)
|
||||||
await self._notify.emit(
|
|
||||||
|
_status: str = "Removed" if removed_files > 0 else "Cleared"
|
||||||
|
self._notify.offload(
|
||||||
Events.CLEARED,
|
Events.CLEARED,
|
||||||
data=item.info.serialize(),
|
data=item.info,
|
||||||
title="Download Cleared",
|
title=f"Download {_status}",
|
||||||
message=f"Cleared download '{item.info.title}' from history.",
|
message=f"{_status} '{item.info.title}' from history.",
|
||||||
)
|
)
|
||||||
|
|
||||||
msg = f"Deleted completed download '{itemRef}'."
|
msg = f"Deleted completed download '{itemRef}'."
|
||||||
|
|
@ -857,6 +870,27 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
def get_item(self, id: str) -> Download | None:
|
||||||
|
"""
|
||||||
|
Get a specific item from the download queue or history.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
id (str): The ID of the item to retrieve.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Download | None: The requested item if found, otherwise None.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.queue.get(key=id)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self.done.get(key=id)
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
async def _download_pool(self) -> None:
|
async def _download_pool(self) -> None:
|
||||||
"""
|
"""
|
||||||
Create a pool of workers to download the files.
|
Create a pool of workers to download the files.
|
||||||
|
|
@ -931,25 +965,25 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
LOG.debug(f"Download Task '{id}' is completed. Removing from queue.")
|
LOG.debug(f"Download Task '{id}' is completed. Removing from queue.")
|
||||||
self.queue.delete(key=id)
|
self.queue.delete(key=id)
|
||||||
|
|
||||||
|
notifyTitle: str | None = None
|
||||||
|
notifyMessage: str | None = None
|
||||||
|
|
||||||
if entry.is_cancelled() is True:
|
if entry.is_cancelled() is True:
|
||||||
await self._notify.emit(
|
await self._notify.emit(
|
||||||
Events.CANCELLED,
|
Events.CANCELLED,
|
||||||
data=entry.info.serialize(),
|
data=entry.info,
|
||||||
title="Download Cancelled",
|
title="Download Cancelled",
|
||||||
message=f"Download '{entry.info.title}' has been cancelled.",
|
message=f"Download '{entry.info.title}' has been cancelled.",
|
||||||
)
|
)
|
||||||
entry.info.status = "cancelled"
|
entry.info.status = "cancelled"
|
||||||
# entry.info.error = "Cancelled by user."
|
notifyTitle = "Download Cancelled"
|
||||||
|
notifyMessage = f"Download '{entry.info.title}' has been cancelled."
|
||||||
|
elif entry.info.status == "finished":
|
||||||
|
notifyTitle = "Download Completed"
|
||||||
|
notifyMessage = f"Download '{entry.info.title}' has been completed."
|
||||||
|
|
||||||
self.done.put(value=entry)
|
self.done.put(value=entry)
|
||||||
await self._notify.emit(
|
await self._notify.emit(Events.COMPLETED, data=entry.info, title=notifyTitle, message=notifyMessage)
|
||||||
Events.COMPLETED,
|
|
||||||
data=entry.info.serialize(),
|
|
||||||
title="Download Completed" if entry.info.status == "finished" else None,
|
|
||||||
message=f"Download '{entry.info.title}' has been completed."
|
|
||||||
if entry.info.status == "finished"
|
|
||||||
else None,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
LOG.warning(f"Download '{id}' not found in queue.")
|
LOG.warning(f"Download '{id}' not found in queue.")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,86 +6,10 @@ from collections.abc import Awaitable
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from .BackgroundWorker import BackgroundWorker
|
||||||
from .Singleton import Singleton
|
from .Singleton import Singleton
|
||||||
|
|
||||||
LOG = logging.getLogger("Events")
|
LOG: logging.Logger = logging.getLogger("Events")
|
||||||
|
|
||||||
|
|
||||||
def error(msg: str, data: dict | None = None) -> dict:
|
|
||||||
"""
|
|
||||||
Create an error message.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (str): The message.
|
|
||||||
data (dict|None): The data to include in the message.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict : The message wrapped in a dictionary.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return message("error", msg, data)
|
|
||||||
|
|
||||||
|
|
||||||
def warning(msg: str, data: dict | None = None) -> dict:
|
|
||||||
"""
|
|
||||||
Create an error message.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (str): The message.
|
|
||||||
data (dict|None): The data to include in the message.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict : The message wrapped in a dictionary.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return message("warning", msg, data)
|
|
||||||
|
|
||||||
|
|
||||||
def info(msg: str, data: dict | None = None) -> dict:
|
|
||||||
"""
|
|
||||||
Create an info message.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (str): The message.
|
|
||||||
data (dict|None): The data to include in the message.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict : The message wrapped in a dictionary.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return message("info", msg, data)
|
|
||||||
|
|
||||||
|
|
||||||
def success(msg: str, data: dict | None = None) -> dict:
|
|
||||||
"""
|
|
||||||
Create a success message.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (str): The message.
|
|
||||||
data (dict|None): The data to include in the message.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict : The message wrapped in a dictionary.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return message("success", msg, data)
|
|
||||||
|
|
||||||
|
|
||||||
def message(type: str, message: str, data: dict | None = None) -> dict:
|
|
||||||
"""
|
|
||||||
Create a message.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
type (str): The type of the message.
|
|
||||||
message (str): The message.
|
|
||||||
data (dict|None): The data to include in the message.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict : The message wrapped in a dictionary.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return {"type": type, "message": message, "data": data if data else {}}
|
|
||||||
|
|
||||||
|
|
||||||
class Events:
|
class Events:
|
||||||
"""
|
"""
|
||||||
|
|
@ -185,7 +109,7 @@ class Events:
|
||||||
list: The list of debug events.
|
list: The list of debug events.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return [Events.UPDATED]
|
return [Events.UPDATED, Events.CLI_OUTPUT]
|
||||||
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
|
|
@ -209,7 +133,7 @@ class Event:
|
||||||
message: str | None = None
|
message: str | None = None
|
||||||
"""The message of the event, if any."""
|
"""The message of the event, if any."""
|
||||||
|
|
||||||
data: any
|
data: Any
|
||||||
"""The data that was passed to the event."""
|
"""The data that was passed to the event."""
|
||||||
|
|
||||||
def serialize(self) -> dict:
|
def serialize(self) -> dict:
|
||||||
|
|
@ -277,12 +201,16 @@ class EventBus(metaclass=Singleton):
|
||||||
debug: bool = False
|
debug: bool = False
|
||||||
"""Whether to log debug messages or not."""
|
"""Whether to log debug messages or not."""
|
||||||
|
|
||||||
|
_offload: BackgroundWorker
|
||||||
|
"""The background worker to offload tasks to."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
EventBus._instance = self
|
EventBus._instance = self
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
|
|
||||||
self.debug = Config.get_instance().debug
|
self.debug = Config.get_instance().debug
|
||||||
|
self._offload = BackgroundWorker.get_instance()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_instance() -> "EventBus":
|
def get_instance() -> "EventBus":
|
||||||
|
|
@ -368,13 +296,24 @@ class EventBus(metaclass=Singleton):
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def sync_emit(self, event: str, data: Any, loop=None, wait: bool = True, **kwargs):
|
def sync_emit(
|
||||||
|
self,
|
||||||
|
event: str,
|
||||||
|
data: Any | None = None,
|
||||||
|
title: str | None = None,
|
||||||
|
message: str | None = None,
|
||||||
|
loop=None,
|
||||||
|
wait: bool = True,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Emit event and (optionally) wait for results.
|
Emit event and (optionally) wait for results.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event (str): The event to emit.
|
event (str): The event to emit.
|
||||||
data (Any): The data to pass to the event.
|
data (Any|None): The data to pass to the event.
|
||||||
|
title (str | None): The title of the event, if any.
|
||||||
|
message (str | None): The message of the event, if any.
|
||||||
loop (asyncio.AbstractEventLoop | None): The event loop to use. If None, the current running loop is used.
|
loop (asyncio.AbstractEventLoop | None): The event loop to use. If None, the current running loop is used.
|
||||||
wait (bool): Whether to wait for the results of the event handlers. Defaults to True.
|
wait (bool): Whether to wait for the results of the event handlers. Defaults to True.
|
||||||
**kwargs: Additional keyword arguments to pass to the event
|
**kwargs: Additional keyword arguments to pass to the event
|
||||||
|
|
@ -389,8 +328,11 @@ class EventBus(metaclass=Singleton):
|
||||||
if event not in self._listeners:
|
if event not in self._listeners:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
data = {}
|
||||||
|
|
||||||
async def emit_all():
|
async def emit_all():
|
||||||
ev = Event(event=event, data=data)
|
ev = Event(event=event, title=title, message=message, data=data)
|
||||||
LOG.debug(f"Emitting event '{ev.id}: {ev.event}'.", extra={"data": data})
|
LOG.debug(f"Emitting event '{ev.id}: {ev.event}'.", extra={"data": data})
|
||||||
|
|
||||||
res: list = []
|
res: list = []
|
||||||
|
|
@ -428,14 +370,14 @@ class EventBus(metaclass=Singleton):
|
||||||
return fut.result() if wait else fut
|
return fut.result() if wait else fut
|
||||||
|
|
||||||
async def emit(
|
async def emit(
|
||||||
self, event: str, data: Any, title: str | None = None, message: str | None = None, **kwargs
|
self, event: str, data: Any | None = None, title: str | None = None, message: str | None = None, **kwargs
|
||||||
) -> Awaitable:
|
) -> Awaitable:
|
||||||
"""
|
"""
|
||||||
Emit an event.
|
Emit an event.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event (str): The event to emit.
|
event (str): The event to emit.
|
||||||
data (Any): The data to pass to the event.
|
data (Any|None): The data to pass to the event.
|
||||||
title (str | None): The title of the event, if any.
|
title (str | None): The title of the event, if any.
|
||||||
message (str | None): The message of the event, if any.
|
message (str | None): The message of the event, if any.
|
||||||
**kwargs: The keyword arguments to pass to the event.
|
**kwargs: The keyword arguments to pass to the event.
|
||||||
|
|
@ -447,7 +389,10 @@ class EventBus(metaclass=Singleton):
|
||||||
if event not in self._listeners:
|
if event not in self._listeners:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
ev = Event(event=event, data=data, title=title, message=message)
|
if not data:
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
ev = Event(event=event, title=title, message=message, data=data)
|
||||||
|
|
||||||
if self.debug or event not in Events.only_debug():
|
if self.debug or event not in Events.only_debug():
|
||||||
LOG.debug(f"Emitting event '{ev.id}: {ev.event}'.", extra={"data": data})
|
LOG.debug(f"Emitting event '{ev.id}: {ev.event}'.", extra={"data": data})
|
||||||
|
|
@ -461,3 +406,35 @@ class EventBus(metaclass=Singleton):
|
||||||
LOG.error(f"Failed to emit event '{ev.event}' to '{handler.name}'. Error message '{e!s}'.")
|
LOG.error(f"Failed to emit event '{ev.event}' to '{handler.name}'. Error message '{e!s}'.")
|
||||||
|
|
||||||
return asyncio.gather(*tasks)
|
return asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
def offload(
|
||||||
|
self, event: str, title: str | None = None, message: str | None = None, data: Any | None = None, **kwargs
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Offload an dispatching event to a background worker.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event (str): The event to offload.
|
||||||
|
data (Any|None): The data to pass to the event.
|
||||||
|
title (str | None): The title of the event, if any.
|
||||||
|
message (str | None): The message of the event, if any.
|
||||||
|
**kwargs: Additional keyword arguments to pass to the event.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if event not in self._listeners:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
ev = Event(event=event, title=title, message=message, data=data)
|
||||||
|
|
||||||
|
if self.debug or event not in Events.only_debug():
|
||||||
|
LOG.debug(f"Offloading event '{ev.id}: {ev.event}'.", extra={"data": data})
|
||||||
|
|
||||||
|
for handler in self._listeners[event].values():
|
||||||
|
try:
|
||||||
|
self._offload.submit(handler.handle, ev, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
LOG.error(f"Failed to offload event '{ev.event}' to '{handler.name}'. Error message '{e!s}'.")
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class HttpSocket:
|
||||||
self.rootPath = root_path
|
self.rootPath = root_path
|
||||||
|
|
||||||
def emit(e: Event, _, **kwargs):
|
def emit(e: Event, _, **kwargs):
|
||||||
return self.sio.emit(event=e.event, data=encoder.encode(e.data), **kwargs)
|
return self.sio.emit(event=e.event, data=encoder.encode(e), **kwargs)
|
||||||
|
|
||||||
services = Services.get_instance()
|
services = Services.get_instance()
|
||||||
services.add_all(
|
services.add_all(
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ from app.library.Services import Services
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .encoder import Encoder
|
from .encoder import Encoder
|
||||||
from .Events import EventBus, Events, error, success
|
from .Events import EventBus, Events
|
||||||
from .Scheduler import Scheduler
|
from .Scheduler import Scheduler
|
||||||
from .Singleton import Singleton
|
from .Singleton import Singleton
|
||||||
from .Utils import init_class, validate_url
|
from .Utils import init_class, validate_url
|
||||||
|
|
@ -325,7 +325,7 @@ class Tasks(metaclass=Singleton):
|
||||||
"template": template,
|
"template": template,
|
||||||
"cli": cli,
|
"cli": cli,
|
||||||
},
|
},
|
||||||
title=f"Task '{task.name}' started",
|
title="Tasks",
|
||||||
message=f"Task '{task.name}' started at '{timeNow}'",
|
message=f"Task '{task.name}' started at '{timeNow}'",
|
||||||
id=task.id,
|
id=task.id,
|
||||||
)
|
)
|
||||||
|
|
@ -337,19 +337,16 @@ class Tasks(metaclass=Singleton):
|
||||||
|
|
||||||
await self._notify.emit(
|
await self._notify.emit(
|
||||||
Events.LOG_SUCCESS,
|
Events.LOG_SUCCESS,
|
||||||
data=success(
|
data={"lowPriority": True},
|
||||||
f"Task '{task.name}' completed in '{ended - started:.2f}' seconds.", data={"lowPriority": True}
|
title="Task completed",
|
||||||
),
|
message=f"Task '{task.name}' completed in '{ended - started:.2f}'.",
|
||||||
title=f"Task '{task.name}' completed",
|
|
||||||
message=f"Task '{task.name}' completed at '{timeNow}'",
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(f"Failed to execute '{task.name}' at '{timeNow}'. '{e!s}'.")
|
LOG.error(f"Failed to execute '{task.name}' at '{timeNow}'. '{e!s}'.")
|
||||||
await self._notify.emit(
|
await self._notify.emit(
|
||||||
Events.ERROR,
|
Events.LOG_ERROR,
|
||||||
data=error(f"Failed to execute '{task.name}' at '{timeNow}'. '{e!s}'."),
|
title="Task failed",
|
||||||
title=f"Task '{task.name}' failed",
|
message=f"Failed to execute '{task.name}'. '{e!s}'",
|
||||||
message=str(e),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
23
app/main.py
23
app/main.py
|
|
@ -111,7 +111,12 @@ class Main:
|
||||||
host = host or self._config.host
|
host = host or self._config.host
|
||||||
port = port or self._config.port
|
port = port or self._config.port
|
||||||
|
|
||||||
EventBus.get_instance().sync_emit(Events.STARTUP, data={"app": self._app})
|
EventBus.get_instance().sync_emit(
|
||||||
|
Events.STARTUP,
|
||||||
|
data={"app": self._app},
|
||||||
|
title="Application Startup",
|
||||||
|
message="The application is starting up.",
|
||||||
|
)
|
||||||
Scheduler.get_instance().attach(self._app)
|
Scheduler.get_instance().attach(self._app)
|
||||||
|
|
||||||
self._socket.attach(self._app)
|
self._socket.attach(self._app)
|
||||||
|
|
@ -123,7 +128,12 @@ class Main:
|
||||||
Notification.get_instance().attach(self._app)
|
Notification.get_instance().attach(self._app)
|
||||||
Conditions.get_instance().attach(self._app)
|
Conditions.get_instance().attach(self._app)
|
||||||
|
|
||||||
EventBus.get_instance().sync_emit(Events.LOADED, data={"app": self._app})
|
EventBus.get_instance().sync_emit(
|
||||||
|
Events.LOADED,
|
||||||
|
data={"app": self._app},
|
||||||
|
title="Application Loaded",
|
||||||
|
message="The application has loaded all components.",
|
||||||
|
)
|
||||||
|
|
||||||
def started(_):
|
def started(_):
|
||||||
LOG.info("=" * 40)
|
LOG.info("=" * 40)
|
||||||
|
|
@ -132,7 +142,14 @@ class Main:
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
EventBus.get_instance().sync_emit(Events.STARTED, data={"app": self._app}, loop=loop, wait=False)
|
EventBus.get_instance().sync_emit(
|
||||||
|
Events.STARTED,
|
||||||
|
data={"app": self._app},
|
||||||
|
title="Application Started",
|
||||||
|
message="The application has started successfully.",
|
||||||
|
loop=loop,
|
||||||
|
wait=False,
|
||||||
|
)
|
||||||
|
|
||||||
if loop and self._config.debug:
|
if loop and self._config.debug:
|
||||||
loop.set_debug(True)
|
loop.set_debug(True)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from aiohttp import web
|
||||||
from aiohttp.web import Request, Response
|
from aiohttp.web import Request, Response
|
||||||
|
|
||||||
from app.library.encoder import Encoder
|
from app.library.encoder import Encoder
|
||||||
from app.library.Events import EventBus, Events, message
|
from app.library.Events import EventBus, Events
|
||||||
from app.library.Notifications import Notification, NotificationEvents
|
from app.library.Notifications import Notification, NotificationEvents
|
||||||
from app.library.router import route
|
from app.library.router import route
|
||||||
from app.library.Utils import validate_uuid
|
from app.library.Utils import validate_uuid
|
||||||
|
|
@ -104,8 +104,6 @@ async def notification_test(encoder: Encoder, notify: EventBus) -> Response:
|
||||||
Response: The response object.
|
Response: The response object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
data = message("test", "This is a test notification.")
|
await notify.emit(Events.TEST, title="Test Notification", message="This is a test notification.")
|
||||||
|
|
||||||
await notify.emit(Events.TEST, data=data, title="Test Notification", message="This is a test notification.")
|
return web.json_response(data={}, status=web.HTTPOk.status_code, dumps=encoder.encode)
|
||||||
|
|
||||||
return web.json_response(data=data, status=web.HTTPOk.status_code, dumps=encoder.encode)
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import socketio
|
import socketio
|
||||||
|
|
||||||
from app.library.config import Config
|
from app.library.config import Config
|
||||||
from app.library.DownloadQueue import DownloadQueue
|
from app.library.DownloadQueue import DownloadQueue
|
||||||
from app.library.Events import EventBus, Events, error
|
from app.library.Events import EventBus, Events
|
||||||
from app.library.Presets import Presets
|
from app.library.Presets import Presets
|
||||||
from app.library.router import RouteType, route
|
from app.library.router import RouteType, route
|
||||||
from app.library.Utils import tail_log
|
from app.library.Utils import tail_log
|
||||||
|
|
@ -52,7 +53,7 @@ async def disconnect(sio: socketio.AsyncServer, sid: str, data: str = None):
|
||||||
|
|
||||||
|
|
||||||
@route(RouteType.SOCKET, "subscribe", "socket_subscribe")
|
@route(RouteType.SOCKET, "subscribe", "socket_subscribe")
|
||||||
async def subscribe(config: Config, notify: EventBus, sio: socketio.AsyncServer, sid: str, data: str):
|
async def subscribe(config: Config, notify: EventBus, sio: socketio.AsyncServer, sid: str, data: str | Any):
|
||||||
"""
|
"""
|
||||||
Subscribe to a specific event.
|
Subscribe to a specific event.
|
||||||
|
|
||||||
|
|
@ -64,8 +65,13 @@ async def subscribe(config: Config, notify: EventBus, sio: socketio.AsyncServer,
|
||||||
data (str): The event to subscribe to.
|
data (str): The event to subscribe to.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not isinstance(data, str):
|
if not isinstance(data, str) or not data:
|
||||||
await notify.emit(Events.ERROR, data=error("Invalid event."), to=sid)
|
notify.offload(
|
||||||
|
Events.ERROR,
|
||||||
|
title="Subscription Error",
|
||||||
|
message="Invalid event type was expecting a string.",
|
||||||
|
to=sid,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if data not in _Data.subscribers:
|
if data not in _Data.subscribers:
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import anyio
|
||||||
|
|
||||||
from app.library.config import Config
|
from app.library.config import Config
|
||||||
from app.library.DownloadQueue import DownloadQueue
|
from app.library.DownloadQueue import DownloadQueue
|
||||||
from app.library.Events import EventBus, Events, error
|
from app.library.Events import EventBus, Events
|
||||||
from app.library.ItemDTO import Item
|
from app.library.ItemDTO import Item
|
||||||
from app.library.router import RouteType, route
|
from app.library.router import RouteType, route
|
||||||
from app.library.Utils import is_downloaded
|
from app.library.Utils import is_downloaded
|
||||||
|
|
@ -43,25 +43,37 @@ async def add_url(queue: DownloadQueue, notify: EventBus, sid: str, data: dict):
|
||||||
url: str | None = data.get("url")
|
url: str | None = data.get("url")
|
||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
await notify.emit(Events.ERROR, data=error("No URL provided.", data={"unlock": True}), to=sid)
|
await notify.emit(
|
||||||
|
Events.LOG_ERROR,
|
||||||
|
title="Invalid URL",
|
||||||
|
message="Please provide a valid URL to add to the download queue.",
|
||||||
|
to=sid,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
status = await queue.add(item=Item.format(data))
|
||||||
await notify.emit(
|
await notify.emit(
|
||||||
event=Events.STATUS,
|
event=Events.STATUS,
|
||||||
data=await queue.add(item=Item.format(data)),
|
title="Adding URL",
|
||||||
|
message=f"Adding URL '{url}' to the download queue.",
|
||||||
|
data=status,
|
||||||
to=sid,
|
to=sid,
|
||||||
)
|
)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
await notify.emit(Events.ERROR, data=error(str(e)), to=sid)
|
await notify.emit(Events.LOG_ERROR, title="Error Adding URL", message=str(e), to=sid)
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
@route(RouteType.SOCKET, "item_cancel", "item_cancel")
|
@route(RouteType.SOCKET, "item_cancel", "item_cancel")
|
||||||
async def item_cancel(queue: DownloadQueue, notify: EventBus, sid: str, data: str):
|
async def item_cancel(queue: DownloadQueue, notify: EventBus, sid: str, data: str):
|
||||||
if not data:
|
if not data:
|
||||||
await notify.emit(Events.ERROR, data=error("Invalid request."), to=sid)
|
await notify.emit(
|
||||||
|
Events.LOG_ERROR,
|
||||||
|
title="Invalid Request",
|
||||||
|
message="No item ID provided to cancel.",
|
||||||
|
to=sid,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
status: dict[str, str] = {}
|
status: dict[str, str] = {}
|
||||||
|
|
@ -74,12 +86,32 @@ async def item_cancel(queue: DownloadQueue, notify: EventBus, sid: str, data: st
|
||||||
@route(RouteType.SOCKET, "item_delete", "item_delete")
|
@route(RouteType.SOCKET, "item_delete", "item_delete")
|
||||||
async def item_delete(queue: DownloadQueue, notify: EventBus, sid: str, data: dict):
|
async def item_delete(queue: DownloadQueue, notify: EventBus, sid: str, data: dict):
|
||||||
if not data:
|
if not data:
|
||||||
await notify.emit(Events.ERROR, data=error("Invalid request."), to=sid)
|
await notify.emit(
|
||||||
|
Events.LOG_ERROR,
|
||||||
|
title="Invalid Request",
|
||||||
|
message="No item ID provided to delete.",
|
||||||
|
to=sid,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
id: str | None = data.get("id")
|
id: str | None = data.get("id")
|
||||||
if not id:
|
if not id:
|
||||||
await notify.emit(Events.ERROR, data=error("Invalid request."), to=sid)
|
await notify.emit(
|
||||||
|
Events.LOG_ERROR,
|
||||||
|
title="Invalid Request",
|
||||||
|
message="No item ID provided to delete.",
|
||||||
|
to=sid,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
item = queue.get_item(id=id)
|
||||||
|
if not item:
|
||||||
|
await notify.emit(
|
||||||
|
Events.LOG_ERROR,
|
||||||
|
title="Item Not Found",
|
||||||
|
message=f"Item with ID '{id}' not found.",
|
||||||
|
to=sid,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
status: dict[str, str] = {}
|
status: dict[str, str] = {}
|
||||||
|
|
@ -87,7 +119,7 @@ async def item_delete(queue: DownloadQueue, notify: EventBus, sid: str, data: di
|
||||||
status.update({"identifier": id})
|
status.update({"identifier": id})
|
||||||
|
|
||||||
await notify.emit(
|
await notify.emit(
|
||||||
Events.ITEM_DELETE, data=status, title="Item Deleted", message=f"Item with ID '{id}' has been deleted."
|
Events.ITEM_DELETE, data=status, title="Item Deleted", message=f"Item '{item.info.title}': has been deleted."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -145,7 +177,12 @@ async def archive_item(config: Config, data: dict):
|
||||||
@route(RouteType.SOCKET, "item_start", "item_start")
|
@route(RouteType.SOCKET, "item_start", "item_start")
|
||||||
async def item_start(queue: DownloadQueue, notify: EventBus, sid: str, data: list | str) -> None:
|
async def item_start(queue: DownloadQueue, notify: EventBus, sid: str, data: list | str) -> None:
|
||||||
if not data:
|
if not data:
|
||||||
await notify.emit(Events.ERROR, data=error("Invalid request."), to=sid)
|
await notify.emit(
|
||||||
|
Events.LOG_ERROR,
|
||||||
|
title="Invalid Request",
|
||||||
|
message="No items provided to start.",
|
||||||
|
to=sid,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(data, str):
|
if isinstance(data, str):
|
||||||
|
|
@ -157,7 +194,12 @@ async def item_start(queue: DownloadQueue, notify: EventBus, sid: str, data: lis
|
||||||
@route(RouteType.SOCKET, "item_pause", "item_pause")
|
@route(RouteType.SOCKET, "item_pause", "item_pause")
|
||||||
async def item_pause(queue: DownloadQueue, notify: EventBus, sid: str, data: list | str) -> None:
|
async def item_pause(queue: DownloadQueue, notify: EventBus, sid: str, data: list | str) -> None:
|
||||||
if not data:
|
if not data:
|
||||||
await notify.emit(Events.ERROR, data=error("Invalid request."), to=sid)
|
await notify.emit(
|
||||||
|
Events.LOG_ERROR,
|
||||||
|
title="Invalid Request",
|
||||||
|
message="No items provided to pause.",
|
||||||
|
to=sid,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(data, str):
|
if isinstance(data, str):
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import os
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from app.library.config import Config
|
from app.library.config import Config
|
||||||
from app.library.Events import EventBus, Events, error
|
from app.library.Events import EventBus, Events
|
||||||
from app.library.router import RouteType, route
|
from app.library.router import RouteType, route
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
@ -17,11 +17,16 @@ LOG: logging.Logger = logging.getLogger(__name__)
|
||||||
@route(RouteType.SOCKET, "cli_post", "socket_cli_post")
|
@route(RouteType.SOCKET, "cli_post", "socket_cli_post")
|
||||||
async def cli_post(config: Config, notify: EventBus, sid: str, data: str):
|
async def cli_post(config: Config, notify: EventBus, sid: str, data: str):
|
||||||
if not config.console_enabled:
|
if not config.console_enabled:
|
||||||
await notify.emit(Events.ERROR, data=error("Console is disabled."), to=sid)
|
notify.offload(
|
||||||
|
Events.LOG_ERROR,
|
||||||
|
title="Feature disabled",
|
||||||
|
message="Console feature is disabled.",
|
||||||
|
to=sid,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not data:
|
if not data:
|
||||||
await notify.emit(Events.CLI_CLOSE, data={"exitcode": 0}, to=sid)
|
notify.offload(Events.CLI_CLOSE, data={"exitcode": 0}, to=sid)
|
||||||
return
|
return
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
@ -29,6 +34,7 @@ async def cli_post(config: Config, notify: EventBus, sid: str, data: str):
|
||||||
import shlex
|
import shlex
|
||||||
import subprocess # ignore
|
import subprocess # ignore
|
||||||
|
|
||||||
|
returncode: int = -1
|
||||||
try:
|
try:
|
||||||
LOG.info(f"Cli command from client '{sid}'. '{data}'")
|
LOG.info(f"Cli command from client '{sid}'. '{data}'")
|
||||||
|
|
||||||
|
|
@ -36,16 +42,22 @@ async def cli_post(config: Config, notify: EventBus, sid: str, data: str):
|
||||||
_env: dict[str, str] = os.environ.copy()
|
_env: dict[str, str] = os.environ.copy()
|
||||||
_env.update(
|
_env.update(
|
||||||
{
|
{
|
||||||
"TERM": "xterm-256color",
|
|
||||||
"LANG": "en_US.UTF-8",
|
|
||||||
"SHELL": "/bin/bash",
|
|
||||||
"LC_ALL": "en_US.UTF-8",
|
|
||||||
"PWD": config.download_path,
|
"PWD": config.download_path,
|
||||||
"FORCE_COLOR": "1",
|
"FORCE_COLOR": "1",
|
||||||
"PYTHONUNBUFFERED": "1",
|
"PYTHONUNBUFFERED": "1",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if "nt" != os.name:
|
||||||
|
_env.update(
|
||||||
|
{
|
||||||
|
"TERM": "xterm-256color",
|
||||||
|
"LANG": "en_US.UTF-8",
|
||||||
|
"LC_ALL": "en_US.UTF-8",
|
||||||
|
"SHELL": "/bin/bash",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import pty
|
import pty
|
||||||
|
|
||||||
|
|
@ -81,7 +93,7 @@ async def cli_post(config: Config, notify: EventBus, sid: str, data: str):
|
||||||
assert proc.stdout is not None
|
assert proc.stdout is not None
|
||||||
async for raw_line in proc.stdout:
|
async for raw_line in proc.stdout:
|
||||||
line = raw_line.rstrip(b"\n")
|
line = raw_line.rstrip(b"\n")
|
||||||
await notify.emit(
|
notify.offload(
|
||||||
Events.CLI_OUTPUT,
|
Events.CLI_OUTPUT,
|
||||||
data={"type": "stdout", "line": line.decode("utf-8", errors="replace")},
|
data={"type": "stdout", "line": line.decode("utf-8", errors="replace")},
|
||||||
to=sid,
|
to=sid,
|
||||||
|
|
@ -100,7 +112,7 @@ async def cli_post(config: Config, notify: EventBus, sid: str, data: str):
|
||||||
|
|
||||||
if not chunk:
|
if not chunk:
|
||||||
if buffer:
|
if buffer:
|
||||||
await notify.emit(
|
notify.offload(
|
||||||
Events.CLI_OUTPUT,
|
Events.CLI_OUTPUT,
|
||||||
data={"type": "stdout", "line": buffer.decode("utf-8", errors="replace")},
|
data={"type": "stdout", "line": buffer.decode("utf-8", errors="replace")},
|
||||||
to=sid,
|
to=sid,
|
||||||
|
|
@ -111,7 +123,7 @@ async def cli_post(config: Config, notify: EventBus, sid: str, data: str):
|
||||||
*lines, buffer = buffer.split(b"\n")
|
*lines, buffer = buffer.split(b"\n")
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
await notify.emit(
|
notify.offload(
|
||||||
Events.CLI_OUTPUT,
|
Events.CLI_OUTPUT,
|
||||||
data={"type": "stdout", "line": line.decode("utf-8", errors="replace")},
|
data={"type": "stdout", "line": line.decode("utf-8", errors="replace")},
|
||||||
to=sid,
|
to=sid,
|
||||||
|
|
@ -126,10 +138,9 @@ async def cli_post(config: Config, notify: EventBus, sid: str, data: str):
|
||||||
returncode: int = await proc.wait()
|
returncode: int = await proc.wait()
|
||||||
|
|
||||||
await read_task
|
await read_task
|
||||||
|
|
||||||
await notify.emit(Events.CLI_CLOSE, data={"exitcode": returncode}, to=sid)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(f"CLI execute exception was thrown for client '{sid}'.")
|
LOG.error(f"CLI execute exception was thrown for client '{sid}'.")
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
await notify.emit(Events.CLI_OUTPUT, data={"type": "stderr", "line": str(e)}, to=sid)
|
notify.offload(Events.CLI_OUTPUT, data={"type": "stderr", "line": str(e)}, to=sid)
|
||||||
await notify.emit(Events.CLI_CLOSE, data={"exitcode": -1}, to=sid)
|
finally:
|
||||||
|
notify.offload(Events.CLI_CLOSE, data={"exitcode": returncode}, to=sid)
|
||||||
|
|
|
||||||
|
|
@ -336,3 +336,7 @@ hr {
|
||||||
.fa-spin-10 {
|
.fa-spin-10 {
|
||||||
--fa-animation-iteration-count: 10;
|
--fa-animation-iteration-count: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Vue-Toastification__toast-body {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -173,9 +173,9 @@ const writer = (s: string) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = JSON.parse(s)
|
const json = JSON.parse(s)
|
||||||
|
|
||||||
terminal.value.writeln(data.line)
|
terminal.value.writeln(json.data.line)
|
||||||
}
|
}
|
||||||
|
|
||||||
const loader = () => isLoading.value = false
|
const loader = () => isLoading.value = false
|
||||||
|
|
|
||||||
|
|
@ -686,7 +686,8 @@ const runNow = async (item: task_item, mass: boolean = false) => {
|
||||||
onBeforeUnmount(() => socket.off('status', statusHandler))
|
onBeforeUnmount(() => socket.off('status', statusHandler))
|
||||||
|
|
||||||
const statusHandler = async (stream: string) => {
|
const statusHandler = async (stream: string) => {
|
||||||
const { status, msg } = JSON.parse(stream)
|
const json = JSON.parse(stream)
|
||||||
|
const { status, msg } = json.data
|
||||||
|
|
||||||
if ('error' === status) {
|
if ('error' === status) {
|
||||||
toast.error(msg)
|
toast.error(msg)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue