Index: XMailEMailModule.py
===================================================================
--- XMailEMailModule.py	(revision 14672)
+++ XMailEMailModule.py	(working copy)
@@ -16,14 +16,20 @@
 from pkg_resources import resource_filename  # @UnresolvedImport
 from trac.config import IntOption
 from trac.loader import get_plugin_info
-from trac.notification import NotifyEmail
 from trac.prefs.web_ui import Locale
 from trac.util import translation
-from trac.util.datefmt import format_datetime
-from trac.util.translation import domain_functions, activate
+from trac.util.translation import domain_functions, activate, deactivate,\
+    reactivate
 from trac.web.api import ITemplateStreamFilter
 from xmail.XMailFilterObject import FilterObject
 from trac.core import Component, implements
+from trac.notification.api import NotificationSystem, NotificationEvent,\
+    INotificationFormatter, IEmailDecorator
+from trac.web.chrome import Chrome
+from trac.util.text import CRLF
+from trac.notification.mail import EmailDistributor
+from datetime import datetime
+from trac.util.datefmt import utc, format_datetime
 
 
 _, tag_, N_, add_domain = domain_functions(
@@ -31,21 +37,15 @@
 
 SEC_MULTIPLIER = 1000000
 
-#=========================================================================
-# Module which handels the email notification logic
-#=========================================================================
 
-
 class XMailEventHandler(Component):
     implements(ITemplateStreamFilter)
+    """Required module which handels the email notification logic"""
 
-    """
-    Email Modul, required
-    """
     _locale_string = 'en'
 
     def __init__(self):
-        self.log.debug("+++++++++++++++ init EMailEventHandler")
+        self.log.debug("init XMailEventHandler")
         locale_dir = resource_filename(__name__, 'locale')
         add_domain(self.env.path, locale_dir)
 
@@ -65,17 +65,17 @@
 
         return stream
 
-    #=========================================================================
+    # =========================================================================
     # Default Config
-    #=========================================================================
+    # =========================================================================
     DEFAULT_SLEEP_TIME = IntOption('xmail-plugin', 'sleeptime', 120,
-                                   """Sleep time in seconds for thread. 
-        This is the time which determines how often the filter should be checked.""")
+                                   """Sleep time in seconds for thread.
+ This is the time which determines how often the filter should be checked.""")
     currentTimeInMicroSec = 0
 
-    #=========================================================================
+    # =========================================================================
     # Controll if the timer thread is still running
-    #=========================================================================
+    # =========================================================================
     def _TimerIsStillAlive(self):
         threadList = threading.enumerate()
         for threadOb in threadList:
@@ -82,14 +82,14 @@
             # if threadOb.__class__.__dict__.has_key('name') and threadOb.name
             # == "xmailThread":
             if threadOb.getName() == "xmailThread":
-                if threadOb.isAlive() == False:
+                if threadOb.isAlive() is False:
                     self._createXMailThread()
-                    self.log.warn(
-                        '_TimerIsStillAlive: Thread has not been alive; started new Thread')
+                    self.log.warn("[_TimerIsStillAlive] Thread is not alive; "
+                                  "started new Thread")
                     return False
                 return True  # return since it has already a Thread
-        self.log.info(
-            "[_TimerIsStillAlive] have not found any thread named 'xmailThread'")
+        self.log.info("[_TimerIsStillAlive] have not found any thread "
+                      "named 'xmailThread'")
 #        self.log.debug('[_TimerIsStillAlive] started Thread')
         self._createXMailThread()
         return False
@@ -103,18 +103,16 @@
     def _get_current_time(self):
         return time.time() * SEC_MULTIPLIER
 
-#=========================================================================
-# EMail Send Logic
-#=========================================================================
     def _sendAllMails(self):
         """Central send logic for sending e-mails"""
-        self.log.info("check mails, language is: %s" % self._locale_string)
+        self.log.debug("[_sendAllMails] check mails, language is: %s"
+                       % self._locale_string)
         sys_desc = self.get_system_info()
         filterids = self._get_all_relevant_filters()
 
         for filter_id, username in filterids:
-            self.log.info(
-                "[XMail._sendAllMails] -- filter_id: %s -- username: %s" % (filter_id, username))
+            self.log.debug("[_sendAllMails] filter_id: %s "
+                           "-- username: %s" % (filter_id, username))
             tickets = {}
             subject = "[%s] " % self.env.project_name
             filter = FilterObject(filter_id, self.env)
@@ -131,7 +129,7 @@
                     tickets[t['id']] = 'new'
             if changed_tickets:
                 for t in changed_tickets:
-                    if not tickets.has_key(t['id']):
+                    if 'id' not in tickets:
                         tickets[t['id']] = 'changed'
                     else:
                         changed_tickets.remove(t)
@@ -139,31 +137,33 @@
             if len(tickets) == 0:
                 if filter.values['interval'] > 0:
                     self._set_next_exe(filter, self._get_current_time())
-                    self.log.debug(
-                        "[XMail._sendAllMails] no relevant tickets, since length of tickets is %s" % len(tickets))
-                # nothing to do, so continue with next filter
+                    self.log.debug("[_sendAllMails] no relevant tickets")
+                # nothing to do anymore, so continue with next filter
                 continue
 
             user_data = self._get_user_data(username)
-            self.log.debug("user data: %r -- negotiated: %s" %
+            self.log.debug("[_sendAllMails] user data: %r -- negotiated: %s" %
                            (user_data, self._locale_string))
-            if not user_data.has_key('language'):
-                self.log.debug(
-                    "got no user-specific language, so using locale %s" % self._locale_string)
+            if 'language' not in user_data:
+                self.log.debug("got no user-specific language, "
+                               "so using locale %s" % self._locale_string)
                 activate(self._locale_string, self.env.path)
             else:
                 activate(user_data['language'], self.env.path)
 
             subject += filter.name
-            notifyer = XMailTicketNotify(self.env, {'user_data': user_data,
-                                                    'new_tickets': new_tickets,
-                                                    'changed_tickets': changed_tickets,
-                                                    'filter': filter,
-                                                    'sys_desc': sys_desc})
-            notifyer.notify(resid=None, subject=subject)
+            notify_data = {'user_data': user_data,
+                           'new_tickets': new_tickets,
+                           'changed_tickets': changed_tickets,
+                           'filter': filter,
+                           'sys_desc': sys_desc}
+            notifyer = XMailTicketNotify(self.env)
+            notifyer.send_mails(username, notify_data)
+
             self._set_next_exe(filter, self._get_current_time())
-            self.log.info("[XMail._sendAllMails] XMailTicketNotify: sent email with %r tickets for user %s" % (
-                len(tickets), username))
+            self.log.info("[_sendAllMails] sent mail "
+                          "with %r tickets for user %s" %
+                          (len(tickets), username))
         return
 
     def get_system_info(self):
@@ -187,14 +187,12 @@
         return trac_ver
 
 
-#=========================================================================
+# =========================================================================
 # DB Access logic
-#=========================================================================
-    #=========================================================================
-    # Returns all filterids and filternaems, which have to been processed
-    # in this cycle
-    #=========================================================================
+# =========================================================================
     def _get_all_relevant_filters(self):
+        """Returns all filterids and filternaems, which have to been processed
+        in this cycle"""
         sqlQuery = ("select id, username from xmail"
                     " where nextexe <= %s and active = 1"
                     % self._get_current_time())
@@ -208,7 +206,8 @@
 
     def _get_relevant_tickets(self, timefilter, filter):
         """retrieve relevant tickets for filterId
-        timefilter is usually 'time' (for new tickets) or 'time != changetime and changetime' (for ticket changes) 
+        timefilter is usually 'time' (for new tickets)
+        or 'time != changetime and changetime' (for ticket changes)
         """
         if filter.values['interval'] > 0:
             t = filter.values['nextexe'] - \
@@ -223,7 +222,6 @@
         data = None
         try:
             result = []
-#            data_dict = {}
             try:
                 with self.env.db_query as db:
                     myCursor = db.cursor()
@@ -232,10 +230,8 @@
             except Exception, e:
                 self.log.error("error while fetching data: %s" % e)
 
-#            print "data: %r" % data
             for row in data:
                 data_dict = {}
-#                print "row: %r" % row
                 for i, col in enumerate(row):
                     if i == 0:
                         data_dict['id'] = col
@@ -242,18 +238,18 @@
                     else:
                         data_dict[filter.select_fields[i - 1]] = col
                 result.append(data_dict)
-#            print "result: %r" % result
         except Exception, e:
             self.log.info(
-                "could not get relevant ticket, since error occured: %s\n sql: %s" % (e, sql))
+                "could not get relevant ticket, since error occured: %s\n"
+                " sql: %s" % (e, sql))
 
         return result
 
-    #=========================================================================
-    # Returns the corresponding email-address to the given username
-    #=========================================================================
     def _get_user_data(self, username):
-        sql = ("select name, value from session_attribute where name in ('language', 'email')"
+        """Returns the corresponding email-address to the given username"""
+
+        sql = ("select name, value from session_attribute"
+               " where name in ('language', 'email')"
                " and sid='%s'" % username)
         result = None
         try:
@@ -268,14 +264,11 @@
             self.log.debug("e: %s" % e)  # TODO[fm]: remove in prod-mode
         return result
 
-    #=========================================================================
-    #
-    #=========================================================================
     def _set_next_exe(self, filter, current_time):
         next_exe = current_time
         if filter.values['interval'] > 0:
-            next_exe = (
-                filter.values['interval'] * SEC_MULTIPLIER) + filter.values['nextexe']
+            next_exe = (filter.values['interval'] * SEC_MULTIPLIER) \
+                + filter.values['nextexe']
 
         sql = "update xmail set nextexe=%i, lastsuccessexe=%i where id=%i" % (
             next_exe, current_time, filter.id)
@@ -291,88 +284,153 @@
 
         return False
 
-    #=========================================================================
-    # Executes given sql statement and returns the sql result
-    #=========================================================================
     def _execute_SQL_Query_and_Feedback(self, select, sqlQuery, *params):
+        """Executes given sql statement and returns the sql result.
+        :return when select is True
+                result is an 2-dimensinal array of the select results
+                otherwise
+                Result is the amount of insered rows
+        """
 
-        if sqlQuery != None and params != None:
+        if sqlQuery is not None and params is not None:
             data = {}
             try:
                 with self.env.db_query as db:
                     myCursor = db.cursor()
                     myCursor.execute(sqlQuery, params)
-                    if(select == True):
-                        """ result is an 2-dimensinal array of the select results"""
+                    if (select is True):
                         data = list(myCursor.fetchall())
                     else:
-                        """ Result is the amount of insered rows"""
                         data = myCursor.rowcount
                     # db.commit()
             except Exception, e:
                 self.log.error(
-                    "Error executing SQL Statement \n ( %s ) \n %s" % (sqlQuery, e))
-#            try:
-#                db.close()
-#            except e:
-#                self.log.error("DB close fails. \n %s" % e)
+                    "Error executing SQL Statement \n ( %s ) "
+                    "Exception: %s" % (sqlQuery, e))
             return data
 
 
-class XMailTicketNotify(NotifyEmail):
-    _data = None
-#    _locale_string = 'en'
-    _email_adr = None
-    template_name = "timed_email.txt"
-    COLS = 75
-    COL_DESC = 20
+class XMailNotificationEvent(NotificationEvent):
+    pass
 
-    def __init__(self, env, data, template_name=None):
-        NotifyEmail.__init__(self, env)
-        locale_dir = resource_filename(__name__, 'locale')
-        add_domain(self.env.path, locale_dir)
 
-        if template_name:
-            self.template_name = template_name
-        self._data = data
-        if self._data and self._data['user_data']:
-            # self._locale_string = self._data['user_data']['language'] # not
-            # used at the moment
-            self._email_adr = self._data['user_data']['email']
-# print "[XMailMultiTicketNotify] self._data: %r --- data: %r --- tickets:
-# %r" % (self._data, data, data['new_tickets'])
+class XMailModel(object):
+    realm = 'xmail'
 
-    # override from TicketNotifyEmail
-    def get_recipients(self, tktid):
-        ccrecipients = []
-        torecipients = []
+    def __init__(self, text):
+        self.text = text
 
-        if self._email_adr:
-            torecipients.append(self._email_adr)
+
+class XMailFormatter(Component):
+    implements(INotificationFormatter)
+
+    template_name = 'timed_email.txt'
+
+    def get_supported_styles(self, transport):
+        yield ('text/plain', 'ticket')
+
+    def format(self, transport, style, event):
+        if event.realm != 'ticket':
+            return
+
+#         return "#{0} {1} by {2}" % (event.target,
+#                                         event.category,
+#                                         event.author)
+        return "#{0} {1} by {2}" % ("eins", "zwei", "drei")
+
+
+class XMailDistributor(EmailDistributor):
+    implements(IEmailDecorator)
+
+    def set_data(self, data):
+        self.data = data
+
+    def decorate_message(self, event, message, charset):
+        template = Chrome(self.env).load_template(self.template_name,
+                                                  method='text')
+        tmpl = Chrome(self.env).populate_data(None, {'CRLF': CRLF})
+        tmpl = self._format_data(tmpl, self.data)
+
+        stream = template.generate(**tmpl)
+        t = deactivate()
+        try:
+            return stream.render('text', encoding='utf-8')
+        finally:
+            reactivate(t)
+
+    def _format_data(self, newdata, data):
         filter_sep = ('=' * self.COLS)
-        filter = self._data['filter']
+        filter = data['filter']
 
         # format objects
-        self.data['new_tickets_count'] = len(self._data['new_tickets'])
-        self.data['new_tickets'] = self._format_tickets(
-            self._data['new_tickets'])
-        self.data['changed_tickets_count'] = len(self._data['changed_tickets'])
-        self.data['changed_tickets'] = self._format_tickets(
-            self._data['changed_tickets'])
-        self.data['filter_sep'] = filter_sep
-        self.data['new_ticket_hdr'] = _('New tickets:')
-        self.data['changed_ticket_hdr'] = _('Changed tickets:')
+        newdata['new_tickets_count'] = len(data['new_tickets'])
+        newdata['new_tickets'] = self._format_tickets(
+            data['new_tickets'])
+        newdata['changed_tickets_count'] = len(data['changed_tickets'])
+        newdata['changed_tickets'] = self._format_tickets(
+            data['changed_tickets'])
+        newdata['filter_sep'] = filter_sep
+        newdata['new_ticket_hdr'] = _('New tickets:')
+        newdata['changed_ticket_hdr'] = _('Changed tickets:')
         # simply copy some data objects
-        self.data['filter'] = filter
-        self.data['sys_desc'] = self._data['sys_desc']
+        newdata['filter'] = filter
+        newdata['sys_desc'] = data['sys_desc']
         link = "%s/xmail/xmail-edit.html?id=%i" % (
             self.env.abs_href.base, filter.id)
-        self.data['change_hint'] = tag_(
+        newdata['change_hint'] = tag_(
             'To change the filter go to %(link)s', link=link)
 
-#        return ([], [])
-        return (torecipients, ccrecipients)
+        return newdata
 
+
+class XMailTicketNotify(NotificationSystem):
+    data = None
+    _email_adr = None
+    COLS = 75
+    COL_DESC = 20
+    template_name = 'timed_email.txt'
+
+#     def __init__(self):
+#         distributors = XMailDistributor()
+
+    # override from NotificationSystem
+    def subscriptions(self, event):
+        subscriptions = []
+#         email_adr = data['user_data']['email']
+
+        if self._email_adr:
+            subscriptions.append(self._email_adr)
+
+        return subscriptions
+
+    def send_mails(self, author, data):
+        self.env.log.debug("[send_mails] author: %s" % author)
+        self._email_adr = data['user_data']['email']
+#         xmail_dist = XMailDistributor(self.env)
+#         xmail_dist.set_data(data)
+#         self.distributors = xmail_dist
+        self.data = data
+        self.notify(NotificationEvent('ticket', 'created',
+                                      XMailModel('Lalilu'),
+                                      datetime.now(utc), author))
+        # WORKING, but with no subject
+#         self.send_email(self.smtp_from_name or self.smtp_from,
+#                         self.subscriptions(data),
+#                         self._format_text(data))
+
+    def _format_text(self, data):
+        template = Chrome(self.env).load_template(self.template_name,
+                                                  method='text')
+        tmpl = Chrome(self.env).populate_data(None, {'CRLF': CRLF})
+        tmpl = self._format_data(tmpl, data)
+
+        stream = template.generate(**tmpl)
+        t = deactivate()
+        try:
+            return stream.render('text', encoding='utf-8')
+        finally:
+            reactivate(t)
+
     def _format_tickets(self, tickets):
         if not tickets or not type(tickets) in (list, tuple):
             return None
@@ -381,6 +439,7 @@
         href = self.env.abs_href.base
         format = "%%%is: %%s\n" % self.COL_DESC
         txt = None
+        col_content = self.COLS - self.COL_DESC
 
         for ticket in tickets:
             id = ticket['id']
@@ -392,7 +451,7 @@
             else:
                 txt += "\n\n#%s" % id
 
-            if ticket.has_key('summary'):
+            if 'summary' in ticket:
                 txt += ": %s" % ticket['summary']
             txt += "\n%s/ticket/%s\n" % (href, id)  # add link
             txt += sep
@@ -399,11 +458,11 @@
             for key in ticket.keys():
                 if key in ('time', 'changetime'):
                     time_fld += format % (_(key), format_datetime(ticket[key]))
-                elif not key in ('id', 'summary'):
+                elif key not in ('id', 'summary'):
                     content = ticket[key]
                     if not content:
                         continue  # ignore empty fields
-                    elif type(tickets) == str and len(content) > (self.COLS - self.COL_DESC):
+                    elif type(tickets) == str and len(content) > col_content:
                         big_cols.append({key: ticket[key]})
                     else:
                         txt += format % (_(key), content)
@@ -415,16 +474,36 @@
 
         return txt
 
+    def _format_data(self, newdata, data):
+        filter_sep = ('=' * self.COLS)
+        filter = data['filter']
 
-#=========================================================================
-# Timer Implementation
-#=========================================================================
-#===========================================================================
-# Timer Implementation with execute a given function in an given intervall
-# @param func: Function to call
-# @param sec: Sleep intervall in seconds
-#===========================================================================
+        # format objects
+        newdata['new_tickets_count'] = len(data['new_tickets'])
+        newdata['new_tickets'] = self._format_tickets(
+            data['new_tickets'])
+        newdata['changed_tickets_count'] = len(data['changed_tickets'])
+        newdata['changed_tickets'] = self._format_tickets(
+            data['changed_tickets'])
+        newdata['filter_sep'] = filter_sep
+        newdata['new_ticket_hdr'] = _('New tickets:')
+        newdata['changed_ticket_hdr'] = _('Changed tickets:')
+        # simply copy some data objects
+        newdata['filter'] = filter
+        newdata['sys_desc'] = data['sys_desc']
+        link = "%s/xmail/xmail-edit.html?id=%i" % (
+            self.env.abs_href.base, filter.id)
+        newdata['change_hint'] = tag_(
+            'To change the filter go to %(link)s', link=link)
+ 
+        return newdata
+
+
 class XMailTimerThread(Thread):
+    """Timer Implementation with execute a given function in an given intervall
+    :param func: Function to call
+    :param sec: Sleep intervall in seconds
+    """
 
     def __init__(self, func, sec=30, log_func=None):
         Thread.__init__(self)
@@ -431,6 +510,7 @@
         self.func = func
         self.sec = sec
         self.log = log_func
+        self.counter = 1
         if self.log:
             self.log.info('XMailTimerThread: init done with sec: %s' % sec)
 
@@ -440,13 +520,18 @@
                 # error occured: 'ascii' codec can't encode character u'\xfc'
                 # in position 19: ordinal not in range(128)
                 self.func()
+                self.counter = self.counter + 1
             except Exception, e:
                 if self.log:
-                    self.log.error('==============================\n'
-                                   '[XMailTimerThread.run] -- Exception occured: %r' % e)
+                    self.log.error('[XMailTimerThread.run] ERROR: %s' % e)
                     exc_traceback = sys.exc_info()[2]
                     self.log.error('TraceBack: %s' % format_tb(exc_traceback))
 
             if self.log:
                 self.log.debug('sleeping %s secs' % self.sec)
+                if ((self.counter % 30) == 0):
+                    self.log.info("checked %s times for mails "
+                                  "(interval is %s seconds)"
+                                  % (self.counter, self.sec))
+                    self.counter = 1
             time.sleep(self.sec)
