Completed
Push — master ( 0bc61b...16fc24 )
by Thomas
8s
created

doorpi.DoorPi   F

Complexity

Total Complexity 63

Size/Duplication

Total Lines 268
Duplicated Lines 0 %
Metric Value
dl 0
loc 268
rs 3.6585
wmc 63

23 Methods

Rating   Name   Duplication   Size   Complexity  
A restart() 0 3 2
A base_path() 0 11 4
A event_handler() 0 2 1
A epilog() 0 2 1
A sipphone() 0 2 1
A doorpi_shutdown() 0 3 1
A shutdown() 0 2 1
A webserver() 0 2 1
A check_time_critical_threads() 0 2 2
A keyboard() 0 2 1
A __del__() 0 2 1
A name() 0 2 1
A modules_destroyed() 0 4 2
A status() 0 2 1
A config() 0 2 1
A get_status() 0 1 1
A name_and_version() 0 2 1
A additional_informations() 0 4 2
B run() 0 28 6
F parse_string() 0 47 13
F prepare() 0 70 12
A __init__() 0 10 1
B destroy() 0 30 6

How to fix   Complexity   

Complex Class

Complex classes like doorpi.DoorPi often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
4
import logging
5
logger = logging.getLogger(__name__)
6
logger.debug("%s loaded", __name__)
7
8
import sys
0 ignored issues
show
Unused Code introduced by
The import sys seems to be unused.
Loading history...
9
import argparse
0 ignored issues
show
Unused Code introduced by
The import argparse seems to be unused.
Loading history...
10
11
import time # used by: DoorPi.run
12
import os # used by: DoorPi.load_config
13
14
import datetime # used by: parse_string
15
import cgi # used by: parse_string
16
import tempfile
0 ignored issues
show
Unused Code introduced by
The import tempfile seems to be unused.
Loading history...
17
#import threading
18
#import BaseHTTPServer
19
20
import metadata
21
from keyboard.KeyboardInterface import load_keyboard
22
from sipphone.SipphoneInterface import load_sipphone
23
from status.webserver import load_webserver
24
from conf.config_object import ConfigObject
25
from action.handler import EventHandler
26
from status.status_class import DoorPiStatus
27
#from status.webservice import run_webservice, WebService
28
from action.base import SingleAction
29
30
31
class DoorPiShutdownAction(SingleAction): pass
32
class DoorPiNotExistsException(Exception): pass
33
class DoorPiEventHandlerNotExistsException(Exception): pass
34
class DoorPiRestartException(Exception): pass
35
36
class Singleton(type):
37
    _instances = {}
38
    def __call__(cls, *args, **kwargs):
39
        if cls not in cls._instances:
40
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
41
        return cls._instances[cls]
42
43
class DoorPi(object):
44
    __metaclass__ = Singleton
45
46
    __prepared = False
47
48
    __config = None
49
    @property
50
    def config(self): return self.__config
51
52
    __keyboard = None
53
    @property
54
    def keyboard(self): return self.__keyboard
55
    #def get_keyboard(self): return self.__keyboard
56
57
    __sipphone = None
58
    @property
59
    def sipphone(self): return self.__sipphone
60
61
    @property
62
    def additional_informations(self):
63
        if self.event_handler is None: return {}
64
        else: return self.event_handler.additional_informations
65
66
    __event_handler = None
67
    @property
68
    def event_handler(self): return self.__event_handler
69
70
    __webserver = None
71
    @property
72
    def webserver(self): return self.__webserver
73
74
    @property
75
    def status(self): return DoorPiStatus(self)
76
    def get_status(self, modules = '', value= '', name = ''): return DoorPiStatus(self, modules, value, name)
77
78
    @property
79
    def epilog(self): return metadata.epilog
80
81
    @property
82
    def name(self): return str(metadata.package)
83
    @property
84
    def name_and_version(self): return str(metadata.package) + " - version: " + metadata.version
85
86
    __shutdown = False
87
    @property
88
    def shutdown(self): return self.__shutdown
89
90
    _base_path = metadata.doorpi_path
91
    @property
92
    def base_path(self):
93
        if self._base_path is None:
94
            try:
95
                self._base_path = os.path.join(os.path.expanduser('~'), metadata.package)
96
                assert os.access(self._base_path, os.W_OK), 'use fallback for base_path (see tmp path)'
97
            except Exception as exp:
98
                logger.error(exp)
99
                import tempfile
0 ignored issues
show
Comprehensibility Bug introduced by
tempfile is re-defining a name which is already available in the outer-scope (previously defined on line 16).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
100
                self._base_path = tempfile.gettempdir()
101
        return self._base_path
102
103
    def __init__(self, parsed_arguments = None):
104
        self.__parsed_arguments = parsed_arguments
105
        # for start as daemon - if start as app it will not matter to load this vars
106
        self.stdin_path = '/dev/null'
107
        self.stdout_path = '/dev/null'
108
        self.stderr_path = '/dev/null'
109
        self.pidfile_path =  '/var/run/doorpi.pid'
110
        self.pidfile_timeout = 5
111
112
        self.__last_tick = time.time()
113
114
    def doorpi_shutdown(self, time_until_shutdown=10):
115
        time.sleep(time_until_shutdown)
116
        self.__shutdown = True
117
118
    def prepare(self, parsed_arguments):
119
        logger.debug("prepare")
120
        logger.debug("given arguments argv: %s", parsed_arguments)
121
122
        self.__config = ConfigObject.load_config(parsed_arguments.configfile)
123
        self._base_path = self.config.get('DoorPi', 'base_path', self.base_path)
124
        self.__event_handler = EventHandler()
125
126
        if self.config.config_file is None:
127
            self.event_handler.register_action('AfterStartup', self.config.save_config)
128
            self.config.get('EVENT_OnStartup', '10', 'sleep:1')
129
130
        if 'test' in parsed_arguments and parsed_arguments.test is True:
131
            logger.warning('using only test-mode and destroy after 5 seconds')
132
            self.event_handler.register_action('AfterStartup', DoorPiShutdownAction(self.doorpi_shutdown))
133
134
        # register own events
135
        self.event_handler.register_event('BeforeStartup', __name__)
136
        self.event_handler.register_event('OnStartup', __name__)
137
        self.event_handler.register_event('AfterStartup', __name__)
138
        self.event_handler.register_event('BeforeShutdown', __name__)
139
        self.event_handler.register_event('OnShutdown', __name__)
140
        self.event_handler.register_event('AfterShutdown', __name__)
141
        self.event_handler.register_event('OnTimeTick', __name__)
142
        self.event_handler.register_event('OnTimeTickRealtime', __name__)
143
144
        # register base actions
145
        self.event_handler.register_action('OnTimeTick', 'time_tick:!last_tick!')
146
147
        # register modules
148
        self.__webserver    = load_webserver()
149
        self.__keyboard     = load_keyboard()
150
        self.__sipphone     = load_sipphone()
151
        self.sipphone.start()
152
153
        # register eventbased actions from configfile
154
        for event_section in self.config.get_sections('EVENT_'):
155
            logger.info("found EVENT_ section '%s' in configfile", event_section)
156
            event_name = event_section[len('EVENT_'):]
157
            for action in sorted(self.config.get_keys(event_section)):
158
                logger.info("registering action '%s' for event '%s'", action, event_name)
159
                self.event_handler.register_action(event_name, self.config.get(event_section, action))
160
161
        # register actions for inputpins
162
        if 'KeyboardHandler' not in self.keyboard.name:
163
            section_name = 'InputPins'
164
            for input_pin in sorted(self.config.get_keys(section_name)):
165
                self.event_handler.register_action('OnKeyPressed_'+input_pin, self.config.get(section_name, input_pin))
166
        else:
167
            for keyboard_name in self.keyboard.loaded_keyboards:
168
                section_name = keyboard_name+'_InputPins'
169
                for input_pin in self.config.get_keys(section_name, log = False):
170
                    self.event_handler.register_action(
171
                        'OnKeyPressed_'+keyboard_name+'.'+input_pin,
172
                        self.config.get(section_name, input_pin)
173
                    )
174
175
        # register actions for DTMF
176
        section_name = 'DTMF'
177
        for DTMF in sorted(self.config.get_keys(section_name)):
178
            self.event_handler.register_action('OnDTMF_'+DTMF, self.config.get(section_name, DTMF))
179
180
        # register keep_alive_led
181
        is_alive_led = self.config.get('DoorPi', 'is_alive_led', '')
182
        if is_alive_led is not '':
183
            self.event_handler.register_action('OnTimeSecondEvenNumber', 'out:%s,HIGH,False'%is_alive_led)
184
            self.event_handler.register_action('OnTimeSecondUnevenNumber', 'out:%s,LOW,False'%is_alive_led)
185
186
        self.__prepared = True
187
        return self
188
189
    def __del__(self):
190
        return self.destroy()
191
192
    @property
193
    def modules_destroyed(self):
194
        if len(self.event_handler.sources) > 1: return False
195
        return self.event_handler.idle
196
197
    def destroy(self):
198
        logger.debug('destroy doorpi')
199
200
        if not self.event_handler or self.event_handler.threads == None:
201
            DoorPiEventHandlerNotExistsException("don't try to stop, when not prepared")
202
            return False
203
204
        logger.debug("Threads before starting shutdown: %s", self.event_handler.threads)
205
206
        self.event_handler.fire_event('BeforeShutdown', __name__)
207
        self.event_handler.fire_event_synchron('OnShutdown', __name__)
208
        self.event_handler.fire_event('AfterShutdown', __name__)
209
210
        timeout = 5
211
        waiting_between_checks = 0.5
212
        time.sleep(waiting_between_checks)
213
        while timeout > 0 and self.modules_destroyed is not True:
214
            # while not self.event_handler.idle and timeout > 0 and len(self.event_handler.sources) > 1:
215
            logger.debug('wait %s seconds for threads %s and %s event',
216
                         timeout, len(self.event_handler.threads[1:]), len(self.event_handler.sources))
217
            logger.trace('still existing threads:       %s', self.event_handler.threads[1:])
218
            logger.trace('still existing event sources: %s', self.event_handler.sources)
219
            time.sleep(waiting_between_checks)
220
            timeout -= waiting_between_checks
221
222
        if timeout <= 0:
223
            logger.warning("waiting for threads to time out - there are still threads: %s", self.event_handler.threads[1:])
224
225
        logger.info('======== DoorPi successfully shutdown ========')
226
        return True
227
228
    def restart(self):
229
        if self.destroy(): self.run()
230
        else: raise DoorPiRestartException()
231
232
    def run(self):
233
        logger.debug("run")
234
        if not self.__prepared: self.prepare(self.__parsed_arguments)
235
236
        self.event_handler.fire_event('BeforeStartup', __name__)
237
        self.event_handler.fire_event_synchron('OnStartup', __name__)
238
        self.event_handler.fire_event('AfterStartup', __name__)
239
240
        # self.event_handler.register_action('OnTimeMinuteUnevenNumber', 'doorpi_restart')
241
242
        logger.info('DoorPi started successfully')
243
        logger.info('BasePath is %s', self.base_path)
244
        if self.__webserver:
245
            logger.info('Weburl is %s', self.__webserver.own_url)
246
        else:
247
            logger.info('no Webserver loaded')
248
249
        time_ticks = 0
250
251
        while True and not self.__shutdown:
252
            time_ticks += 0.05
253
            self.check_time_critical_threads()
254
            if time_ticks > 0.5:
255
                self.__last_tick = time.time()
256
                self.__event_handler.fire_event_asynchron('OnTimeTick', __name__)
257
                time_ticks = 0
258
            time.sleep(0.05)
259
        return self
260
261
    def check_time_critical_threads(self):
262
        if self.sipphone: self.sipphone.self_check()
263
264
    def parse_string(self, input_string):
265
        parsed_string = datetime.datetime.now().strftime(str(input_string))
266
267
        if self.keyboard is None or self.keyboard.last_key is None:
268
            self.additional_informations['LastKey'] = "NotSetYet"
269
        else:
270
            self.additional_informations['LastKey'] = str(self.keyboard.last_key)
271
272
        infos_as_html = '<table>'
273
        for key in self.additional_informations.keys():
274
            infos_as_html += '<tr><td>'
275
            infos_as_html += '<b>'+key+'</b>'
276
            infos_as_html += '</td><td>'
277
            infos_as_html += '<i>'+cgi.escape(
278
                str(self.additional_informations.get(key)).replace("\r\n", "<br />")
279
            )+'</i>'
280
            infos_as_html += '</td></tr>'
281
        infos_as_html += '</table>'
282
283
        mapping_table = {
284
            'INFOS_PLAIN':      str(self.additional_informations),
285
            'INFOS':            infos_as_html,
286
            'BASEPATH':         self.base_path,
287
            'last_tick':        str(self.__last_tick)
288
        }
289
290
        if self.keyboard and 'KeyboardHandler' not in self.keyboard.name:
291
            for output_pin in self.config.get_keys('OutputPins', log = False):
292
                mapping_table[self.config.get('OutputPins', output_pin, log = False)] = output_pin
293
        elif self.keyboard and 'KeyboardHandler' in self.keyboard.name:
294
            for outputpin_section in self.config.get_sections('_OutputPins', False):
295
                for output_pin in self.config.get_keys(outputpin_section, log = False):
296
                    mapping_table[self.config.get(outputpin_section, output_pin, log = False)] = output_pin
297
298
        for key in mapping_table.keys():
299
            parsed_string = parsed_string.replace(
300
                "!"+key+"!",
301
                mapping_table[key]
302
            )
303
304
        for key in self.additional_informations.keys():
305
            parsed_string = parsed_string.replace(
306
                "!"+key+"!",
307
                str(self.additional_informations[key])
308
            )
309
310
        return parsed_string
311
312
if __name__ == '__main__':
313
    raise Exception('use main.py to start DoorPi')