source: mailjam/mailjam/daemon.py@ 31:86fa46480912

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

Fixed bug #4 (Mailing lists are not available after adding them)

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