Mercurial > p > roundup > code
diff roundup/roundupdb.py @ 475:a1a44636bace
Fix breakage caused by transaction changes.
Sorry for the huge checkin message - I was only intending to implement
[SF#496356] but I found a number of places where things had been
broken by transactions:
. modified ROUNDUPDBSENDMAILDEBUG to be SENDMAILDEBUG and hold a filename
for _all_ roundup-generated smtp messages to be sent to.
. the transaction cache had broken the roundupdb.Class set() reactors
. newly-created author users in the mailgw weren't being committed to the db
Stuff that made it into CHANGES.txt (ie. the stuff I was actually working
on when I found that stuff :):
. [SF#496356] Use threading in messages
. detectors were being registered multiple times
. added tests for mailgw
. much better attaching of erroneous messages in the mail gateway
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Wed, 02 Jan 2002 02:31:38 +0000 |
| parents | 103f521810f7 |
| children | 9ad589d0a60f c242455d9b46 |
line wrap: on
line diff
--- a/roundup/roundupdb.py Mon Dec 31 05:20:34 2001 +0000 +++ b/roundup/roundupdb.py Wed Jan 02 02:31:38 2002 +0000 @@ -15,20 +15,21 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: roundupdb.py,v 1.35 2001-12-20 15:43:01 rochecompaan Exp $ +# $Id: roundupdb.py,v 1.36 2002-01-02 02:31:38 richard Exp $ __doc__ = """ Extending hyperdb with types specific to issue-tracking. """ -import re, os, smtplib, socket, copy +import re, os, smtplib, socket, copy, time, random import mimetools, MimeWriter, cStringIO import base64, mimetypes import hyperdb, date # set to indicate to roundup not to actually _send_ email -ROUNDUPDBSENDMAILDEBUG = os.environ.get('ROUNDUPDBSENDMAILDEBUG', '') +# this var must contain a file to write the mail to +SENDMAILDEBUG = os.environ.get('SENDMAILDEBUG', '') class DesignatorError(ValueError): pass @@ -72,6 +73,7 @@ # couldn't match address or username, so create a new user if create: + print 'CREATING USER', address return self.user.create(username=address, address=address, realname=realname) else: @@ -110,9 +112,16 @@ raise KeyError, '"creation" and "activity" are reserved' for audit in self.auditors['set']: audit(self.db, self, nodeid, propvalues) - # take a copy of the node dict so that the subsequent set - # operation doesn't modify the oldvalues structure - oldvalues = copy.deepcopy(self.db.getnode(self.classname, nodeid)) + # Take a copy of the node dict so that the subsequent set + # operation doesn't modify the oldvalues structure. + try: + # try not using the cache initially + oldvalues = copy.deepcopy(self.db.getnode(self.classname, nodeid, + cache=0)) + except IndexError: + # this will be needed if somone does a create() and set() + # with no intervening commit() + oldvalues = copy.deepcopy(self.db.getnode(self.classname, nodeid)) hyperdb.Class.set(self, nodeid, **propvalues) for react in self.reactors['set']: react(self.db, self, nodeid, oldvalues) @@ -127,7 +136,7 @@ for react in self.reactors['retire']: react(self.db, self, nodeid, None) - def get(self, nodeid, propname, default=_marker): + def get(self, nodeid, propname, default=_marker, cache=1): """Attempts to get the "creation" or "activity" properties should do the right thing. """ @@ -153,9 +162,10 @@ return None return self.db.user.lookup(name) if default is not _marker: - return hyperdb.Class.get(self, nodeid, propname, default) + return hyperdb.Class.get(self, nodeid, propname, default, + cache=cache) else: - return hyperdb.Class.get(self, nodeid, propname) + return hyperdb.Class.get(self, nodeid, propname, cache=cache) def getprops(self, protected=1): """In addition to the actual properties on the node, these @@ -176,12 +186,16 @@ def audit(self, event, detector): """Register a detector """ - self.auditors[event].append(detector) + l = self.auditors[event] + if detector not in l: + self.auditors[event].append(detector) def react(self, event, detector): """Register a detector """ - self.reactors[event].append(detector) + l = self.reactors[event] + if detector not in l: + self.reactors[event].append(detector) class FileClass(Class): @@ -194,15 +208,15 @@ self.db.storefile(self.classname, newid, None, content) return newid - def get(self, nodeid, propname, default=_marker): + def get(self, nodeid, propname, default=_marker, cache=1): ''' trap the content propname and get it from the file ''' if propname == 'content': return self.db.getfile(self.classname, nodeid, None) if default is not _marker: - return Class.get(self, nodeid, propname, default) + return Class.get(self, nodeid, propname, default, cache=cache) else: - return Class.get(self, nodeid, propname) + return Class.get(self, nodeid, propname, cache=cache) def getprops(self, protected=1): ''' In addition to the actual properties on the node, these methods @@ -270,25 +284,28 @@ These users are then added to the message's "recipients" list. """ + users = self.db.user + messages = self.db.msg + files = self.db.file + # figure the recipient ids - recipients = self.db.msg.get(msgid, 'recipients') + sendto = [] r = {} - for recipid in recipients: + recipients = messages.get(msgid, 'recipients') + for recipid in messages.get(msgid, 'recipients'): r[recipid] = 1 - rlen = len(recipients) # figure the author's id, and indicate they've received the message - authid = self.db.msg.get(msgid, 'author') + authid = messages.get(msgid, 'author') # get the current nosy list, we'll need it nosy = self.get(nodeid, 'nosy') - # ... but duplicate the message to the author as long as it's not - # the anonymous user + # possibly send the message to the author, as long as they aren't + # anonymous if (self.MESSAGES_TO_AUTHOR == 'yes' and - self.db.user.get(authid, 'username') != 'anonymous'): - if not r.has_key(authid): - recipients.append(authid) + users.get(authid, 'username') != 'anonymous'): + sendto.append(authid) r[authid] = 1 # now figure the nosy people who weren't recipients @@ -296,26 +313,40 @@ # Don't send nosy mail to the anonymous user (that user # shouldn't appear in the nosy list, but just in case they # do...) - if self.db.user.get(nosyid, 'username') == 'anonymous': continue + if users.get(nosyid, 'username') == 'anonymous': + continue + # make sure they haven't seen the message already if not r.has_key(nosyid): + # send it to them + sendto.append(nosyid) recipients.append(nosyid) # no new recipients - if rlen == len(recipients): + if not sendto: return + # determine the messageid and inreplyto of the message + inreplyto = messages.get(msgid, 'inreplyto') + messageid = messages.get(msgid, 'messageid') + if not messageid: + # this is an old message that didn't get a messageid, so + # create one + messageid = "%s.%s.%s%s-%s"%(time.time(), random.random(), + self.classname, nodeid, self.MAIL_DOMAIN) + messages.set(msgid, messageid=messageid) + # update the message's recipients list - self.db.msg.set(msgid, recipients=recipients) + messages.set(msgid, recipients=recipients) # send an email to the people who missed out - sendto = [self.db.user.get(i, 'address') for i in recipients] + sendto = [users.get(i, 'address') for i in sendto] cn = self.classname title = self.get(nodeid, 'title') or '%s message copy'%cn # figure author information - authname = self.db.user.get(authid, 'realname') + authname = users.get(authid, 'realname') if not authname: - authname = self.db.user.get(authid, 'username') - authaddr = self.db.user.get(authid, 'address') + authname = users.get(authid, 'username') + authaddr = users.get(authid, 'address') if authaddr: authaddr = ' <%s>'%authaddr else: @@ -336,7 +367,7 @@ m.append('') # add the content - m.append(self.db.msg.get(msgid, 'content')) + m.append(messages.get(msgid, 'content')) # add the change note if change_note: @@ -347,7 +378,7 @@ m.append(self.email_signature(nodeid, msgid)) # get the files for this message - files = self.db.msg.get(msgid, 'files') + files = messages.get(msgid, 'files') # create the message message = cStringIO.StringIO() @@ -358,6 +389,10 @@ writer.addheader('Reply-To', '%s <%s>'%(self.INSTANCE_NAME, self.ISSUE_TRACKER_EMAIL)) writer.addheader('MIME-Version', '1.0') + if messageid: + writer.addheader('Message-Id', messageid) + if inreplyto: + writer.addheader('In-Reply-To', inreplyto) # attach files if files: @@ -366,9 +401,9 @@ body = part.startbody('text/plain') body.write('\n'.join(m)) for fileid in files: - name = self.db.file.get(fileid, 'name') - mime_type = self.db.file.get(fileid, 'type') - content = self.db.file.get(fileid, 'content') + name = files.get(fileid, 'name') + mime_type = files.get(fileid, 'type') + content = files.get(fileid, 'content') part = writer.nextpart() if mime_type == 'text/plain': part.addheader('Content-Disposition', @@ -394,21 +429,21 @@ body.write('\n'.join(m)) # now try to send the message - try: - if ROUNDUPDBSENDMAILDEBUG: - print 'From: %s\nTo: %s\n%s\n=-=-=-=-=-=-=-='%( - self.ADMIN_EMAIL, sendto, message.getvalue()) - else: + if SENDMAILDEBUG: + open(SENDMAILDEBUG, 'w').write('FROM: %s\nTO: %s\n%s\n'%( + self.ADMIN_EMAIL, ', '.join(sendto), message.getvalue())) + else: + try: + # send the message as admin so bounces are sent there + # instead of to roundup smtp = smtplib.SMTP(self.MAILHOST) - # send the message as admin so bounces are sent there instead - # of to roundup smtp.sendmail(self.ADMIN_EMAIL, sendto, message.getvalue()) - except socket.error, value: - raise MessageSendError, \ - "Couldn't send confirmation email: mailhost %s"%value - except smtplib.SMTPException, value: - raise MessageSendError, \ - "Couldn't send confirmation email: %s"%value + except socket.error, value: + raise MessageSendError, \ + "Couldn't send confirmation email: mailhost %s"%value + except smtplib.SMTPException, value: + raise MessageSendError, \ + "Couldn't send confirmation email: %s"%value def email_signature(self, nodeid, msgid): ''' Add a signature to the e-mail with some useful information @@ -495,6 +530,14 @@ # # $Log: not supported by cvs2svn $ +# Revision 1.35 2001/12/20 15:43:01 rochecompaan +# Features added: +# . Multilink properties are now displayed as comma separated values in +# a textbox +# . The add user link is now only visible to the admin user +# . Modified the mail gateway to reject submissions from unknown +# addresses if ANONYMOUS_ACCESS is denied +# # Revision 1.34 2001/12/17 03:52:48 richard # Implemented file store rollback. As a bonus, the hyperdb is now capable of # storing more than one file per node - if a property name is supplied,
