source: mailjam/mailjam/daemon.py@ 20:bf238ca0c37f

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

Fixed multiple bugs found while doing manual tests of both the server and
MTA client. All of those bugs are related to how configuration files are
loaded.

File size: 9.9 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) if '_' not in i]
166 public_methods += ['members.'+i for i in dir(MemberXMLRPC) if '_' not in i]
167 return public_methods
168
169 def _methodHelp(self, method):
170 f = getattr(self, method)
171 return inspect.getdoc(f)
172
173
174class MailingListXMLRPC():
175 def __init__(self, configfile=None):
176 self.mailjam = Mailjam(configfile=configfile)
177 self.mailjam.load()
178 def add(self, info={}):
179 self.mailjam.add_mailing_list(info)
180 def addresses(self):
181 return self.mailjam.mailings_addresses
182
183
184class MemberXMLRPC():
185 def __init__(self, configfile=None):
186 self.mailjam = Mailjam(configfile=configfile)
187 self.mailjam.load()
188 def add(self, member_addr=None, list_addr=None):
189 self.mailjam.add_mailing_member(member_addr, list_addr)
190 def list(self, mailing):
191 if mailing in self.mailjam.mailings_addresses:
192 return self.mailjam.mailings[mailing].members_addresses()
193
194
195class MailjamDaemon():
196 def __init__(self, configfile=None):
197 self.configfile = configfile
198 self.config = config.get_config_parameters('xmlrpc_server', configfile)
199 self.address = self.config.get('address', 'localhost')
200 self.port = int(self.config.get('port', 9876))
201 self.logfile = self.config.get('logfile',
202 os.path.join(os.path.dirname(__file__),
203 'server.log'))
204 logging.basicConfig(filename=self.logfile, level=logging.DEBUG)
205 self.server = None
206 self.ready_to_serve = False
207
208 def create_server(self):
209 """
210 If there is no server initialized in self.server, create an instance
211 of SimpleXMLRPCServer in that attribute. If there is already a server
212 initialized there, simply return True
213 """
214 if not self.server:
215 msg = 'Creating XMLRPC server object on {}:{}'.format(self.address,
216 self.port)
217 logging.info(msg)
218 self.server = SimpleXMLRPCServer((self.address, self.port),
219 allow_none=True,
220 logRequests=False)
221 self.server.register_introspection_functions()
222 return True
223
224 def add_methods(self):
225 """
226 Check if there is an initialized server (initialize it if there is none)
227 and then register all the Mailjam public methods to be served through
228 the xml-rpc link
229
230 Once the methods are registered set self.ready_to_serve to True
231 """
232 if not self.server:
233 # ensure there is an XMLRPC server initialized
234 self.create_server()
235 msg = 'Registering public methods'
236 logging.info(msg)
237 root = MailjamXMLRPC(self.configfile)
238 root.lists = MailingListXMLRPC(self.configfile)
239 root.members = MemberXMLRPC(self.configfile)
240 self.server.register_instance(root, allow_dotted_names=True)
241 self.ready_to_serve = True
242 return self.ready_to_serve
243
244 def run(self):
245 """
246 Run the xmlrpc daemon. If self.ready_to_serve is False, call
247 self.add_methods, which will initialize the server and will register all
248 the public methods into that server
249 """
250 if not self.ready_to_serve:
251 self.add_methods()
252 msg = 'Starting XMLRPC server on {}:{}'.format(self.address,
253 self.port)
254 logging.info(msg)
255 try:
256 self.server.serve_forever()
257 except KeyboardInterrupt:
258 msg = 'Stopping server'
259 logging.info(msg)
Note: See TracBrowser for help on using the repository browser.