source: mailjam/postman/daemon.py@ 13:f1f11159dbed

Last change on this file since 13:f1f11159dbed was 13:f1f11159dbed, checked in by Borja Lopez <borja@…>, 12 years ago

Added first version of the project documentation. Inside docs/src there is
the sphinx [1] project we use to manage the docs. Inside docs/html there is
a copy of the docs in html format.

Fixed some typos in the INSTALL file

Added two new parameters to the postman.conf file (both the example config
file inside conf/ and the one used for testing in postman/tests/). These
parameters do nothing yet (they are related to ssl stuff) but we do talk
about them in the docs.

Added a new method to the XMLRPC list of methods related to members.

[1] http://sphinx.pocoo.org

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