Passed
Push — main ( 34b5d7...c26449 )
by Jochen
06:29
created

syslog2irc.main.Processor.connect_to_signals()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 1
dl 0
loc 3
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
"""
2
syslog2irc.main
3
~~~~~~~~~~~~~~~
4
5
Orchestration, application entry point
6
7
:Copyright: 2007-2021 Jochen Kupperschmidt
8
:License: MIT, see LICENSE for details.
9
"""
10
11 1
from __future__ import annotations
12 1
import logging
13 1
from queue import SimpleQueue
14 1
from typing import Callable, Optional, Tuple
15
16 1
from syslogmp import Message as SyslogMessage
17
18 1
from .cli import parse_args
19 1
from .config import Config, load_config
20 1
from .formatting import format_message
21 1
from .irc import create_bot
22 1
from .network import Port
23 1
from .routing import Router
24 1
from .signals import irc_channel_joined, syslog_message_received
25 1
from .syslog import start_syslog_message_receivers
26 1
from .util import configure_logging
27
28
29 1
logger = logging.getLogger(__name__)
30
31
32 1
FormatMessageCallable = Callable[[Tuple[str, int], SyslogMessage], str]
33
34
35
# A note on threads (implementation detail):
36
#
37
# This application uses threads. Besides the main thread there is one
38
# thread for *each* syslog message receiver (which itself is a threading
39
# server!) and one thread for the (actual) IRC bot. (The dummy bot does
40
# not run in a separate thread.)
41
42
# Those threads are configured to be daemon threads. A Python
43
# application exits if no more non-daemon threads are running.
44
#
45
# For details, consult the documentation on the `threading` module that
46
# is part of Python's standard library.
47
48
49 1
class Processor:
50 1
    def __init__(
51
        self,
52
        config: Config,
53
        *,
54
        custom_format_message: Optional[FormatMessageCallable] = None,
55
    ) -> None:
56 1
        self.irc_bot = create_bot(config.irc)
57 1
        self.syslog_ports = {route.syslog_port for route in config.routes}
58 1
        self.router = Router(config.routes)
59 1
        self.message_queue: SimpleQueue = SimpleQueue()
60
61 1
        if custom_format_message is not None:
62
            self.format_message = custom_format_message
63
        else:
64 1
            self.format_message = format_message
65
66
        # Up to this point, no signals must have been sent.
67 1
        self.connect_to_signals()
68
        # Signals are allowed be sent from here on.
69
70 1
    def connect_to_signals(self) -> None:
71 1
        irc_channel_joined.connect(self.router.enable_channel)
72 1
        syslog_message_received.connect(self.handle_syslog_message)
73
74 1
    def handle_syslog_message(
75
        self,
76
        port: Port,
77
        *,
78
        source_address: Optional[tuple[str, int]] = None,
79
        message: Optional[SyslogMessage] = None,
80
    ) -> None:
81
        """Process an incoming syslog message."""
82
        self.message_queue.put((port, source_address, message))
83
84 1
    def announce_message(
85
        self,
86
        port: Port,
87
        source_address: tuple[str, int],
88
        message: SyslogMessage,
89
    ) -> None:
90
        """Announce message on IRC."""
91
        channel_names = self.router.get_channel_names_for_port(port)
92
        text = self.format_message(source_address, message)
93
94
        for channel_name in channel_names:
95
            if self.router.is_channel_enabled(channel_name):
96
                self.irc_bot.say(channel_name, text)
97
98 1
    def run(self) -> None:
99
        """Start network-based components, run main loop."""
100
        self.irc_bot.start()
101
        start_syslog_message_receivers(self.syslog_ports)
102
103
        try:
104
            while True:
105
                port, source_address, message = self.message_queue.get()
106
                self.announce_message(port, source_address, message)
107
        except KeyboardInterrupt:
108
            pass
109
110
        logger.info('Shutting down ...')
111
        self.irc_bot.disconnect('Bye.')  # Joins bot thread.
112
113
114 1
def main(
115
    *, custom_format_message: Optional[FormatMessageCallable] = None
116
) -> None:
117
    """Parse arguments, load configuration, and start the application."""
118
    args = parse_args()
119
    config = load_config(args.config_filename)
120
    configure_logging(config.log_level)
121
122
    processor = Processor(config, custom_format_message=custom_format_message)
123
    processor.run()
124
125
126 1
if __name__ == '__main__':
127
    main()
128