diff --git a/roundup/mailgw.py b/roundup/mailgw.py
index c4967728f26bad8bdc267ae0234b68232194b970..ad9c10594e1aabf76365c9cf31997bc9d1c860a0 100644 (file)
--- a/roundup/mailgw.py
+++ b/roundup/mailgw.py
an exception, the original message is bounced back to the sender with the
explanatory message given in the exception.
-$Id: mailgw.py,v 1.67 2002-04-23 15:46:49 rochecompaan Exp $
+$Id: mailgw.py,v 1.75 2002-07-09 01:21:24 richard Exp $
'''
self.instance = instance
self.db = db
+ # should we trap exceptions (normal usage) or pass them through
+ # (for testing)
+ self.trapExceptions = 1
+
def main(self, fp):
''' fp - the file from which to read the Message.
'''
- self.handle_Message(Message(fp))
+ return self.handle_Message(Message(fp))
def handle_Message(self, message):
'''Handle an RFC822 Message
# its way into here... try to handle it gracefully
sendto = message.getaddrlist('from')
if sendto:
+ if not self.trapExceptions:
+ return self.handle_message(message)
try:
return self.handle_message(message)
except MailUsageHelp:
title = ''
# but we do need either a title or a nodeid...
- if not nodeid and not title:
+ if nodeid is None and not title:
raise MailUsageError, '''
I cannot match your message to a node in the database - you need to either
supply a full node identifier (with number, eg "[issue123]" or keep the
Subject was: "%s"
'''%subject
- # extract the args
- subject_args = m.group('args')
-
# If there's no nodeid, check to see if this is a followup and
# maybe someone's responded to the initial mail that created an
# entry. Try to find the matching nodes with the same title, and
# use the _last_ one matched (since that'll _usually_ be the most
# recent...)
- if not nodeid and m.group('refwd'):
+ if nodeid is None and m.group('refwd'):
l = cl.stringFind(title=title)
if l:
nodeid = l[-1]
- # start of the props
+ # if a nodeid was specified, make sure it's valid
+ if nodeid is not None and not cl.hasnode(nodeid):
+ raise MailUsageError, '''
+The node specified by the designator in the subject of your message ("%s")
+does not exist.
+
+Subject was: "%s"
+'''%(nodeid, subject)
+
+ #
+ # extract the args
+ #
+ subject_args = m.group('args')
+
+ #
+ # handle the subject argument list
+ #
+ # figure what the properties of this Class are
properties = cl.getprops()
props = {}
-
- # handle the args
args = m.group('args')
if args:
+ errors = []
for prop in string.split(args, ';'):
# extract the property name and value
try:
- key, value = prop.split('=')
+ propname, value = prop.split('=')
except ValueError, message:
- raise MailUsageError, '''
-Subject argument list not of form [arg=value,value,...;arg=value,value...]
- (specific exception message was "%s")
-
-Subject was: "%s"
-'''%(message, subject)
+ errors.append('not of form [arg=value,'
+ 'value,...;arg=value,value...]')
+ break
# ensure it's a valid property name
- key = key.strip()
+ propname = propname.strip()
try:
- proptype = properties[key]
+ proptype = properties[propname]
except KeyError:
- raise MailUsageError, '''
-Subject argument list refers to an invalid property: "%s"
-
-Subject was: "%s"
-'''%(key, subject)
+ errors.append('refers to an invalid property: '
+ '"%s"'%propname)
+ continue
# convert the string value to a real property value
if isinstance(proptype, hyperdb.String):
- props[key] = value.strip()
+ props[propname] = value.strip()
if isinstance(proptype, hyperdb.Password):
- props[key] = password.Password(value.strip())
+ props[propname] = password.Password(value.strip())
elif isinstance(proptype, hyperdb.Date):
try:
- props[key] = date.Date(value.strip())
+ props[propname] = date.Date(value.strip())
except ValueError, message:
- raise UsageError, '''
-Subject argument list contains an invalid date for %s.
-
-Error was: %s
-Subject was: "%s"
-'''%(key, message, subject)
+ errors.append('contains an invalid date for '
+ '%s.'%propname)
elif isinstance(proptype, hyperdb.Interval):
try:
- props[key] = date.Interval(value) # no strip needed
+ props[propname] = date.Interval(value)
except ValueError, message:
- raise UsageError, '''
-Subject argument list contains an invalid date interval for %s.
-
-Error was: %s
-Subject was: "%s"
-'''%(key, message, subject)
+ errors.append('contains an invalid date interval'
+ 'for %s.'%propname)
elif isinstance(proptype, hyperdb.Link):
linkcl = self.db.classes[proptype.classname]
propkey = linkcl.labelprop(default_to_id=1)
try:
- props[key] = linkcl.lookup(value)
+ props[propname] = linkcl.lookup(value)
except KeyError, message:
- raise MailUsageError, '''
-Subject argument list contains an invalid value for %s.
-
-Error was: %s
-Subject was: "%s"
-'''%(key, message, subject)
+ errors.append('"%s" is not a value for %s.'%(value,
+ propname))
elif isinstance(proptype, hyperdb.Multilink):
# get the linked class
linkcl = self.db.classes[proptype.classname]
propkey = linkcl.labelprop(default_to_id=1)
+ if nodeid:
+ curvalue = cl.get(nodeid, propname)
+ else:
+ curvalue = []
+
+ # handle each add/remove in turn
for item in value.split(','):
item = item.strip()
+
+ # handle +/-
+ remove = 0
+ if item.startswith('-'):
+ remove = 1
+ item = item[1:]
+ elif item.startswith('+'):
+ item = item[1:]
+
+ # look up the value
try:
item = linkcl.lookup(item)
except KeyError, message:
- raise MailUsageError, '''
-Subject argument list contains an invalid value for %s.
+ errors.append('"%s" is not a value for %s.'%(item,
+ propname))
+ continue
+
+ # perform the add/remove
+ if remove:
+ try:
+ curvalue.remove(item)
+ except ValueError:
+ errors.append('"%s" is not currently in '
+ 'for %s.'%(item, propname))
+ continue
+ else:
+ if item not in curvalue:
+ curvalue.append(item)
+
+ # that's it, set the new Multilink property value
+ props[propname] = curvalue
+
+ # handle any errors parsing the argument list
+ if errors:
+ errors = '\n- '.join(errors)
+ raise MailUsageError, '''
+There were problems handling your subject line argument list:
+- %s
-Error was: %s
Subject was: "%s"
-'''%(key, message, subject)
- if props.has_key(key):
- props[key].append(item)
- else:
- props[key] = [item]
+'''%(errors, subject)
#
# handle the users
r = recipient[1].strip().lower()
if r == tracker_email or not r:
continue
- recipients.append(self.db.uidFromAddress(recipient))
+
+ # look up the recipient - create if necessary (and we're
+ # allowed to)
+ recipient = self.db.uidFromAddress(recipient, create)
+
+ # if all's well, add the recipient to the list
+ if recipient:
+ recipients.append(recipient)
#
# handle message-id and in-reply-to
else:
content = self.get_part_data_decoded(message)
- keep_citations = self.instance.EMAIL_KEEP_QUOTED_TEXT == 'yes'
- keep_body = self.instance.EMAIL_LEAVE_BODY_UNCHANGED == 'yes'
+ # figure how much we should muck around with the email body
+ keep_citations = getattr(self.instance, 'EMAIL_KEEP_QUOTED_TEXT',
+ 'no') == 'yes'
+ keep_body = getattr(self.instance, 'EMAIL_LEAVE_BODY_UNCHANGED',
+ 'no') == 'yes'
+
+ # parse the body of the message, stripping out bits as appropriate
summary, content = parseContent(content, keep_citations,
keep_body)
files.append(self.db.file.create(type=mime_type, name=name,
content=data))
+ #
+ # create the message if there's a message body (content)
#
- # now handle the db stuff
- #
- if nodeid:
- # If an item designator (class name and id number) is found there,
- # the newly created "msg" node is added to the "messages" property
- # for that item, and any new "file" nodes are added to the "files"
- # property for the item.
-
- # if the message is currently 'unread' or 'resolved', then set
- # it to 'chatting'
- if properties.has_key('status'):
- try:
- # determine the id of 'unread', 'resolved' and 'chatting'
- unread_id = self.db.status.lookup('unread')
- resolved_id = self.db.status.lookup('resolved')
- chatting_id = self.db.status.lookup('chatting')
- except KeyError:
- pass
- else:
- current_status = cl.get(nodeid, 'status')
- if (not props.has_key('status') and
- current_status == unread_id or
- current_status == resolved_id):
- props['status'] = chatting_id
-
- # add nosy in arguments to issue's nosy list
- if not props.has_key('nosy'): props['nosy'] = []
- n = {}
- for nid in cl.get(nodeid, 'nosy'):
- n[nid] = 1
- for value in props['nosy']:
- if self.db.hasnode('user', value):
- nid = value
- else:
- continue
- if n.has_key(nid): continue
- n[nid] = 1
- props['nosy'] = n.keys()
- # add assignedto to the nosy list
- if props.has_key('assignedto'):
- assignedto = props['assignedto']
- if assignedto not in props['nosy']:
- props['nosy'].append(assignedto)
-
+ if content:
message_id = self.db.msg.create(author=author,
recipients=recipients, date=date.Date('.'), summary=summary,
content=content, files=files, messageid=messageid,
inreplyto=inreplyto)
- try:
+
+ # attach the message to the node
+ if nodeid:
+ # add the message to the node's list
messages = cl.get(nodeid, 'messages')
- except IndexError:
- raise MailUsageError, '''
-The node specified by the designator in the subject of your message ("%s")
-does not exist.
+ messages.append(message_id)
+ props['messages'] = messages
+ else:
+ # pre-load the messages list
+ props['messages'] = [message_id]
-Subject was: "%s"
-'''%(nodeid, subject)
- messages.append(message_id)
- props['messages'] = messages
+ # set the title to the subject
+ if properties.has_key('title') and not props.has_key('title'):
+ props['title'] = title
- # now apply the changes
- try:
+ #
+ # perform the node change / create
+ #
+ try:
+ if nodeid:
cl.set(nodeid, **props)
- except (TypeError, IndexError, ValueError), message:
- raise MailUsageError, '''
-There was a problem with the message you sent:
- %s
-'''%message
- # commit the changes to the DB
- self.db.commit()
- else:
- # If just an item class name is found there, we attempt to create a
- # new item of that class with its "messages" property initialized to
- # contain the new "msg" node and its "files" property initialized to
- # contain any new "file" nodes.
- message_id = self.db.msg.create(author=author,
- recipients=recipients, date=date.Date('.'), summary=summary,
- content=content, files=files, messageid=messageid,
- inreplyto=inreplyto)
-
- # pre-set the issue to unread
- if properties.has_key('status') and not props.has_key('status'):
- try:
- # determine the id of 'unread'
- unread_id = self.db.status.lookup('unread')
- except KeyError:
- pass
- else:
- props['status'] = '1'
-
- # set the title to the subject
- if properties.has_key('title') and not props.has_key('title'):
- props['title'] = title
-
- # pre-load the messages list
- props['messages'] = [message_id]
-
- # set up (clean) the nosy list
- nosy = props.get('nosy', [])
- n = {}
- for value in nosy:
- nid = value
- if n.has_key(nid): continue
- n[nid] = 1
- props['nosy'] = n.keys()
- # add on the recipients of the message
- for recipient in recipients:
- if not n.has_key(recipient):
- props['nosy'].append(recipient)
- n[recipient] = 1
-
- # add the author to the nosy list
- if not n.has_key(author):
- props['nosy'].append(author)
- n[author] = 1
-
- # add assignedto to the nosy list
- if properties.has_key('assignedto') and props.has_key('assignedto'):
- assignedto = props['assignedto']
- if not n.has_key(assignedto):
- props['nosy'].append(assignedto)
- n[assignedto] = 1
-
- # and attempt to create the new node
- try:
+ else:
nodeid = cl.create(**props)
- except (TypeError, IndexError, ValueError), message:
- raise MailUsageError, '''
+ except (TypeError, IndexError, ValueError), message:
+ raise MailUsageError, '''
There was a problem with the message you sent:
%s
'''%message
- # commit the new node(s) to the DB
- self.db.commit()
+ # commit the changes to the DB
+ self.db.commit()
+
+ return nodeid
def parseContent(content, keep_citations, keep_body,
blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'),
eol=re.compile(r'[\r\n]+'),
- signature=re.compile(r'^[>|\s]*[-_]+\s*$')):
+ signature=re.compile(r'^[>|\s]*[-_]+\s*$'),
+ original_message=re.compile(r'^[>|\s]*-----Original Message-----$')):
''' The message body is divided into sections by blank lines.
Sections where the second and all subsequent lines begin with a ">" or "|"
character are considered "quoting sections". The first line of the first
# if we don't have our summary yet use the first line of this
# section
summary = lines[0]
- elif signature.match(lines[0]):
+ elif signature.match(lines[0]) and 2 <= len(lines) <= 10:
+ # lose any signature
+ break
+ elif original_message.match(lines[0]):
+ # ditch the stupid Outlook quoting of the entire original message
break
# and add the section to the output
#
# $Log: not supported by cvs2svn $
+# Revision 1.74 2002/05/29 01:16:17 richard
+# Sorry about this huge checkin! It's fixing a lot of related stuff in one go
+# though.
+#
+# . #541941 ] changing multilink properties by mail
+# . #526730 ] search for messages capability
+# . #505180 ] split MailGW.handle_Message
+# - also changed cgi client since it was duplicating the functionality
+# . build htmlbase if tests are run using CVS checkout (removed note from
+# installation.txt)
+# . don't create an empty message on email issue creation if the email is empty
+#
+# Revision 1.73 2002/05/22 04:12:05 richard
+# . applied patch #558876 ] cgi client customization
+# ... with significant additions and modifications ;)
+# - extended handling of ML assignedto to all places it's handled
+# - added more NotFound info
+#
+# Revision 1.72 2002/05/22 01:24:51 richard
+# Added note to MIGRATION about new config vars. Also made us more resilient
+# for upgraders. Reinstated list header style (oops)
+#
+# Revision 1.71 2002/05/08 02:40:55 richard
+# grr
+#
+# Revision 1.70 2002/05/06 23:40:07 richard
+# hrm
+#
+# Revision 1.69 2002/05/06 23:37:21 richard
+# Tweaking the signature deletion from mail messages.
+# Added nuking of the "-----Original Message-----" crap from Outlook.
+#
+# Revision 1.68 2002/05/02 07:56:34 richard
+# . added option to automatically add the authors and recipients of messages
+# to the nosy lists with the options ADD_AUTHOR_TO_NOSY (default 'new') and
+# ADD_RECIPIENTS_TO_NOSY (default 'new'). These settings emulate the current
+# behaviour. Setting them to 'yes' will add the author/recipients to the nosy
+# on messages that create issues and followup messages.
+# . added missing documentation for a few of the config option values
+#
+# Revision 1.67 2002/04/23 15:46:49 rochecompaan
+# . stripping of the email message body can now be controlled through
+# the config variables EMAIL_KEEP_QUOTED_TEST and
+# EMAIL_LEAVE_BODY_UNCHANGED.
+#
# Revision 1.66 2002/03/14 23:59:24 richard
# . #517734 ] web header customisation is obscure
#