source: mailjam/postman/daemon.py@ 6:2a0f6f8cbc52

Last change on this file since 6:2a0f6f8cbc52 was 6:2a0f6f8cbc52, checked in by Francisco de Borja Lopez Rio <borja@…>, 12 years ago

Added an initial version of the code that will handle the XMLRPC interface
of the server.

Added a script to start the server (not fully functional yet)

File size: 8.9 KB
Line 
1# -*- coding: utf-8 -*-
2
3import os, inspect, logging
4from SimpleXMLRPCServer import SimpleXMLRPCServer, list_public_methods
5
6from postman import config
7from postman.models import Member, MailingList
8from postman.storage import JsonStorage as Storage
9
10
11class Postman():
12
13 def __init__(self, configfile=None):
14 if not configfile:
15 # FIXME: This is not used right now, we are getting the stuff
16 # from postman.config, but we will move configurations to a
17 # external ini-style config file soon
18 configfile = os.path.join(os.path.dirname(__file__),
19 '../conf/postman.conf')
20 self.configfile = configfile
21
22 # lists were the currently managed mailing lists information is going
23 # to be saved
24 self.mailings = {}
25 self.mailings_addresses = []
26
27 # the files were internal information (like active mailing lists,
28 # members, etc) is saved
29 self.dbs = {'mailings': Storage(os.path.join(config.storage_path,
30 'mailings.json')),
31 'members': Storage(os.path.join(config.storage_path,
32 'members.json'))}
33
34 def save(self):
35 """
36 Save all the current managed data to disk
37 """
38 if self.mailings:
39 # Save the config file from where we can reload information about
40 # the mailing lists managed by this postman instance
41 self.dbs['mailings'].write(self.mailings_addresses)
42 # Save each mailing list data into its separated persistence file
43 for m in self.mailings.keys():
44 self.mailings[m].save()
45 return True
46 return False
47
48 def load(self):
49 """
50 Load all data from the storage files
51 """
52 if self.dbs['mailings'].exists():
53 # load the list of managed mailing lists
54 # FIXME: This is quite naive, we do not perform any check here after
55 # loading the data from the json file, which can be modified by
56 # untrustred users.
57 self.mailings_addresses = self.dbs['mailings'].read()
58
59 # now load all the mailing objects:
60 for address in self.mailings_addresses:
61 mailing = MailingList(address, address)
62 mailing.load()
63 self.mailings[address] = mailing
64 return True
65 return False
66
67 def clear(self):
68 """
69 Delete all stored data from disk (useful for testing).
70 DANGER: Calling this method will remove all data from disk, leaving the
71 postman instance with no persistence data, if the postman process die,
72 before another .save() call is made, all data will be lost.
73 """
74 if self.dbs['mailings'].exists():
75 # We do not delete each mailing list file, but only the file
76 # containing the list of existing mailing lists
77 self.dbs['mailings'].delete()
78 return True
79 return False
80
81 def add_mailing_list(self, info={}):
82 """
83 Add a new mailing list to this postman instance. expects one parameter,
84 info, which is a dictionary that should contain, at least, the
85 following keys:
86
87 - name: (string) the name we will give to the list
88 - address: (string) the email address of the list
89 - members: (list) a list of email adddress of the list members
90
91 """
92 if not isinstance(info, dict):
93 raise TypeError(info, ' is not a valid dictionary')
94
95 if 'name' not in info.keys() or \
96 'address' not in info.keys() or \
97 'members' not in info.keys() or \
98 'config' not in info.keys():
99 raise ValueError(info, ' does not seem to be a valid configuration')
100
101 if info['address'] in self.mailings_addresses:
102 raise IndexError(info['address'],
103 ' has been already added to postman')
104
105 mailing = MailingList(info['name'], info['address'],
106 info['members'], info['config'])
107 self.mailings[mailing.address] = mailing
108 self.mailings_addresses.append(mailing.address)
109 # After adding new mailings, save them to disk
110 self.save()
111 return True
112
113 def add_mailing_member(self, member_addr=None, list_addr=None):
114 """
115 Add a new member for the mailing list represented by list_addr (a string
116 containing the email address of any mailing list managed by this postman
117 instance). member_addr is a string representing the email address of the
118 new member
119 """
120
121 if not member_addr:
122 raise ValueError(member_addr, 'missing member address')
123
124 if not list_addr:
125 raise ValueError(list_addr, 'missing list address')
126
127 if list_addr not in self.mailings_addresses:
128 # FIXME: Perhaps we should add it, perhaps not (mispelled address?)
129 raise IndexError(list_addr, ' is not a valid mailing list')
130
131 added = self.mailings[list_addr].add_member_by_address(member_addr)
132 if added:
133 self.save()
134 return added
135
136
137class PostmanXMLRPC():
138 """
139 This class is a wrapper we will use to limit the methods that will be
140 published through the XMLRPC link. Only the methods from this class
141 will be available through that link.
142
143 As we use dotted names to separate xmlrpc-exported methods into different
144 namespaces, this class contains nothing, it will be used only for
145 method-registering purposes. The MailingListXMLRPC and MemberXMLRPC classes
146 contain the actual methods that are published.
147
148 More information on this approach here:
149
150 http://www.doughellmann.com/PyMOTW/SimpleXMLRPCServer/#exposing-methods-of-objects
151 """
152
153 def _listMethods(self):
154 return list_public_methods(self)
155
156 def _methodHelp(self, method):
157 f = getattr(self, method)
158 return inspect.getdoc(f)
159
160
161class MailingListXMLRPC():
162 def __init__(self):
163 self.postman = Postman()
164 self.postman.load()
165 def add(self, info={}):
166 self.postman.add_mailing_list(info)
167
168
169class MemberXMLRPC():
170 def __init__(self):
171 self.postman = Postman()
172 self.postman.load()
173 def add(self, member_addr=None, list_addr=None):
174 self.postman.add_mailing_member(member_addr, list_addr)
175
176
177class PostmanDaemon():
178 def __init__(self, configfile=None):
179 if not configfile:
180 # FIXME: This is not used right now, we are getting the stuff
181 # from postman.config, but we will move configurations to a
182 # external ini-style config file soon
183 configfile = os.path.join(os.path.dirname(__file__),
184 '../conf/postman.conf')
185 self.configfile = configfile
186
187 self.logfile = os.path.join(os.path.dirname(__file__), 'server.log')
188 logging.basicConfig(filename=self.logfile, level=logging.DEBUG)
189
190 self.server = None
191 self.ready_to_serve = False
192
193 def create_server(self):
194 """
195 If there is no server initialized in self.server, create an instance
196 of SimpleXMLRPCServer in that attribute. If there is already a server
197 initialized there, simply return True
198 """
199 address='localhost'
200 port = 9000
201 if not self.server:
202 msg = 'Creating XMLRPC server object on {}:{}'.format(address,port)
203 logging.info(msg)
204 self.server = SimpleXMLRPCServer((address, port), allow_none=True,
205 logRequests=True)
206 self.server.register_introspection_functions()
207 return True
208
209 def add_methods(self):
210 """
211 Check if there is an initialized server (initialize it if there is none)
212 and then register all the Postman public methods to be served through
213 the xml-rpc link
214
215 Once the methods are registered set self.ready_to_serve to True
216 """
217 if not self.server:
218 # ensure there is an XMLRPC server initialized
219 self.create_server()
220 msg = 'Registering public methods'
221 logging.info(msg)
222 root = PostmanXMLRPC()
223 root.lists = MailingListXMLRPC()
224 root.members = MemberXMLRPC()
225 self.server.register_instance(root, allow_dotted_names=True)
226 self.ready_to_serve = True
227
228 def run(self):
229 """
230 Run the xmlrpc daemon. If self.ready_to_serve is False, call
231 self.add_methods, which will initialize the server and will register all
232 the public methods into that server
233 """
234 if not self.ready_to_serve:
235 self.add_methods()
236 msg = 'Starting XMLRPC server on {}:{}'.format(address,port)
237 logging.info(msg)
238 self.server.serve_forever()
Note: See TracBrowser for help on using the repository browser.