From 1805327d39d2687a56dee39ded1b2b60ac04a767 Mon Sep 17 00:00:00 2001 From: Blallo Date: Tue, 29 Jan 2019 15:56:00 +0100 Subject: [PATCH] Moving utils to bot_z.utils --- bot_z/utils.py | 151 +++++++++++++++++++++++++++++++++++++++++++++++ bot_z/zdaemon.py | 46 ++------------- 2 files changed, 156 insertions(+), 41 deletions(-) create mode 100644 bot_z/utils.py diff --git a/bot_z/utils.py b/bot_z/utils.py new file mode 100644 index 0000000..b1b4c49 --- /dev/null +++ b/bot_z/utils.py @@ -0,0 +1,151 @@ +#! -*- encoding: utf-8 -*- + +"""Utilities to better handle the project.""" + +import errno +import logging +import os +import typing as T + + +logger = logging.getLogger(__name__) + + +class Fifo: + """ + Iterator to continuously read the command fifo. + Supports also write operations if opened with mode="w". + """ + + def __init__(self, fifopath: str, mode: str = "r") -> None: + try: + os.mkfifo(fifopath) + logger.info("Control pipe opened at: %s", fifopath) + except OSError as err: + if err.errno != errno.EEXIST: + logger.critical("Could not open control pipe at: %s", fifopath) + raise + self.fh = open(fifopath, mode) + + def __iter__(self): # pragma: noqa + return self + + def __next__(self) -> str: + data = self.fh.read() + # This is a never ending iterator. We should exit the + # loop independently. + return data + + def __del__(self) -> None: + self.fh.close() + + def __enter__(self) -> T.TextIO: + return self.fh + + def __exit__(self, *args) -> None: + self.fh.close() + + def write(self, data) -> None: + self.fh.write(data) + self.fh.flush() + + +class PLifo: + """ + Implements an on-disk persistent LIFO. + Casts everything to str. + """ + + def __init__(self, path: str) -> None: + self.path = path + self.content = [] + self.fh = None + + def __enter__(self) -> T.TextIO: + with open(self.path, "r") as fr: + self.content = [line.strip("\n") for line in fr.readlines()] + self.fh = open(self.path, "w") + return self + + def __exit__(self, *args) -> None: + for line in self.content: + self.fh.write("{}\n".format(line.strip("\n"))) + self.fh.close() + self.fh = None + + def push(self, value: T.Any) -> None: + """ + Pushes on the stack. It is an in-memory structure. + The variations are written on disk when exiting the context. + """ + if self.fh is None: + raise RuntimeError("Called out of context. Open the PLifo firts.") + self.content.append(str(value)) + + def pop(self) -> str: + """ + Pops on the stack. It is an in-memory structure. + The variations are written on disk when exiting the context. + """ + if self.fh is None: + raise RuntimeError("Called out of context. Open the PLifo firts.") + return self.content.pop() + + @classmethod + def Push(cls, path: str, value: T.Any) -> None: + """ + Deploys the context and pushes onto the stack, then exits the + context. + """ + with cls(path) as _lifo: + _lifo.push(value) + + @classmethod + def Pop(cls, path: str) -> str: + """ + Deploys the context and pops from the stack, then exits the + context. + """ + with cls(path) as _lifo: + _value = _lifo.pop() + return _value + + def all(self) -> T.Iterable[str]: + """ + Returns + """ + if self.fh is None: + raise RuntimeError("Called out of context. Open the PLifo firts.") + return self.content + + @classmethod + def All(cls, path: str) -> T.Iterable[str]: + """ + Deploys the context and returns the full list of elements, + then exits the context. + """ + with cls(path) as _lifo: + return _lifo.content + + def __contains__(self, *args) -> bool: + return [el in self.content for el in args] + + def __len__(self) -> int: + if self.fh is None: + raise RuntimeError("Called out of context. Open the PLifo firts.") + return len(self.content) + + def __iter__(self) -> T.Iterable: + if self.fh is None: + raise RuntimeError("Called out of context. Open the PLifo firts.") + return self.content + + def __next__(self) -> str: + if self.fh is None: + raise RuntimeError("Called out of context. Open the PLifo firts.") + return next(self.content) + + def last(self) -> str: + if self.fh is None: + raise RuntimeError("Called out of context. Open the PLifo firts.") + return self.content[-1] diff --git a/bot_z/zdaemon.py b/bot_z/zdaemon.py index 8ec50ed..acf2cdf 100644 --- a/bot_z/zdaemon.py +++ b/bot_z/zdaemon.py @@ -136,52 +136,16 @@ def daemon_process( listen_commands(fifo_path, cmd_map) -class Fifo(object): - """ - Iterator to continuously read the command fifo. - Supports also write operations if opened with mode="r". - """ - - def __init__(self, fifopath: str, mode: str = "r") -> None: - try: - os.mkfifo(fifopath) - logger.info("Control pipe opened at: %s", fifopath) - except OSError as err: - if err.errno != errno.EEXIST: - logger.critical("Could not open control pipe at: %s", fifopath) - raise - self.fh = open(fifopath, mode) - - def __iter__(self): # pragma: noqa - return self - - def __next__(self) -> str: - data = self.fh.read() - # This is a never ending iterator. We should exit the - # loop independently. - return data - - def __del__(self) -> None: - self.fh.close() - - def __enter__(self) -> T.TextIO: - return self.fh - - def __exit__(self, *args) -> None: - self.fh.close() - - def write(self, data) -> None: - self.fh.write(data) - self.fh.flush() - - -def listen_commands(fifopath: str, cmd_map: T.Dict[str, T.Callable]) -> None: +def listen_commands(name: str, fifopath: str, cmd_map: T.Dict[str, T.Callable]) -> None: """ The main loop to listen incoming commands. """ while True: fifo = Fifo(fifopath, "r") - for command in fifo: + for cmd_str in fifo: + rcpt, command = cmd_str.split('\u2193') + if rcpt != name: + continue if command == "stop": logger.debug("Received command: %s", "stop") cmd_map["stop"]()