From 988b86a70cfc493f51b71e3e0b7effa439719a13 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 23 Jul 2009 11:37:45 -0400 Subject: Broke encodedMIMEText out of send-pgp-mime.PGPMimeMessageFactory. It's useful enough even when you're not intending to encrypt something. --- interfaces/email/interactive/send_pgp_mime.py | 41 ++++++++++++++------------- 1 file changed, 21 insertions(+), 20 deletions(-) (limited to 'interfaces') diff --git a/interfaces/email/interactive/send_pgp_mime.py b/interfaces/email/interactive/send_pgp_mime.py index babd720..09ac0ed 100644 --- a/interfaces/email/interactive/send_pgp_mime.py +++ b/interfaces/email/interactive/send_pgp_mime.py @@ -153,6 +153,25 @@ def header_from_text(text, encoding="us-ascii"): p = Parser() return p.parsestr(text, headersonly=True) +def encodedMIMEText(body, encoding=None): + if encoding == None: + if type(body) == types.StringType: + encoding = "us-ascii" + elif type(body) == types.UnicodeType: + for encoding in ["us-ascii", "iso-8859-1", "utf-8"]: + try: + body.encode(encoding) + except UnicodeError: + pass + else: + break + assert encoding != None + # Create the message ('plain' stands for Content-Type: text/plain) + if encoding == "us-ascii": + return MIMEText(body) + else: + return MIMEText(body.encode(encoding), 'plain', encoding) + def attach_root(header, root_part): """ Attach the email.Message root_part to the email.Message header @@ -355,26 +374,8 @@ class PGPMimeMessageFactory (object): """ def __init__(self, body): self.body = body - def encodedMIMEText(self, body, encoding=None): - if encoding == None: - if type(body) == types.StringType: - encoding = "us-ascii" - elif type(body) == types.UnicodeType: - for encoding in ["us-ascii", "iso-8859-1", "utf-8"]: - try: - body.encode(encoding) - except UnicodeError: - pass - else: - break - assert encoding != None - # Create the message ('plain' stands for Content-Type: text/plain) - if encoding == "us-ascii": - return MIMEText(body) - else: - return MIMEText(body.encode(encoding), 'plain', encoding) def clearBodyPart(self): - body = self.encodedMIMEText(self.body) + body = encodedMIMEText(self.body) body.add_header('Content-Disposition', 'inline') return body def passphrase_arg(self, passphrase=None): @@ -387,7 +388,7 @@ class PGPMimeMessageFactory (object): """ text/plain """ - return self.encodedMIMEText(self.body) + return encodedMIMEText(self.body) def sign(self, header, passphrase=None): """ multipart/signed -- cgit From 2a1a71dac497c38dd4b300a27eff2d801a51aecf Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 23 Jul 2009 16:18:34 -0400 Subject: be-handle-mail successfully generates bugdir-changed notification emails. This still needs a lot of cleaning up, but it worked for an "all" subscription to "DIR", so I thought I'd lock in the current status ;). --- interfaces/email/interactive/be-handle-mail | 187 ++++++++++++++++++++++++++-- 1 file changed, 180 insertions(+), 7 deletions(-) (limited to 'interfaces') diff --git a/interfaces/email/interactive/be-handle-mail b/interfaces/email/interactive/be-handle-mail index f457b6a..4e861ba 100755 --- a/interfaces/email/interactive/be-handle-mail +++ b/interfaces/email/interactive/be-handle-mail @@ -52,10 +52,14 @@ import traceback import doctest import unittest -import libbe.cmdutil, libbe.encoding, libbe.utility +from becommands import subscribe +import libbe.cmdutil, libbe.encoding, libbe.utility, libbe.bugdir +import libbe.diff import send_pgp_mime -HANDLER_ADDRESS = u"BE Bugs " +THIS_SERVER = u"thor.physics.drexel.edu" +THIS_ADDRESS = u"BE Bugs " + _THIS_DIR = os.path.abspath(os.path.dirname(__file__)) BE_DIR = _THIS_DIR LOGPATH = os.path.join(_THIS_DIR, u"be-handle-mail.log") @@ -135,6 +139,12 @@ class InvalidOption (InvalidCommand): InvalidCommand.__init__(self, msg, info, command, bigmessage) self.option = option +class NotificationFailed (Exception): + def __init__(self, msg): + bigmessage = "Notification failed: %s" % msg + Exception.__init__(self, bigmessage) + self.short_msg = msg + class ID (object): """ Sometimes you want to reference the output of a command that @@ -472,15 +482,15 @@ class Message (object): finally: if AUTOCOMMIT == True: tag,subject = self._split_subject() - command = Command(self, "commit", [subject]) - command.run() + self.commit_command = Command(self, "commit", [subject]) + self.commit_command.run() if LOGFILE != None: LOGFILE.write("Autocommit:\n%s\n\n" % - send_pgp_mime.flatten(command.response_msg(), - to_unicode=True)) + send_pgp_mime.flatten(self.commit_command.response_msg(), + to_unicode=True)) def _begin_response(self): tag,subject = self._split_subject() - response_header = [u"From: %s" % HANDLER_ADDRESS, + response_header = [u"From: %s" % THIS_ADDRESS, u"To: %s" % self.author_addr(), u"Date: %s" % libbe.utility.time_to_str(time.time()), u"Subject: %s Re: %s"%(SUBJECT_TAG_RESPONSE,subject) @@ -501,6 +511,152 @@ class Message (object): for message in self._response_messages: response_body.attach(message) return send_pgp_mime.attach_root(self.response_header, response_body) + def subscriber_emails(self): + if AUTOCOMMIT != True: # no way to tell what's changed + raise NotificationFailed("Autocommit dissabled") + assert len(self._response_messages) > 0 + if self.commit_command.ret != 0: + # commit failed. Error already logged. + raise NotificationFailed("Commit failed") + + # read only bugdir. + bd = libbe.bugdir.BugDir(from_disk=True, + manipulate_encodings=False) + if bd.rcs.versioned == False: # no way to tell what's changed + raise NotificationFailed("Not versioned") + + subscribers = subscribe.get_bugdir_subscribers(bd, THIS_SERVER) + if len(subscribers) == 0: + return [] + + before_bd, after_bd = self._get_before_and_after_bugdirs(bd) + rem,mod,add = libbe.diff.bug_diffs(before_bd, after_bd) + bug_index = self._subscriber_bug_change_index(rem,mod,add) + header = self._subscriber_header(bd) + + parts = {} + emails = [] + for subscriber,subscriptions in subscribers.items(): + header["to"] = subscriber + root = MIMEMultipart() + for id,types in subscriptions.items(): + if id == "DIR": + if subscribe.BUGDIR_TYPE_ALL in subscriptions["DIR"]: + if ("DIR", "all") not in parts: + parts[("DIR", "all")] = \ + self._subscriber_bugdir_all_part( \ + rem,mod,add,before_bd,after_bd) + root.attach(parts[("DIR", "all")]) + if subscribe.BUGDIR_TYPE_NEW in subscriptions["DIR"]: + if ("DIR", "new") not in parts: + parts[("DIR", "new")] = \ + self._subscriber_bugdir_new_part(add) + root.attach(parts[("DIR", "new")]) + continue + assert subscriptions[id] == [subscribe.BUG_TYPE_ALL], \ + subscriptions[id] + type,bug = bug_index[id] + if type == "added": + pass # no-one other than self.author should be subscribed. + elif type == "modified": + old,new = bug + if (new.uuid, "mod") not in parts: + parts[(new.uuid, "mod")] = \ + self._subscriber_bug_mod_part(old, new) + root.attach(parts[(new.uuid, "mod")]) + elif type == "removed": + if (bug.uuid, "rem") not in parts: + parts[(bug.uuid, "rem")] = \ + self._subscriber_bug_rem_part(old, bug) + root.attach(parts[(bug.uuid, "rem")]) + if len(root.get_payload()) > 0: + emails.append(send_pgp_mime.attach_root(header, root)) + if LOGFILE != None: + LOGFILE.write("Notfying %s of changes\n" % subscriber) + return emails + def _get_before_and_after_bugdirs(self, bd): + commit_msg = self.commit_command.stdout + assert commit_msg.startswith("Committed "), commit_msg + after_revision = commit_msg[len("Committed "):] + before_revision = bd.rcs.revision_id(-2) + if before_revision == None: + # this commit was the initial commit + before_bd = libbe.bugdir.BugDir(from_disk=False, + manipulate_encodings=False) + else: + before_bd = bd.duplicate_bugdir(before_revision) + #after_bd = bd.duplicate_bugdir(after_revision) + after_bd = bd # assume no changes since commit a few cycles ago + return (before_bd, after_bd) + def _subscriber_header(self, bd): + root_dir = os.path.basename(bd.root) + subject = "Changes to %s on %s by %s" \ + % (root_dir, THIS_SERVER, self.author_addr()) + header = [u"From: %s" % THIS_ADDRESS, + u"To: %s" % u"DUMMY-AUTHOR", + u"Date: %s" % libbe.utility.time_to_str(time.time()), + u"Subject: %s Re: %s" % (SUBJECT_TAG_RESPONSE, subject) + ] + return send_pgp_mime.header_from_text(text=u"\n".join(header)) + def _subscriber_bug_change_index(self, removed, modified, added): + bug_index = {} + for bug in removed: + bug_index[bug.uuid] = ("removed", bug) + for bug in modified: + old,new = bug + bug_index[new.uuid] = ("modified", bug) + for bug in added: + bug_index[bug.uuid] = ("added", bug) + return bug_index + def _subscriber_bugdir_all_part(self, rem,mod,add,before_bd,after_bd): + root = MIMEMultipart() + self._att(root,self._subscriber_bugdir_bugdir_part(before_bd,after_bd)) + self._att(root, self._subscriber_bugdir_mod_part(mod)) + self._att(root, self._subscriber_bugdir_rem_part(rem)) + self._att(root, self._subscriber_bugdir_new_part(add)) + if len(root.get_payload()) == 0: + return None + return root + def _subscriber_bugdir_bugdir_part(self, before_bd, after_bd): + if before_bd.settings != after_bd.settings: + return send_pgp_mime.encodedMIMEText(u"BD changed!") + return None + def _subscriber_bugdir_rem_part(self, rem): + lines = [u"The following bugs were removed by %s." % self.author_addr(), u""] + for bug in rem: + bug_text = bug.string(show_comments=False) + lines.extend(bug_text.splitlines()) + return send_pgp_mime.encodedMIMEText(u"\n".join(lines)+u"\n") + def _subscriber_bugdir_new_part(self, add): + lines = [u"The following bugs were added by %s." % self.author_addr(), u""] + for bug in add: + bug_text = bug.string(show_comments=True) + lines.extend(bug_text.splitlines()) + return send_pgp_mime.encodedMIMEText(u"\n".join(lines)+u"\n") + def _subscriber_bugdir_mod_part(self, mod): + lines = [u"The following bugs were modified by %s." \ + % self.author_addr(), u""] + for old,new in mod: + change_text = libbe.diff.bug_changes(old, new) + if change_text == None: + return None + lines.extend(change_text.splitlines()) + return send_pgp_mime.encodedMIMEText(u"\n".join(lines)+u"\n") + def _subscriber_bug_rem_part(self, bug): + lines = [u"The following bug was removed by %s"%self.author_addr(),u""] + bug_text = bug.string(show_comments=False) + lines.extend(bug_text.splitlines()) + return send_pgp_mime.encodedMIMEText(u"\n".join(lines)+u"\n") + def _subscriber_bug_mod_part(self, old, new): + lines = [u"Bug %s was modified by %s" % \ + (new.uuid, self.author_addr()), u""] + change_text = libbe.diff.bug_changes(old, new) + lines.extend(change_text.splitlines()) + return send_pgp_mime.encodedMIMEText(u"\n".join(lines)+u"\n") + def _att(self, root, attachment): + if attachment != None: + root.attach(attachment) + return root def generate_global_tags(tag_base=u"be-bug"): """ @@ -571,6 +727,9 @@ def main(): parser.add_option('-a', '--disable-autocommit', dest='autocommit', default=True, action='store_false', help='Disable the autocommit after parsing the email.') + parser.add_option('-s', '--disable-subscribers', dest='subscribers', + default=True, action='store_false', + help='Disable subscriber notification emails.') parser.add_option('--test', dest='test', action='store_true', help='Run internal unit-tests and exit.') @@ -615,8 +774,22 @@ def main(): LOGFILE.write(u"\n%s\n\n" % send_pgp_mime.flatten(response, to_unicode=True)) send_pgp_mime.mail(response, send_pgp_mime.sendmail) + if options.subscribers == True: + LOGFILE.write(u"Checking for subscribers\n") + try: + emails = m.subscriber_emails() + except NotificationFailed, e: + LOGFILE.write(unicode(e) + u"\n") + else: + for msg in emails: + if options.output == True: + print send_pgp_mime.flatten(msg, to_unicode=True) + else: + send_pgp_mime.mail(msg, send_pgp_mime.sendmail) + close_logfile() + class GenerateGlobalTagsTestCase (unittest.TestCase): def setUp(self): super(GenerateGlobalTagsTestCase, self).setUp() -- cgit From 9afbe4e78f7a332401ec03008ff66faa5c11e297 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 25 Jul 2009 07:26:19 -0400 Subject: Renamed Comment.From and .time_string to .author and .date respectively. Now they conform to the libbe.settings_object.setting_name_to_attr_name() standard. I fixed the references I found in becommands/comment.py interfaces/xml/be-mbox-to-xml interfaces/xml/be-xml-to-mbox but there may have been some references or files that slipped through. --- interfaces/xml/be-mbox-to-xml | 7 ++++--- interfaces/xml/be-xml-to-mbox | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'interfaces') diff --git a/interfaces/xml/be-mbox-to-xml b/interfaces/xml/be-mbox-to-xml index 57de719..dc6a1c5 100755 --- a/interfaces/xml/be-mbox-to-xml +++ b/interfaces/xml/be-mbox-to-xml @@ -28,6 +28,7 @@ from libbe.encoding import get_encoding, set_IO_stream_encodings from mailbox import mbox, Message # the mailbox people really want an on-disk copy from time import asctime, gmtime import types +from xml.sax.saxutils import escape DEFAULT_ENCODING = get_encoding() set_IO_stream_encodings(DEFAULT_ENCODING) @@ -40,7 +41,7 @@ def comment_message_to_xml(message, fields=None): new_fields = {} new_fields[u'alt-id'] = message[u'message-id'] new_fields[u'in-reply-to'] = message[u'in-reply-to'] - new_fields[u'from'] = message[u'from'] + new_fields[u'author'] = message[u'from'] new_fields[u'date'] = message[u'date'] new_fields[u'content-type'] = message.get_content_type() for k,v in new_fields.items(): @@ -77,12 +78,12 @@ def comment_message_to_xml(message, fields=None): if message.is_multipart(): ret = [] alt_id = fields[u'alt-id'] - from_str = fields[u'from'] + from_str = fields[u'author'] date = fields[u'date'] for m in message.walk(): if m == message: continue - fields[u'from'] = from_str + fields[u'author'] = from_str fields[u'date'] = date if len(ret) > 0: # we've added one part already fields.pop(u'alt-id') # don't pass alt-id to other parts diff --git a/interfaces/xml/be-xml-to-mbox b/interfaces/xml/be-xml-to-mbox index ea77c34..c630447 100755 --- a/interfaces/xml/be-xml-to-mbox +++ b/interfaces/xml/be-xml-to-mbox @@ -129,7 +129,7 @@ class Comment (LimitedAttrDict): u"alt-id", u"short-name", u"in-reply-to", - u"from", + u"author", u"date", u"content-type", u"body"] @@ -137,7 +137,7 @@ class Comment (LimitedAttrDict): if bug == None: bug = Bug() bug[u"uuid"] = u"no-uuid" - name,addr = email.utils.parseaddr(self["from"]) + name,addr = email.utils.parseaddr(self["author"]) print "From %s %s" % (addr, rfc2822_to_asctime(self["date"])) if "uuid" in self: id = self["uuid"] elif "alt-id" in self: id = self["alt-id"] @@ -145,7 +145,7 @@ class Comment (LimitedAttrDict): if id != None: print "Message-ID: <%s@%s>" % (id, DEFAULT_DOMAIN) print "Date: %s" % self["date"] - print "From: %s" % self["from"] + print "From: %s" % self["author"] subject = "" if "short-name" in self: subject += self["short-name"]+u": " -- cgit From 08b226ce40f951818fab6bdbfc6eae6bfb0e7deb Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 27 Jul 2009 08:09:20 -0400 Subject: Moved be-handle-mail over to new libbe.diff classes. --- interfaces/email/interactive/be-handle-mail | 145 ++++++++++------------------ 1 file changed, 49 insertions(+), 96 deletions(-) (limited to 'interfaces') diff --git a/interfaces/email/interactive/be-handle-mail b/interfaces/email/interactive/be-handle-mail index 4e861ba..ed45bdd 100755 --- a/interfaces/email/interactive/be-handle-mail +++ b/interfaces/email/interactive/be-handle-mail @@ -271,6 +271,23 @@ class Command (object): send_pgp_mime.PGPMimeMessageFactory(u"\n".join(response_body)) return response_generator.plain() +class DiffTree (libbe.diff.DiffTree): + def report_string(self): + return send_pgp_mime.flatten(self.report(), to_unicode=True) + def make_root(self): + return MIMEMultipart() + def join(self, root, part): + if part != None: + root.attach(send_pgp_mime.encodedMIMEText(part)) + def data_string(self, depth, indent=False): + return libbe.diff.DiffTree.data_string(self, depth, indent=indent) + +class Diff (libbe.diff.Diff): + def bug_add_string(self, bug): + return bug.string(show_comments=True) + def comment_summary_string(self, comment): + return comment.string() + class Message (object): def __init__(self, email_text): self.text = email_text @@ -530,49 +547,44 @@ class Message (object): return [] before_bd, after_bd = self._get_before_and_after_bugdirs(bd) - rem,mod,add = libbe.diff.bug_diffs(before_bd, after_bd) - bug_index = self._subscriber_bug_change_index(rem,mod,add) + diff = Diff(before_bd, after_bd) + diff_tree = diff.report_tree(diff_tree=DiffTree) + bug_index = {} + for child in diff_tree.child_by_path("/bugs/new"): + bug_index[child.name] = ("added", child) + for child in diff_tree.child_by_path("/bugs/mod"): + bug_index[child.name] = ("modified", child) + for child in diff_tree.child_by_path("/bugs/rem"): + bug_index[child.name] = ("removed", child) header = self._subscriber_header(bd) - parts = {} emails = [] for subscriber,subscriptions in subscribers.items(): - header["to"] = subscriber - root = MIMEMultipart() + header.replace_header("to", subscriber) + parts = [] for id,types in subscriptions.items(): if id == "DIR": - if subscribe.BUGDIR_TYPE_ALL in subscriptions["DIR"]: - if ("DIR", "all") not in parts: - parts[("DIR", "all")] = \ - self._subscriber_bugdir_all_part( \ - rem,mod,add,before_bd,after_bd) - root.attach(parts[("DIR", "all")]) - if subscribe.BUGDIR_TYPE_NEW in subscriptions["DIR"]: - if ("DIR", "new") not in parts: - parts[("DIR", "new")] = \ - self._subscriber_bugdir_new_part(add) - root.attach(parts[("DIR", "new")]) - continue - assert subscriptions[id] == [subscribe.BUG_TYPE_ALL], \ - subscriptions[id] - type,bug = bug_index[id] - if type == "added": - pass # no-one other than self.author should be subscribed. - elif type == "modified": - old,new = bug - if (new.uuid, "mod") not in parts: - parts[(new.uuid, "mod")] = \ - self._subscriber_bug_mod_part(old, new) - root.attach(parts[(new.uuid, "mod")]) - elif type == "removed": - if (bug.uuid, "rem") not in parts: - parts[(bug.uuid, "rem")] = \ - self._subscriber_bug_rem_part(old, bug) - root.attach(parts[(bug.uuid, "rem")]) - if len(root.get_payload()) > 0: - emails.append(send_pgp_mime.attach_root(header, root)) - if LOGFILE != None: - LOGFILE.write("Notfying %s of changes\n" % subscriber) + if subscribe.BUGDIR_TYPE_ALL in types: + parts.append(diff_tree.report()) + break + if subscribe.BUGDIR_TYPE_NEW in types: + new = diff_tree.child_by_path("/bugs/new") + parts.append(new.report()) + continue # move on to next id + assert types == [subscribe.BUG_TYPE_ALL], types + type,bug_root = bug_index[id] + parts.append(bug_root.report()) + if len(parts) == 0: + continue # no email to this subscriber + elif len(parts) == 1: + root = parts[0] + else: # join subscription parts into a single body + root = MIMEMultipart() + for part in parts: + root.attach(part) + emails.append(send_pgp_mime.attach_root(header, root)) + if LOGFILE != None: + LOGFILE.write("Notfying %s of changes\n" % subscriber) return emails def _get_before_and_after_bugdirs(self, bd): commit_msg = self.commit_command.stdout @@ -598,65 +610,6 @@ class Message (object): u"Subject: %s Re: %s" % (SUBJECT_TAG_RESPONSE, subject) ] return send_pgp_mime.header_from_text(text=u"\n".join(header)) - def _subscriber_bug_change_index(self, removed, modified, added): - bug_index = {} - for bug in removed: - bug_index[bug.uuid] = ("removed", bug) - for bug in modified: - old,new = bug - bug_index[new.uuid] = ("modified", bug) - for bug in added: - bug_index[bug.uuid] = ("added", bug) - return bug_index - def _subscriber_bugdir_all_part(self, rem,mod,add,before_bd,after_bd): - root = MIMEMultipart() - self._att(root,self._subscriber_bugdir_bugdir_part(before_bd,after_bd)) - self._att(root, self._subscriber_bugdir_mod_part(mod)) - self._att(root, self._subscriber_bugdir_rem_part(rem)) - self._att(root, self._subscriber_bugdir_new_part(add)) - if len(root.get_payload()) == 0: - return None - return root - def _subscriber_bugdir_bugdir_part(self, before_bd, after_bd): - if before_bd.settings != after_bd.settings: - return send_pgp_mime.encodedMIMEText(u"BD changed!") - return None - def _subscriber_bugdir_rem_part(self, rem): - lines = [u"The following bugs were removed by %s." % self.author_addr(), u""] - for bug in rem: - bug_text = bug.string(show_comments=False) - lines.extend(bug_text.splitlines()) - return send_pgp_mime.encodedMIMEText(u"\n".join(lines)+u"\n") - def _subscriber_bugdir_new_part(self, add): - lines = [u"The following bugs were added by %s." % self.author_addr(), u""] - for bug in add: - bug_text = bug.string(show_comments=True) - lines.extend(bug_text.splitlines()) - return send_pgp_mime.encodedMIMEText(u"\n".join(lines)+u"\n") - def _subscriber_bugdir_mod_part(self, mod): - lines = [u"The following bugs were modified by %s." \ - % self.author_addr(), u""] - for old,new in mod: - change_text = libbe.diff.bug_changes(old, new) - if change_text == None: - return None - lines.extend(change_text.splitlines()) - return send_pgp_mime.encodedMIMEText(u"\n".join(lines)+u"\n") - def _subscriber_bug_rem_part(self, bug): - lines = [u"The following bug was removed by %s"%self.author_addr(),u""] - bug_text = bug.string(show_comments=False) - lines.extend(bug_text.splitlines()) - return send_pgp_mime.encodedMIMEText(u"\n".join(lines)+u"\n") - def _subscriber_bug_mod_part(self, old, new): - lines = [u"Bug %s was modified by %s" % \ - (new.uuid, self.author_addr()), u""] - change_text = libbe.diff.bug_changes(old, new) - lines.extend(change_text.splitlines()) - return send_pgp_mime.encodedMIMEText(u"\n".join(lines)+u"\n") - def _att(self, root, attachment): - if attachment != None: - root.attach(attachment) - return root def generate_global_tags(tag_base=u"be-bug"): """ -- cgit