From e2d8ea996bbf6404148e25510de154e178ad7941 Mon Sep 17 00:00:00 2001
From: Emmanuel Blot <eblot@neotion.com>
Date: Fri, 13 May 2011 19:38:32 +0200
Subject: [PATCH] Add attachment notification

---
 trac/ticket/notification.py |  290 ++++++++++++++++++++++++++++---------------
 1 files changed, 187 insertions(+), 103 deletions(-)

diff --git a/trac/ticket/notification.py b/trac/ticket/notification.py
index ed25467..931b234 100644
--- a/trac/ticket/notification.py
+++ b/trac/ticket/notification.py
@@ -18,16 +18,19 @@
 
 from __future__ import with_statement
 
+from datetime import datetime
 from hashlib import md5
 from unicodedata import east_asian_width
 
 from genshi.template.text import NewTextTemplate
 
+from trac.attachment import IAttachmentChangeListener
 from trac.core import *
 from trac.config import *
 from trac.notification import NotifyEmail
 from trac.ticket.api import TicketSystem
-from trac.util.datefmt import to_utimestamp
+from trac.ticket.model import Ticket
+from trac.util.datefmt import to_utimestamp, utc
 from trac.util.text import CRLF, wrap, obfuscate_email_address, to_unicode, \
                            text_width
 from trac.util.translation import deactivate, reactivate
@@ -92,121 +95,165 @@ class TicketNotifyEmail(NotifyEmail):
         translated_fields = ticket.fields
         try:
             ticket.fields = TicketSystem(self.env).get_ticket_fields()
-            self._notify(ticket, newticket, modtime)
+            self.ticket = ticket
+            self.modtime = modtime
+            self.newticket = newticket
+            self.reporter = ''
+            self.owner = ''
+            link = self.env.abs_href.ticket(ticket.id)
+            summary = self.ticket['summary']
+            author = None
+
+            if not newticket and modtime:  # Ticket change
+                (link, summary) = \
+                    self._build_notification_changes(ticket, modtime)
+            else:
+                link = self.env.abs_href.ticket(ticket.id)
+                summary = self.ticket['summary']
+            if newticket:
+                author = ticket['reporter']
+
+            ticket_values = ticket.values.copy()
+            ticket_values['id'] = ticket.id
+            ticket_values['description'] = wrap(
+                ticket_values.get('description', ''), self.COLS,
+                initial_indent=' ', subsequent_indent=' ', linesep=CRLF,
+                ambiwidth=self.ambiwidth)
+            ticket_values['new'] = self.newticket
+            ticket_values['link'] = link
+
+            subject = self.format_subj(summary)
+            if not self.newticket:
+                subject = 'Re: ' + subject
+            self.data.update({
+                'ticket_props': self.format_props(),
+                'ticket_body_hdr': self.format_hdr(),
+                'subject': subject,
+                'ticket': ticket_values,
+                })
+            NotifyEmail.notify(self, ticket.id, subject, author)
         finally:
             ticket.fields = translated_fields
             reactivate(t)
 
-    def _notify(self, ticket, newticket=True, modtime=None):
-        self.ticket = ticket
-        self.modtime = modtime
-        self.newticket = newticket
+    def notify_attachment(self, ticket, author, filename, modtime, add):
+        """Send ticket attachment notification (untranslated)"""
+        t = deactivate()
+        translated_fields = ticket.fields
+        try:
+            ticket.fields = TicketSystem(self.env).get_ticket_fields()
+            self.ticket = ticket
+            self.modtime = modtime
+            self.newticket = False
+            self.reporter = ''
+            self.owner = ''
+            link = self.env.abs_href.ticket(ticket.id)
+            summary = self.ticket['summary']
+            ticket_values = ticket.values.copy()
+            ticket_values['id'] = ticket.id
+            ticket_values['description'] = wrap(
+                ticket_values.get('description', ''), self.COLS,
+                initial_indent=' ', subsequent_indent=' ', linesep=CRLF,
+                ambiwidth=self.ambiwidth)
+            ticket_values['new'] = self.newticket
+            ticket_values['link'] = link
+            subject = 'Re: ' + self.format_subj(summary)
+            body = '%s attachment "%s"' % (add and 'Add' or 'Delete', filename)
+            change = { 'author': author }
+            self.data.update({
+                'ticket_props': self.format_props(),
+                'ticket_body_hdr': self.format_hdr(),
+                'subject': subject,
+                'ticket': ticket_values,
+                'change': change,
+                'changes_body': body,
+                })
+            NotifyEmail.notify(self, ticket.id, subject, author)
+        finally:
+            ticket.fields = translated_fields
+            reactivate(t)
 
-        changes_body = ''
-        self.reporter = ''
-        self.owner = ''
-        changes_descr = ''
+    def _build_notification_changes(self, ticket, modtime):
+        from trac.ticket.web_ui import TicketModule
         change_data = {}
+        changes_descr = ''
+        changes_body = ''
         link = self.env.abs_href.ticket(ticket.id)
         summary = self.ticket['summary']
-        author = None
-        
-        if not self.newticket and modtime:  # Ticket change
-            from trac.ticket.web_ui import TicketModule
-            for change in TicketModule(self.env).grouped_changelog_entries(
-                                                ticket, when=modtime):
-                if not change['permanent']: # attachment with same time...
-                    continue
-                author = change['author']
-                change_data.update({
-                    'author': obfuscate_email_address(author),
-                    'comment': wrap(change['comment'], self.COLS, ' ', ' ',
-                                    CRLF, self.ambiwidth)
-                    })
-                link += '#comment:%s' % str(change.get('cnum', ''))
-                for field, values in change['fields'].iteritems():
-                    old = values['old']
-                    new = values['new']
-                    newv = ''
-                    if field == 'description':
-                        new_descr = wrap(new, self.COLS, ' ', ' ', CRLF,
-                                         self.ambiwidth)
-                        old_descr = wrap(old, self.COLS, '> ', '> ', CRLF,
-                                         self.ambiwidth)
-                        old_descr = old_descr.replace(2 * CRLF, CRLF + '>' + \
-                                                      CRLF)
-                        cdescr = CRLF
-                        cdescr += 'Old description:' + 2 * CRLF + old_descr + \
-                                  2 * CRLF
-                        cdescr += 'New description:' + 2 * CRLF + new_descr + \
-                                  CRLF
-                        changes_descr = cdescr
-                    elif field == 'summary':
-                        summary = "%s (was: %s)" % (new, old)
-                    elif field == 'cc':
-                        (addcc, delcc) = self.diff_cc(old, new)
-                        chgcc = ''
-                        if delcc:
-                            chgcc += wrap(" * cc: %s (removed)" %
-                                          ', '.join(delcc), 
-                                          self.COLS, ' ', ' ', CRLF,
-                                          self.ambiwidth) + CRLF
-                        if addcc:
-                            chgcc += wrap(" * cc: %s (added)" %
-                                          ', '.join(addcc), 
-                                          self.COLS, ' ', ' ', CRLF,
-                                          self.ambiwidth) + CRLF
-                        if chgcc:
-                            changes_body += chgcc
-                        self.prev_cc += self.parse_cc(old) if old else []
-                    else:
-                        if field in ['owner', 'reporter']:
-                            old = obfuscate_email_address(old)
-                            new = obfuscate_email_address(new)
-                        newv = new
-                        length = 7 + len(field)
-                        spacer_old, spacer_new = ' ', ' '
-                        if len(old + new) + length > self.COLS:
-                            length = 5
-                            if len(old) + length > self.COLS:
-                                spacer_old = CRLF
-                            if len(new) + length > self.COLS:
-                                spacer_new = CRLF
-                        chg = '* %s: %s%s%s=>%s%s' % (field, spacer_old, old,
-                                                      spacer_old, spacer_new,
-                                                      new)
-                        chg = chg.replace(CRLF, CRLF + length * ' ')
-                        chg = wrap(chg, self.COLS, '', length * ' ', CRLF,
-                                   self.ambiwidth)
-                        changes_body += ' %s%s' % (chg, CRLF)
-                    if newv:
-                        change_data[field] = {'oldvalue': old, 'newvalue': new}
-        
-        if newticket:
-            author = ticket['reporter']
-
-        ticket_values = ticket.values.copy()
-        ticket_values['id'] = ticket.id
-        ticket_values['description'] = wrap(
-            ticket_values.get('description', ''), self.COLS,
-            initial_indent=' ', subsequent_indent=' ', linesep=CRLF,
-            ambiwidth=self.ambiwidth)
-        ticket_values['new'] = self.newticket
-        ticket_values['link'] = link
-        
-        subject = self.format_subj(summary)
-        if not self.newticket:
-            subject = 'Re: ' + subject
+        for change in TicketModule(self.env).grouped_changelog_entries(
+                                            ticket, when=modtime):
+            if not change['permanent']: # attachment with same time...
+                continue
+            author = change['author']
+            change_data.update({
+                'author': obfuscate_email_address(author),
+                'comment': wrap(change['comment'], self.COLS, ' ', ' ',
+                                CRLF, self.ambiwidth)
+                })
+            link += '#comment:%s' % str(change.get('cnum', ''))
+            for field, values in change['fields'].iteritems():
+                old = values['old']
+                new = values['new']
+                newv = ''
+                if field == 'description':
+                    new_descr = wrap(new, self.COLS, ' ', ' ', CRLF,
+                                     self.ambiwidth)
+                    old_descr = wrap(old, self.COLS, '> ', '> ', CRLF,
+                                     self.ambiwidth)
+                    old_descr = old_descr.replace(2 * CRLF, CRLF + '>' + \
+                                                  CRLF)
+                    cdescr = CRLF
+                    cdescr += 'Old description:' + 2 * CRLF + old_descr + \
+                              2 * CRLF
+                    cdescr += 'New description:' + 2 * CRLF + new_descr + \
+                              CRLF
+                    changes_descr = cdescr
+                elif field == 'summary':
+                    summary = "%s (was: %s)" % (new, old)
+                elif field == 'cc':
+                    (addcc, delcc) = self.diff_cc(old, new)
+                    chgcc = ''
+                    if delcc:
+                        chgcc += wrap(" * cc: %s (removed)" %
+                                      ', '.join(delcc), 
+                                      self.COLS, ' ', ' ', CRLF,
+                                      self.ambiwidth) + CRLF
+                    if addcc:
+                        chgcc += wrap(" * cc: %s (added)" %
+                                      ', '.join(addcc), 
+                                      self.COLS, ' ', ' ', CRLF,
+                                      self.ambiwidth) + CRLF
+                    if chgcc:
+                        changes_body += chgcc
+                    self.prev_cc += self.parse_cc(old) if old else []
+                else:
+                    if field in ['owner', 'reporter']:
+                        old = obfuscate_email_address(old)
+                        new = obfuscate_email_address(new)
+                    newv = new
+                    length = 7 + len(field)
+                    spacer_old, spacer_new = ' ', ' '
+                    if len(old + new) + length > self.COLS:
+                        length = 5
+                        if len(old) + length > self.COLS:
+                            spacer_old = CRLF
+                        if len(new) + length > self.COLS:
+                            spacer_new = CRLF
+                    chg = '* %s: %s%s%s=>%s%s' % (field, spacer_old, old,
+                                                  spacer_old, spacer_new,
+                                                  new)
+                    chg = chg.replace(CRLF, CRLF + length * ' ')
+                    chg = wrap(chg, self.COLS, '', length * ' ', CRLF,
+                               self.ambiwidth)
+                    changes_body += ' %s%s' % (chg, CRLF)
+                if newv:
+                    change_data[field] = {'oldvalue': old, 'newvalue': new}
         self.data.update({
-            'ticket_props': self.format_props(),
-            'ticket_body_hdr': self.format_hdr(),
-            'subject': subject,
-            'ticket': ticket_values,
             'changes_body': changes_body,
             'changes_descr': changes_descr,
             'change': change_data
             })
-        NotifyEmail.notify(self, ticket.id, subject, author)
+        return (link, summary)
 
     def format_props(self):
         tkt = self.ticket
@@ -404,3 +451,40 @@ class TicketNotifyEmail(NotifyEmail):
     def get_text_width(self, text):
         return text_width(text, ambiwidth=self.ambiwidth)
 
+
+class TicketAttachmentNotifier(Component):
+    """Sends notification on attachment change"""
+
+    implements(IAttachmentChangeListener)
+
+    ticket_notify_attachment = BoolOption('notification',
+                                          'ticket_notify_attachment',
+                                          'true',
+        """Always send notifications on ticket attachment events.""")
+
+    # IAttachmentChangeListener
+
+    def attachment_added(self, attachment):
+        if self.ticket_notify_attachment:
+            self._notify_attachment(attachment, True)
+
+    def attachment_deleted(self, attachment):
+        if self.ticket_notify_attachment:
+            self._notify_attachment(attachment, False)
+
+    def attachment_reparented(self, attachment, old_parent_realm,
+                              old_parent_id):
+        pass
+
+    # Implementation
+
+    def _notify_attachment(self, attachment, add):
+        author = attachment.author
+        resource = attachment.resource.parent
+        if resource.realm != 'ticket':
+            return
+        ticket = Ticket(self.env, resource.id)
+        tn = TicketNotifyEmail(self.env)
+        filename = attachment.filename
+        date = attachment.date or datetime.now(utc)
+        tn.notify_attachment(ticket, author, filename, date, add)
-- 
1.7.4.1+GitX

