Passed
Push — beta ( 72a57d...7d0ef0 )
by Dean
03:02
created

SingletonHandler   A

Complexity

Total Complexity 6

Size/Duplication

Total Lines 27
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 6
dl 0
loc 27
ccs 0
cts 21
cp 0
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A on_shutdown() 0 5 1
A handle() 0 5 2
A process() 0 14 3
1
from plugin.core.environment import Environment
2
3
from SocketServer import TCPServer, StreamRequestHandler
4
from threading import Thread
5
import logging
6
import os
7
import socket
8
import time
9
10
log = logging.getLogger(__name__)
11
12
PORT_DEFAULT = 35374
13
PORT_PATH = os.path.join(Environment.path.plugin_data, 'Singleton')
14
15
16
def get_port():
17
    if not os.path.exists(PORT_PATH):
18
        return PORT_DEFAULT
19
20
    # Read data from file
21
    with open(PORT_PATH, 'r') as fp:
22
        data = fp.read()
23
24
    # Parse data
25
    try:
26
        return int(data.strip())
27
    except Exception, ex:
28
        log.warn('Unable to parse integer from %r: %s', PORT_PATH, ex, exc_info=True)
29
        return PORT_DEFAULT
30
31
32
class Singleton(object):
33
    host = '127.0.0.1'
34
    port = get_port()
35
36
    _server = None
37
    _thread = None
38
39
    @classmethod
40
    def acquire(cls):
41
        try:
42
            return cls._acquire()
43
        except Exception, ex:
44
            log.error('Exception raised in _acquire(): %s', ex, exc_info=True)
45
            return False
46
47
    @classmethod
48
    def _acquire(cls):
49
        # Start server
50
        if cls._start():
51
            return True
52
53
        # Attempt existing plugin shutdown
54
        if not cls.release():
55
            log.warn('Unable to shutdown existing plugin instance')
56
            return False
57
58
        # Try start server again
59
        return cls._start()
60
61
    @classmethod
62
    def release(cls):
63
        log.debug('Attempting to shutdown the existing plugin instance (port: %s)...', cls.port)
64
65
        # Request shutdown
66
        client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
67
        client.setblocking(0)
68
        client.settimeout(1.0)
69
70
        client.connect((cls.host, cls.port))
71
72
        client.sendall('shutdown\n')
73
74
        try:
75
            # Read response
76
            response = client.recv(128).strip()
77
        except socket.timeout, ex:
78
            log.info('Release timeout', exc_info=True)
79
            return False
80
81
        if response != 'ok':
82
            log.info('Release failed: %r', response)
83
            return False
84
85
        log.info('Existing plugin instance has been shutdown')
86
87
        time.sleep(2)
88
        return True
89
90
    @classmethod
91
    def _start(cls):
92
        log.debug('Starting server (port: %s)...', cls.port)
93
94
        try:
95
            # Construct server
96
            cls._server = TCPServer((cls.host, cls.port), SingletonHandler)
97
        except socket.error, ex:
98
            if ex.errno != 10048:
99
                raise ex
100
101
            log.info('Plugin already running: %s', ex)
102
            return False
103
104
        # Start listening thread
105
        cls._thread = Thread(target=cls._run, name='Singleton')
106
        cls._thread.daemon = True
107
108
        cls._thread.start()
109
110
        log.debug('Started')
111
        return True
112
113
    @classmethod
114
    def _run(cls):
115
        try:
116
            cls._server.serve_forever()
117
        except Exception, ex:
118
            log.error('Server exception raised: %s', ex, exc_info=True)
119
120
        log.info('Server exited')
121
122
123
class SingletonHandler(StreamRequestHandler):
124
    def handle(self):
125
        try:
126
            self.process()
127
        except Exception, ex:
128
            log.error('Exception raised in process(): %s', ex, exc_info=True)
129
130
    def process(self):
131
        command = self.rfile.readline().strip()
132
        handler = getattr(self, 'on_%s' % (command, ), None)
133
134
        if handler is None:
135
            log.debug('Unknown command received: %r', command)
136
            return
137
138
        log.debug('Processing command: %r', command)
139
140
        try:
141
            handler()
142
        except Exception, ex:
143
            log.error('Handler raised an exception: %s', ex, exc_info=True)
144
145
    def on_shutdown(self):
146
        self.wfile.write('ok\n')
147
148
        log.info('Exit')
149
        os._exit(1)
150