Passed
Push — main ( 152cdf...ce0359 )
by Jochen
02:15
created

syslog2irc.main.start()   A

Complexity

Conditions 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.4218

Importance

Changes 0
Metric Value
cc 1
eloc 4
nop 1
dl 0
loc 10
ccs 1
cts 4
cp 0.25
crap 1.4218
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
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 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 threading
35
# server!) and one thread for the (actual) IRC bot. (The dummy bot does
36
# 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
        syslog_ports: Set[Port],
50
        router: Router,
51
        *,
52
        custom_format_message: Optional[
53
            Callable[[Tuple[str, int], SyslogMessage], str]
54
        ] = None,
55
    ) -> None:
56 1
        self.irc_bot = irc_bot
57 1
        self.syslog_ports = syslog_ports
58 1
        self.router = router
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 1
    def connect_to_signals(self) -> None:
67 1
        irc_channel_joined.connect(self.router.enable_channel)
68 1
        syslog_message_received.connect(self.handle_syslog_message)
69
70 1
    def handle_syslog_message(
71
        self,
72
        port: Port,
73
        *,
74
        source_address: Optional[Tuple[str, int]] = None,
75
        message: Optional[SyslogMessage] = None,
76
    ) -> None:
77
        """Process an incoming syslog message."""
78
        self.message_queue.put((port, source_address, message))
79
80 1
    def announce_message(
81
        self,
82
        port: Port,
83
        source_address: Tuple[str, int],
84
        message: SyslogMessage,
85
    ) -> None:
86
        """Announce message on IRC."""
87
        channel_names = self.router.get_channel_names_for_port(port)
88
        text = self.format_message(source_address, message)
89
90
        for channel_name in channel_names:
91
            if self.router.is_channel_enabled(channel_name):
92
                self.irc_bot.say(channel_name, text)
93
94 1
    def run(self) -> None:
95
        """Start network-based components, run main loop."""
96
        self.irc_bot.start()
97
        start_syslog_message_receivers(self.syslog_ports)
98
99
        try:
100
            while True:
101
                port, source_address, message = self.message_queue.get()
102
                self.announce_message(port, source_address, message)
103
        except KeyboardInterrupt:
104
            pass
105
106
        logger.info('Shutting down ...')
107
        self.irc_bot.disconnect('Bye.')  # Joins bot thread.
108
109
110 1
def create_processor(config: Config) -> Processor:
111
    """Create a processor."""
112
    irc_bot = create_bot(config.irc)
113
    syslog_ports = {route.syslog_port for route in config.routes}
114
    router = Router(config.routes)
115
116
    return Processor(irc_bot, syslog_ports, router)
117
118
119 1
def start(config: Config) -> None:
120
    """Start the IRC bot and the syslog listen server(s)."""
121
    processor = create_processor(config)
122
123
    # Up to this point, no signals must have been sent.
124
    processor.connect_to_signals()
125
126
    # Signals are allowed be sent from here on.
127
128
    processor.run()
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