summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimeon Simeonov2015-04-13 23:15:39 +0200
committerSimeon Simeonov2015-04-13 23:15:39 +0200
commitd606cbfc4932dd67cdb272a32e8103998fd3312a (patch)
treeec9c794c77ea755722d8f1ce5444cf8e90b38d0f
parentcde83e91609d513f445efb7696b32edce81293dc (diff)
beinc_server_xmlrpc.py and version 2.0 started. Cosmetic changes in beinc_server.py
-rwxr-xr-xbeinc_server.py5
-rwxr-xr-xbeinc_server_xmlrpc.py355
2 files changed, 357 insertions, 3 deletions
diff --git a/beinc_server.py b/beinc_server.py
index 894eeb2..d079384 100755
--- a/beinc_server.py
+++ b/beinc_server.py
@@ -1,7 +1,7 @@
1#!/usr/bin/env python 1#!/usr/bin/env python
2# -*- coding: utf-8 -*- 2# -*- coding: utf-8 -*-
3 3
4# Blackmore's Enhanced IRC-Notification Collection (BEINC) v1.0 4# Blackmore's Enhanced IRC-Notification Collection (BEINC) v1.1
5# Copyright (C) 2013-2015 Simeon Simeonov 5# Copyright (C) 2013-2015 Simeon Simeonov
6 6
7# This program is free software: you can redistribute it and/or modify 7# This program is free software: you can redistribute it and/or modify
@@ -23,7 +23,6 @@ import errno
23import getpass 23import getpass
24import json 24import json
25import os 25import os
26import random
27import sys 26import sys
28 27
29import cherrypy 28import cherrypy
@@ -46,7 +45,7 @@ except ImportError as e:
46 45
47 46
48__author__ = 'Simeon Simeonov' 47__author__ = 'Simeon Simeonov'
49__version__ = '1.0' 48__version__ = '1.1'
50__license__ = 'GPL3' 49__license__ = 'GPL3'
51 50
52 51
diff --git a/beinc_server_xmlrpc.py b/beinc_server_xmlrpc.py
new file mode 100755
index 0000000..a9d0fa5
--- /dev/null
+++ b/beinc_server_xmlrpc.py
@@ -0,0 +1,355 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# Blackmore's Enhanced IRC-Notification Collection (BEINC) v2.0
5# Copyright (C) 2013-2015 Simeon Simeonov
6
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20
21import argparse
22import errno
23import getpass
24import json
25import os
26import sys
27
28import OpenSSL
29
30from twisted.web import xmlrpc, server
31from twisted.internet import protocol, reactor, ssl
32from twisted.python.filepath import FilePath
33
34try:
35 import pynotify
36except ImportError as e:
37 pynotify = None
38
39try:
40 import pyosd
41 pyosd_positions = {'top': pyosd.POS_TOP,
42 'middle': pyosd.POS_MID,
43 'bottom': pyosd.POS_BOT}
44 pyosd_alignments = {'left': pyosd.ALIGN_LEFT,
45 'center': pyosd.ALIGN_CENTER,
46 'right': pyosd.ALIGN_RIGHT}
47except ImportError as e:
48 pyosd = None
49
50
51__author__ = 'Simeon Simeonov'
52__version__ = '1.1'
53__license__ = 'GPL3'
54
55
56BEINC_OSD_TYPE_NONE = 0
57BEINC_OSD_TYPE_PYNOTIFY = 1
58BEINC_OSD_TYPE_PYOSD = 2
59
60class BEINCInstance(object):
61 """
62 Represents a single server-instance
63 """
64
65 def __init__(self, instance_dict):
66 """
67 instance_dict: the config-dictionary node that represents this instance
68 """
69 self.__message_queue = list()
70 self.__osd_type = BEINC_OSD_TYPE_NONE
71 self.__osd_notification = None
72
73 self.__name = instance_dict.get('name')
74 self.__password = instance_dict.get('password', '')
75 self.__queue_size = int(instance_dict.get('queue_size', 3))
76 if instance_dict['osd_system'].lower() == 'pynotify':
77 self.__queue_size = 0 # disable queueing
78 if not pynotify:
79 sys.stderr.write(
80 'This server does not possess pynotify capability\n')
81 sys.stderr.write(
82 'Remove the instance {0}'.format(self.__name))
83 sys.stderr.write(
84 'or define it with "osd_system": "none" '
85 'or other available backend\n')
86 sys.exit(errno.EPERM)
87 try:
88 self.__osd_notification = pynotify.Notification(' ')
89 self.__osd_notification.set_timeout(
90 1000 * int(instance_dict.get('osd_timeout', 5)))
91 self.__osd_notification.set_property(
92 'app_name',
93 '{0} {1}'.format(sys.argv[0], __version__))
94 except Exception as e:
95 sys.stderr.write(
96 'Unable to set up a notification object for {0} ({1})\n')
97 sys.exit(errno.EPERM)
98 self.__osd_type = BEINC_OSD_TYPE_PYNOTIFY
99 elif instance_dict['osd_system'].lower() == 'pyosd':
100 self.__queue_size = 0 # disable queueing
101 if not pyosd:
102 sys.stderr.write(
103 'This server does not possess pyosd capability\n')
104 sys.stderr.write(
105 'Remove the instance {0}'.format(self.__name))
106 sys.stderr.write(
107 'or define it with "osd_system": "none" '
108 'or other available backend\n')
109 sys.exit(errno.EPERM)
110 try:
111 self.__osd_notification = pyosd.osd()
112 self.__osd_notification.set_timeout(
113 int(instance_dict.get('osd_timeout', 5)))
114 pyosd_font = instance_dict.get('pyosd_font')
115 if pyosd_font:
116 self.__osd_notification.set_font(pyosd_font)
117 self.__osd_notification.set_vertical_offset(
118 instance_dict.get('pyosd_vertical_offset', 120))
119 self.__osd_notification.set_horizontal_offset(
120 instance_dict.get('pyosd_horizontal_offset', 30))
121 align_str = instance_dict.get('pyosd_align', 'left')
122 self.__osd_notification.set_align(
123 pyosd_alignments.get(align_str, pyosd.ALIGN_LEFT))
124 position_str = instance_dict.get('pyosd_position', 'bottom')
125 self.__osd_notification.set_pos(
126 pyosd_positions.get(position_str, pyosd.POS_BOT))
127 self.__osd_notification.set_colour(
128 instance_dict.get('pyosd_color', 'blue'))
129 except Exception as e:
130 sys.stderr.write(
131 'Unable to set up a notification object for {0} ({1})\n')
132 sys.exit(errno.EPERM)
133 self.__osd_type = BEINC_OSD_TYPE_PYOSD
134
135 @property
136 def name(self):
137 """
138 name-property for the server instance (read-only)
139 """
140 return self.__name
141
142 @property
143 def queueable(self):
144 """
145 True if this instance has a queueing capability (read-only)
146 """
147 return bool(self.__queue_size)
148
149 def password_match(self, password):
150 """
151 Returns True if 'passowrd' matches the instance-password,
152 otherwise - False
153 """
154 return True if self.__password == password else False
155
156 def send_message(self, title, message):
157 """
158 Displays or enqueues the message,
159 depending on the instance's type in regard to the osd_system
160 """
161 if self.__osd_type == BEINC_OSD_TYPE_PYNOTIFY:
162 self.__send_pynotify_messaage(title, message)
163 elif self.__osd_type == BEINC_OSD_TYPE_PYOSD:
164 self.__send_pyosd_message(title, message)
165 else:
166 self.__send_message_to_queue(title, message)
167
168 def get_queue(self):
169 """
170 Reruens a json representation of the message queue
171 """
172 jstr = json.dumps(self.__message_queue)
173 self.__message_queue = list()
174 return jstr
175
176 def __send_pynotify_messaage(self, title, message):
177 """
178 Displays pynotify message
179 """
180 self.__osd_notification.set_properties(summary=title, body=message)
181 self.__osd_notification.show()
182
183 def __send_pyosd_message(self, title, message):
184 """
185 Displays pyosd message
186 """
187 self.__osd_notification.display(title, line=0)
188 self.__osd_notification.display(message, line=1)
189
190 def __send_message_to_queue(self, title, message):
191 """
192 Enqueues the message
193 """
194 if len(self.__message_queue) >= self.__queue_size:
195 self.__message_queue.pop(0)
196 self.__message_queue.append({'title': title, 'message': message})
197
198
199def beinc_login_required(method):
200 """
201 decorator for checking login credentials
202 """
203
204 def wrapper(self, resource_name, password, *args, **kwargs):
205 try:
206 instance = self.instances[resource_name]
207 except Exception as e:
208 raise xmlrpc.Fault(401,
209 'Wrong instance or password')
210 if not instance.password_match(password):
211 raise xmlrpc.Fault(401,
212 'Wrong instance or password')
213 return method(self, resource_name, password, *args, **kwargs)
214 return wrapper
215
216
217class XMLRPCNotifyServer(xmlrpc.XMLRPC):
218 """
219 A class representing the entire server
220 """
221
222 def __init__(self, config):
223 """
224 """
225 xmlrpc.XMLRPC.__init__(self)
226 self.__config = config
227 self.__instances = dict()
228 # initialize pynotify if the module exists and if needed
229 if pynotify:
230 for instance in self.__config['server']['instances']:
231 # check if we have at least one instance that uses pynotify
232 # before initializing it
233 if instance.get('osd_system', '').lower() == 'pynotify':
234 if not pynotify.init('BEINC Notify'):
235 sys.stderr.write('pynotify.init failed! Exiting...\n')
236 sys.exit(1)
237 break
238 try:
239 for instance in self.__config['server']['instances']:
240 self.__instances[instance['name']] = BEINCInstance(instance)
241 print('Instance "{0}" added'.format(instance['name']))
242 except Exception as e:
243 sys.stderr.write('Unable to create instance "{0}": {1}\n'.format(
244 instance['name'],
245 e))
246 sys.exit(1)
247
248 @property
249 def instances(self):
250 """
251 a property that returns the instance list (read-only)
252 """
253 return self.__instances
254
255 @beinc_login_required
256 def xmlrpc_push(self, resource_name, password, title, message):
257 """
258 Return all passed args.
259 """
260 instance = self.__instances[resource_name]
261 try:
262 instance.send_message(title, message)
263 return 'OK'
264 except Exception as e:
265 sys.stderr.write(
266 'Unable to handle message in {0}: ({1})\n'.format(
267 instance.name,
268 e))
269 raise xmlrpc.Fault(500, 'Unable to send message')
270
271 def xmlrpc_pull(self, resource_name, password):
272 """
273 Return sum of arguments.
274 """
275 instance = self.__instances[args[0]]
276 if not instance.queueable:
277 raise xmlrpc.Fault(
278 405,
279 'BEINC instance "{0}" does not support queuing'.format(
280 instance.name))
281 return instance.get_queue()
282
283
284def main():
285 """
286 """
287 parser = argparse.ArgumentParser(
288 description='The following options are available')
289 parser.add_argument(
290 '-d',
291 action='store_true',
292 dest='daemonize',
293 default=False,
294 help='Run the BEINC-server in the background')
295 parser.add_argument(
296 '-H', '--hostname',
297 metavar='HOSTNAME',
298 type=str,
299 dest='hostname',
300 default='127.0.0.1',
301 help='BEINC server IP / hostname (default: 127.0.0.1)')
302 parser.add_argument(
303 '-p', '--port',
304 metavar='PORT',
305 type=int,
306 dest='port',
307 default=9998,
308 help='BEINC server port (default: 9998)')
309 parser.add_argument(
310 '-f', '--config-file',
311 metavar='FILE',
312 type=str,
313 default=os.path.expanduser('~/.beinc_server.json'),
314 dest='config_file',
315 help='BEINC config file (default: ~/.beinc_server.json)')
316 parser.add_argument(
317 '-v', '--version',
318 action='version',
319 version='%(prog)s {0}'.format(__version__),
320 help='Display program-version and exit')
321 args = parser.parse_args()
322 try:
323 with open(args.config_file, 'r') as fp:
324 config_dict = json.load(fp)
325 except Exception as e:
326 sys.stderr.write('Unable to parse {0}: {1}'.format(args.config_file,
327 e))
328 sys.exit(errno.EIO)
329 ssl_certificate = config_dict['server']['general']['ssl_certificate']
330 ssl_private_key = config_dict['server']['general']['ssl_private_key']
331 try:
332 beinc_server = XMLRPCNotifyServer(config_dict)
333 cert_path = FilePath(
334 config_dict['server']['general']['ssl_certificate'])
335 key_path = FilePath(
336 config_dict['server']['general']['ssl_private_key'])
337 private_certificate = ssl.PrivateCertificate.loadPEM(
338 key_path.getContent() + cert_path.getContent())
339 options = private_certificate.options()
340 options.method = OpenSSL.SSL.TLSv1_2_METHOD
341 # options.acceptableCiphers =
342 # ssl.AcceptableCiphers.fromOpenSSLCipherString('EXP-RC4-MD5')
343 reactor.listenSSL(args.port,
344 server.Site(beinc_server),
345 options,
346 interface=args.hostname)
347 reactor.run()
348 except Exception as e:
349 sys.stderr.write("WebServer error: {0}".format(e))
350 sys.exit(1)
351 sys.exit(0)
352
353
354if __name__ == "__main__":
355 main()