source: mailjam/postman/daemon.py@ 10:d5329a2a05b7

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

Fully added support for the configuration file. Now all the code uses
postman.conf to read the configuration parameters from it

Added a tests/postman.conf configuration file with specific configurtion
to use while testing (mostly tmp paths)

Updated the run_tests script to delete the temporary directory where
data is saved while running tests

Updated the tests modules/files so they use the tests config file

File size: 9.2 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
176
177
178class PostmanDaemon():
179 def __init__(self, configfile=None):
180 self.config = config.get_config_parameters('xmlrpc_server', configfile)
181
182 # FIXME: These should be loaded from a config file
183 self.address = self.config.get('address', 'localhost')
184 self.port = int(self.config.get('port', 9876))
185 self.logfile = self.config.get('logfile',
186 os.path.join(os.path.dirname(__file__),
187 '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 if not self.server:
200 msg = 'Creating XMLRPC server object on {}:{}'.format(self.address,
201 self.port)
202 logging.info(msg)
203 self.server = SimpleXMLRPCServer((self.address, self.port),
204 allow_none=True,
205 logRequests=False)
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 return self.ready_to_serve
228
229 def run(self):
230 """
231 Run the xmlrpc daemon. If self.ready_to_serve is False, call
232 self.add_methods, which will initialize the server and will register all
233 the public methods into that server
234 """
235 if not self.ready_to_serve:
236 self.add_methods()
237 msg = 'Starting XMLRPC server on {}:{}'.format(self.address,
238 self.port)
239 logging.info(msg)
240 try:
241 self.server.serve_forever()
242 except KeyboardInterrupt:
243 msg = 'Stopping server'
244 logging.info(msg)
Note: See TracBrowser for help on using the repository browser.