Passed
Push — main ( aae69a...152cdf )
by Jochen
02:03
created

syslog2irc.main.Processor.announce_message()   A

Complexity

Conditions 3

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 8.2077

Importance

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