# Libreprinter is a software allowing to use the Centronics and serial printing
# functions of vintage computers on modern equipement through a tiny hardware
# interface.
# Copyright (C) 2020-2024 Ysard
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Watchdog for /raw directory that is able to parse & convert new files
into CSV and/or into pdfs
Conversions are made thanks to Seiko-converter.
As soon as a raw file is modified or closed, a pdf is created.
This allows to always have the latest data available even with long gate modes
configured on the QT-2100 device.
"""
# Standard imports
from importlib.util import find_spec
from pathlib import Path
from datetime import datetime
from watchdog.observers.inotify import InotifyObserver
from watchdog.events import RegexMatchingEventHandler, FileSystemEvent
# Custom imports
from libreprinter import plugins_handler
from libreprinter.file_handler import init_directories
from libreprinter.commons import logger
LOGGER = logger()
CONFIG = {
"misc": {
"emulation": "seiko-qt2100",
}
}
REQUIRED_DIRS = ["csv"]
EXTERNAL_PACKAGE = "seiko_converter"
SECTION_NAME = "seiko-qt2100"
[docs]
class SeikoEventHandler(RegexMatchingEventHandler):
"""Watch a directory via a parent Observer and emit events accordingly
This class reimplements :meth:`on_closed + :meth:`on_modified` events.
Watched directory:
- `raw`: `*.raw`
Attributes:
:param seiko_settings: Settings of the seiko-qt2100 section from the config file.
:param last_timestamp: Keep the timestamp of the last modified file event.
Used to reduce overhead.
:type seiko_settings: dict
:type last_timestamp: float
Class attribute:
:param FILES_REGEX: Patterns to detect files.
:type FILES_REGEX: list[str]
"""
FILES_REGEX = [r".*\.raw"]
def __init__(self, *args, seiko_settings=None, **kwargs):
"""Constructor override
Just add converter settings attr and define watchdog regexes.
"""
super().__init__(*args, regexes=self.FILES_REGEX, **kwargs)
# Format values from the config file (especially the cutoff numeric value)
seiko_settings = seiko_settings or {}
temp_config = dict()
for key in seiko_settings.keys():
try:
temp_config[key] = seiko_settings.getboolean(key, True)
except ValueError:
temp_config[key] = seiko_settings.getfloat(key)
self.seiko_settings = temp_config
self.last_timestamp = datetime.now().timestamp()
[docs]
def on_modified(self, event: FileSystemEvent) -> None:
"""File is modified, generate partial files"""
timestamp = datetime.now().timestamp()
if timestamp - self.last_timestamp > 4:
self.last_timestamp = timestamp
self.build_data(event)
[docs]
def on_closed(self, event):
"""File creation is detected, generate full files"""
self.build_data(event)
[docs]
def build_data(self, event):
"""Generate csv and/or pdf files according to the config file specs"""
from seiko_converter.qt2100_parser import SeikoQT2100Parser
from seiko_converter.qt2100_converter import SeikoQT2100GraphTool
LOGGER.debug("Event detected: %s", event)
src_path = Path(event.src_path)
out_path = str(src_path.parent.parent / "{0}" / (src_path.stem + ".{0}"))
parser = SeikoQT2100Parser(src_path)
obj = SeikoQT2100GraphTool(parser)
if self.seiko_settings["enable-csv"]:
obj.to_csv(output_filename=out_path.format("csv"))
if self.seiko_settings["enable-graph"]:
obj.to_graph(output_filename=out_path.format("pdf"), **self.seiko_settings)
[docs]
@plugins_handler.register
def setup_seiko_watchdog(config):
"""Initialise a watchdog on `/raw` directory in configured `output_path`.
Any raw file created in this directory will be converted in `/pdf` or `/csv`
by the seiko-converter Python program installed on the system.
"""
LOGGER.info("Launch seiko watchdog...")
# Test the existence of the external module
if find_spec(EXTERNAL_PACKAGE) is None:
msg = f"<{EXTERNAL_PACKAGE}> package is not installed!"
LOGGER.error(msg)
raise ImportError(msg)
init_directories(config["misc"]["output_path"], REQUIRED_DIRS)
event_handler = SeikoEventHandler(
seiko_settings=config[SECTION_NAME], ignore_directories=True
)
# Attach event handler to the configured output_path
observer = InotifyObserver()
observer.schedule(
event_handler, config["misc"]["output_path"] + "raw/", recursive=False
)
observer.start()
return observer
if __name__ == "__main__": # pragma: no cover
import configparser
min_config = configparser.ConfigParser()
min_config["misc"] = {"output_path": "./"}
min_config["seiko-qt2100"] = {"enable-csv": "True", "enable-graph": "True"}
obs = setup_seiko_watchdog(min_config)
obs.join()