1 # $Id: roundupdb.py,v 1.3 2001-07-23 07:14:41 richard Exp $
3 import re, os, smtplib, socket
5 import hyperdb, date
7 def splitDesignator(designator, dre=re.compile(r'([^\d]+)(\d+)')):
8 ''' Take a foo123 and return ('foo', 123)
9 '''
10 m = dre.match(designator)
11 return m.group(1), m.group(2)
13 class Database:
14 def getuid(self):
15 """Return the id of the "user" node associated with the user
16 that owns this connection to the hyperdatabase."""
17 return self.user.lookup(self.journaltag)
19 def uidFromAddress(self, address):
20 ''' address is from the rfc822 module, and therefore is (name, addr)
22 user is created if they don't exist in the db already
23 '''
24 (realname, address) = address
25 users = self.user.stringFind(address=address)
26 if users: return users[0]
27 return self.user.create(username=address, address=address,
28 realname=realname)
30 # XXX: added the 'creator' faked attribute
31 class Class(hyperdb.Class):
32 # Overridden methods:
33 def __init__(self, db, classname, **properties):
34 hyperdb.Class.__init__(self, db, classname, **properties)
35 self.auditors = {'create': [], 'set': [], 'retire': []}
36 self.reactors = {'create': [], 'set': [], 'retire': []}
38 def create(self, **propvalues):
39 """These operations trigger detectors and can be vetoed. Attempts
40 to modify the "creation" or "activity" properties cause a KeyError.
41 """
42 if propvalues.has_key('creation') or propvalues.has_key('activity'):
43 raise KeyError, '"creation" and "activity" are reserved'
44 for audit in self.auditors['create']:
45 audit(self.db, self, None, propvalues)
46 nodeid = hyperdb.Class.create(self, **propvalues)
47 for react in self.reactors['create']:
48 react(self.db, self, nodeid, None)
49 return nodeid
51 def set(self, nodeid, **propvalues):
52 """These operations trigger detectors and can be vetoed. Attempts
53 to modify the "creation" or "activity" properties cause a KeyError.
54 """
55 if propvalues.has_key('creation') or propvalues.has_key('activity'):
56 raise KeyError, '"creation" and "activity" are reserved'
57 for audit in self.auditors['set']:
58 audit(self.db, self, nodeid, propvalues)
59 oldvalues = self.db.getnode(self.classname, nodeid)
60 hyperdb.Class.set(self, nodeid, **propvalues)
61 for react in self.reactors['set']:
62 react(self.db, self, nodeid, oldvalues)
64 def retire(self, nodeid):
65 """These operations trigger detectors and can be vetoed. Attempts
66 to modify the "creation" or "activity" properties cause a KeyError.
67 """
68 for audit in self.auditors['retire']:
69 audit(self.db, self, nodeid, None)
70 hyperdb.Class.retire(self, nodeid)
71 for react in self.reactors['retire']:
72 react(self.db, self, nodeid, None)
74 def get(self, nodeid, propname):
75 """Attempts to get the "creation" or "activity" properties should
76 do the right thing
77 """
78 if propname == 'creation':
79 journal = self.db.getjournal(self.classname, nodeid)
80 if journal:
81 return self.db.getjournal(self.classname, nodeid)[0][1]
82 else:
83 # on the strange chance that there's no journal
84 return date.Date()
85 if propname == 'activity':
86 journal = self.db.getjournal(self.classname, nodeid)
87 if journal:
88 return self.db.getjournal(self.classname, nodeid)[-1][1]
89 else:
90 # on the strange chance that there's no journal
91 return date.Date()
92 if propname == 'creator':
93 journal = self.db.getjournal(self.classname, nodeid)
94 if journal:
95 name = self.db.getjournal(self.classname, nodeid)[0][2]
96 else:
97 return None
98 return self.db.user.lookup(name)
99 return hyperdb.Class.get(self, nodeid, propname)
101 def getprops(self):
102 """In addition to the actual properties on the node, these
103 methods provide the "creation" and "activity" properties."""
104 d = hyperdb.Class.getprops(self).copy()
105 d['creation'] = hyperdb.Date()
106 d['activity'] = hyperdb.Date()
107 d['creator'] = hyperdb.Link("user")
108 return d
110 # New methods:
112 def audit(self, event, detector):
113 """Register a detector
114 """
115 self.auditors[event].append(detector)
117 def react(self, event, detector):
118 """Register a detector
119 """
120 self.reactors[event].append(detector)
122 class FileClass(Class):
123 def create(self, **propvalues):
124 ''' snaffle the file propvalue and store in a file
125 '''
126 content = propvalues['content']
127 del propvalues['content']
128 newid = Class.create(self, **propvalues)
129 self.setcontent(self.classname, newid, content)
130 return newid
132 def filename(self, classname, nodeid):
133 # TODO: split into multiple files directories
134 return os.path.join(self.db.dir, 'files', '%s%s'%(classname, nodeid))
136 def setcontent(self, classname, nodeid, content):
137 ''' set the content file for this file
138 '''
139 open(self.filename(classname, nodeid), 'wb').write(content)
141 def getcontent(self, classname, nodeid):
142 ''' get the content file for this file
143 '''
144 return open(self.filename(classname, nodeid), 'rb').read()
146 def get(self, nodeid, propname):
147 ''' trap the content propname and get it from the file
148 '''
149 if propname == 'content':
150 return self.getcontent(self.classname, nodeid)
151 return Class.get(self, nodeid, propname)
153 def getprops(self):
154 ''' In addition to the actual properties on the node, these methods
155 provide the "content" property.
156 '''
157 d = Class.getprops(self).copy()
158 d['content'] = hyperdb.String()
159 return d
161 # XXX deviation from spec - was called ItemClass
162 class IssueClass(Class):
163 # Overridden methods:
165 def __init__(self, db, classname, **properties):
166 """The newly-created class automatically includes the "messages",
167 "files", "nosy", and "superseder" properties. If the 'properties'
168 dictionary attempts to specify any of these properties or a
169 "creation" or "activity" property, a ValueError is raised."""
170 if not properties.has_key('title'):
171 properties['title'] = hyperdb.String()
172 if not properties.has_key('messages'):
173 properties['messages'] = hyperdb.Multilink("msg")
174 if not properties.has_key('files'):
175 properties['files'] = hyperdb.Multilink("file")
176 if not properties.has_key('nosy'):
177 properties['nosy'] = hyperdb.Multilink("user")
178 if not properties.has_key('superseder'):
179 properties['superseder'] = hyperdb.Multilink("issue")
180 if (properties.has_key('creation') or properties.has_key('activity')
181 or properties.has_key('creator')):
182 raise ValueError, '"creation", "activity" and "creator" are reserved'
183 Class.__init__(self, db, classname, **properties)
185 # New methods:
187 def addmessage(self, nodeid, summary, text):
188 """Add a message to an issue's mail spool.
190 A new "msg" node is constructed using the current date, the user that
191 owns the database connection as the author, and the specified summary
192 text.
194 The "files" and "recipients" fields are left empty.
196 The given text is saved as the body of the message and the node is
197 appended to the "messages" field of the specified issue.
198 """
200 def sendmessage(self, nodeid, msgid):
201 """Send a message to the members of an issue's nosy list.
203 The message is sent only to users on the nosy list who are not
204 already on the "recipients" list for the message.
206 These users are then added to the message's "recipients" list.
207 """
208 # figure the recipient ids
209 recipients = self.db.msg.get(msgid, 'recipients')
210 r = {}
211 for recipid in recipients:
212 r[recipid] = 1
213 authid = self.db.msg.get(msgid, 'author')
214 r[authid] = 1
216 # now figure the nosy people who weren't recipients
217 sendto = []
218 nosy = self.get(nodeid, 'nosy')
219 for nosyid in nosy:
220 if not r.has_key(nosyid):
221 sendto.append(nosyid)
222 recipients.append(nosyid)
224 if sendto:
225 # update the message's recipients list
226 self.db.msg.set(msgid, recipients=recipients)
228 # send an email to the people who missed out
229 sendto = [self.db.user.get(i, 'address') for i in recipients]
230 cn = self.classname
231 title = self.get(nodeid, 'title') or '%s message copy'%cn
232 m = ['Subject: [%s%s] %s'%(cn, nodeid, title)]
233 m.append('To: %s'%', '.join(sendto))
234 m.append('Reply-To: %s'%self.ISSUE_TRACKER_EMAIL)
235 m.append('')
236 m.append(self.db.msg.get(msgid, 'content'))
237 # TODO attachments
238 try:
239 smtp = smtplib.SMTP(self.MAILHOST)
240 smtp.sendmail(self.ISSUE_TRACKER_EMAIL, sendto, '\n'.join(m))
241 except socket.error, value:
242 return "Couldn't send confirmation email: mailhost %s"%value
243 except smtplib.SMTPException, value:
244 return "Couldn't send confirmation email: %s"%value
246 #
247 # $Log: not supported by cvs2svn $
248 # Revision 1.2 2001/07/22 12:09:32 richard
249 # Final commit of Grande Splite
250 #
251 # Revision 1.1 2001/07/22 11:58:35 richard
252 # More Grande Splite
253 #