source: trunk/email2trac.py.in @ 163

Last change on this file since 163 was 163, checked in by bas, 17 years ago

email2trac.py.in, ChangeLog?:

  • Added keyword python_egg_cache for environment variable closes tocjet #10
  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 24.4 KB
Line 
1#!@PYTHON@
2# Copyright (C) 2002
3#
4# This file is part of the email2trac utils
5#
6# This program is free software; you can redistribute it and/or modify it
7# under the terms of the GNU General Public License as published by the
8# Free Software Foundation; either version 2, or (at your option) any
9# later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
19#
20# For vi/emacs or other use tabstop=4 (vi: set ts=4)
21#
22"""
23email2trac.py -- Email tickets to Trac.
24
25A simple MTA filter to create Trac tickets from inbound emails.
26
27Copyright 2005, Daniel Lundin <daniel@edgewall.com>
28Copyright 2005, Edgewall Software
29
30Changed By: Bas van der Vlies <basv@sara.nl>
31Date      : 13 September 2005
32Descr.    : Added config file and command line options, spam level
33            detection, reply address and mailto option. Unicode support
34
35Changed By: Walter de Jong <walter@sara.nl>
36Descr.    : multipart-message code and trac attachments
37
38
39The scripts reads emails from stdin and inserts directly into a Trac database.
40MIME headers are mapped as follows:
41
42        * From:      => Reporter
43                     => CC (Optional via reply_all option)
44        * Subject:   => Summary
45        * Body       => Description
46        * Component  => Can be set to SPAM via spam_level option
47
48How to use
49----------
50 * Create an config file:
51        [DEFAULT]                      # REQUIRED
52        project      : /data/trac/test # REQUIRED
53        debug        : 1               # OPTIONAL, if set print some DEBUG info
54        spam_level   : 4               # OPTIONAL, if set check for SPAM mail
55        reply_all    : 1               # OPTIONAL, if set then fill in ticket CC field
56        umask        : 022             # OPTIONAL, if set then use this umask for creation of the attachments
57        mailto_link  : 1               # OPTIONAL, if set then [mailto:<>] in description
58        mailto_cc    : basv@sara.nl    # OPTIONAL, use this address as CC in mailto line
59        ticket_update: 1               # OPTIONAL, if set then check if this is an update for a ticket
60        trac_version : 0.8             # OPTIONAL, default is 0.10
61
62        [jouvin]                       # OPTIONAL project declaration, if set both fields necessary
63        project      : /data/trac/jouvin # use -p|--project jouvin. 
64       
65 * default config file is : /etc/email2trac.conf
66
67 * Commandline opions:
68                -h | --help
69                -c <value> | --component=<value>
70                -f <config file> | --file=<config file>
71                -p <project name> | --project=<project name>
72
73SVN Info:
74        $Id: email2trac.py.in 163 2007-07-05 09:17:13Z bas $
75"""
76import os
77import sys
78import string
79import getopt
80import stat
81import time
82import email
83import email.Iterators
84import email.Header
85import re
86import urllib
87import unicodedata
88import ConfigParser
89from stat import *
90import mimetypes
91import syslog
92import traceback
93
94
95# Some global variables
96#
97trac_default_version = 0.10
98m = None
99
100
101class TicketEmailParser(object):
102        env = None
103        comment = '> '
104   
105        def __init__(self, env, parameters, version):
106                self.env = env
107
108                # Database connection
109                #
110                self.db = None
111
112                # Some useful mail constants
113                #
114                self.author = None
115                self.email_addr = None
116                self.email_field = None
117
118                self.VERSION = version
119                if self.VERSION == 0.8:
120                        self.get_config = self.env.get_config
121                else:
122                        self.get_config = self.env.config.get
123
124                if parameters.has_key('umask'):
125                        os.umask(int(parameters['umask'], 8))
126
127                if parameters.has_key('debug'):
128                        self.DEBUG = int(parameters['debug'])
129                else:
130                        self.DEBUG = 0
131
132                if parameters.has_key('mailto_link'):
133                        self.MAILTO = int(parameters['mailto_link'])
134                        if parameters.has_key('mailto_cc'):
135                                self.MAILTO_CC = parameters['mailto_cc']
136                        else:
137                                self.MAILTO_CC = ''
138                else:
139                        self.MAILTO = 0
140
141                if parameters.has_key('spam_level'):
142                        self.SPAM_LEVEL = int(parameters['spam_level'])
143                else:
144                        self.SPAM_LEVEL = 0
145
146                if parameters.has_key('email_comment'):
147                        self.comment = str(parameters['email_comment'])
148
149                if parameters.has_key('email_header'):
150                        self.EMAIL_HEADER = int(parameters['email_header'])
151                else:
152                        self.EMAIL_HEADER = 0
153
154                if parameters.has_key('alternate_notify_template'):
155                        self.notify_template = str(parameters['alternate_notify_template'])
156                else:
157                        self.notify_template = None
158
159                if parameters.has_key('reply_all'):
160                        self.REPLY_ALL = int(parameters['reply_all'])
161                else:
162                        self.REPLY_ALL = 0
163
164                if parameters.has_key('ticket_update'):
165                        self.TICKET_UPDATE = int(parameters['ticket_update'])
166                else:
167                        self.TICKET_UPDATE = 0
168
169                if parameters.has_key('drop_spam'):
170                        self.DROP_SPAM = int(parameters['drop_spam'])
171                else:
172                        self.DROP_SPAM = 0
173
174                if parameters.has_key('verbatim_format'):
175                        self.VERBATIM_FORMAT = int(parameters['verbatim_format'])
176                else:
177                        self.VERBATIM_FORMAT = 1
178
179                if parameters.has_key('strip_signature'):
180                        self.STRIP_SIGNATURE = int(parameters['strip_signature'])
181                else:
182                        self.STRIP_SIGNATURE = 0
183
184                if parameters.has_key('use_textwrap'):
185                        self.USE_TEXTWRAP = int(parameters['use_textwrap'])
186                else:
187                        self.USE_TEXTWRAP = 0
188
189                if parameters.has_key('python_egg_cache'):
190                        self.python_egg_cache = str(parameters['python_egg_cache'])
191                        os.environ['PYTHON_EGG_CACHE'] = self.python_egg_cache
192
193        # X-Spam-Score: *** (3.255) BAYES_50,DNS_FROM_AHBL_RHSBL,HTML_
194        # Note if Spam_level then '*' are included
195        def spam(self, message):
196                if message.has_key('X-Spam-Score'):
197                        spam_l = string.split(message['X-Spam-Score'])
198                        number = spam_l[0].count('*')
199
200                        if number >= self.SPAM_LEVEL:
201                                return 'Spam'
202
203                elif message.has_key('X-Virus-found'):                  # treat virus mails as spam
204                        return 'Spam'
205
206                return self.get_config('ticket', 'default_component')
207
208        def blacklisted_from(self):
209                FROM_RE = re.compile(r"""
210                    MAILER-DAEMON@
211                    """, re.VERBOSE)
212                result =  FROM_RE.search(self.email_addr)
213                if result:
214                        return True
215                else:
216                        return False
217
218        def email_to_unicode(self, message_str):
219                """
220                Email has 7 bit ASCII code, convert it to unicode with the charset
221        that is encoded in 7-bit ASCII code and encode it as utf-8 so Trac
222                understands it.
223                """
224                results =  email.Header.decode_header(message_str)
225                str = None
226                for text,format in results:
227                        if format:
228                                try:
229                                        temp = unicode(text, format)
230                                except UnicodeError, detail:
231                                        # This always works
232                                        #
233                                        temp = unicode(text, 'iso-8859-15')
234                                except LookupError, detail:
235                                        #text = 'ERROR: Could not find charset: %s, please install' %format
236                                        #temp = unicode(text, 'iso-8859-15')
237                                        temp = message_str
238                                       
239                        else:
240                                temp = string.strip(text)
241                                temp = unicode(text, 'iso-8859-15')
242
243                        if str:
244                                str = '%s %s' %(str, temp)
245                        else:
246                                str = '%s' %temp
247
248                #str = str.encode('utf-8')
249                return str
250
251        def debug_attachments(self, message):
252                n = 0
253                for part in message.walk():
254                        if part.get_content_maintype() == 'multipart':      # multipart/* is just a container
255                                print 'TD: multipart container'
256                                continue
257
258                        n = n + 1
259                        print 'TD: part%d: Content-Type: %s' % (n, part.get_content_type())
260                        print 'TD: part%d: filename: %s' % (n, part.get_filename())
261
262                        if part.is_multipart():
263                                print 'TD: this part is multipart'
264                                payload = part.get_payload(decode=1)
265                                print 'TD: payload:', payload
266                        else:
267                                print 'TD: this part is not multipart'
268
269                        part_file = '/var/tmp/part%d' % n
270                        print 'TD: writing part%d (%s)' % (n,part_file)
271                        fx = open(part_file, 'wb')
272                        text = part.get_payload(decode=1)
273                        if not text:
274                                text = '(None)'
275                        fx.write(text)
276                        fx.close()
277                        try:
278                                os.chmod(part_file,S_IRWXU|S_IRWXG|S_IRWXO)
279                        except OSError:
280                                pass
281
282        def email_header_txt(self, m):
283                """
284                Display To and CC addresses in description field
285                """
286                str = ''
287                if m['To'] and len(m['To']) > 0 and m['To'] != 'hic@sara.nl':
288                        str = "'''To:''' %s [[BR]]" %(m['To'])
289                if m['Cc'] and len(m['Cc']) > 0:
290                        str = "%s'''Cc:''' %s [[BR]]" % (str, m['Cc'])
291
292                return  self.email_to_unicode(str)
293
294
295        def set_owner(self, ticket):
296                """
297                Select default owner for ticket component
298                """
299                cursor = self.db.cursor()
300                sql = "SELECT owner FROM component WHERE name='%s'" % ticket['component']
301                cursor.execute(sql)
302                try:
303                        ticket['owner'] = cursor.fetchone()[0]
304                except TypeError, detail:
305                        ticket['owner'] = "UNKNOWN"
306
307        def get_author_emailaddrs(self, message):
308                """
309                Get the default author name and email address from the message
310                """
311                temp = self.email_to_unicode(message['from'])
312                #print temp.encode('utf-8')
313
314                self.author, self.email_addr  = email.Utils.parseaddr(temp)
315                #print self.author.encode('utf-8', 'replace')
316
317                # Look for email address in registered trac users
318                #
319                if self.VERSION == 0.8:
320                        users = []
321                else:
322                        users = [ u for (u, n, e) in self.env.get_known_users(self.db)
323                                if e == self.email_addr ]
324
325                if len(users) == 1:
326                        self.email_field = users[0]
327                else:
328                        self.email_field =  self.email_to_unicode(message['from'])
329                        #self.email_field =  self.email_to_unicode(self.email_addr)
330
331        def set_reply_fields(self, ticket, message):
332                """
333                Set all the right fields for a new ticket
334                """
335                ticket['reporter'] = self.email_field
336
337                # Put all CC-addresses in ticket CC field
338                #
339                if self.REPLY_ALL:
340                        #tos = message.get_all('to', [])
341                        ccs = message.get_all('cc', [])
342
343                        addrs = email.Utils.getaddresses(ccs)
344                        if not addrs:
345                                return
346
347                        # Remove reporter email address if notification is
348                        # on
349                        #
350                        if self.notification:
351                                try:
352                                        addrs.remove((self.author, self.email_addr))
353                                except ValueError, detail:
354                                        pass
355
356                        for name,mail in addrs:
357                                try:
358                                        mail_list = '%s, %s' %(mail_list, mail)
359                                except:
360                                        mail_list = mail
361
362                        if mail_list:
363                                ticket['cc'] = self.email_to_unicode(mail_list)
364
365        def save_email_for_debug(self, message, tempfile=False):
366                if tempfile:
367                        import tempfile
368                        msg_file = tempfile.mktemp('.email2trac')
369                else:
370                        msg_file = '/var/tmp/msg.txt'
371                print 'TD: saving email to %s' % msg_file
372                fx = open(msg_file, 'wb')
373                fx.write('%s' % message)
374                fx.close()
375                try:
376                        os.chmod(msg_file,S_IRWXU|S_IRWXG|S_IRWXO)
377                except OSError:
378                        pass
379
380        def ticket_update(self, m):
381                """
382                If the current email is a reply to an existing ticket, this function
383                will append the contents of this email to that ticket, instead of
384                creating a new one.
385                """
386                if not m['Subject']:
387                        return False
388                else:
389                        subject  = self.email_to_unicode(m['Subject'])
390
391                TICKET_RE = re.compile(r"""
392                                        (?P<ticketnr>[#][0-9]+:)
393                                        """, re.VERBOSE)
394
395                result =  TICKET_RE.search(subject)
396                if not result:
397                        return False
398
399                body_text = self.get_body_text(m)
400
401                # Strip '#' and ':' from ticket_id
402                #
403                ticket_id = result.group('ticketnr')
404                ticket_id = int(ticket_id[1:-1])
405
406                # Get current time
407                #
408                when = int(time.time())
409
410                if self.VERSION  == 0.8:
411                        tkt = Ticket(self.db, ticket_id)
412                        tkt.save_changes(self.db, self.author, body_text, when)
413                else:
414                        try:
415                                tkt = Ticket(self.env, ticket_id, self.db)
416                        except util.TracError, detail:
417                                return False
418
419                        tkt.save_changes(self.author, body_text, when)
420                        tkt['id'] = ticket_id
421
422                if self.VERSION  == 0.9:
423                        str = self.attachments(m, tkt, True)
424                else:
425                        str = self.attachments(m, tkt)
426
427                if self.notification:
428                        self.notify(tkt, False, when)
429
430                return True
431
432        def new_ticket(self, msg):
433                """
434                Create a new ticket
435                """
436                tkt = Ticket(self.env)
437                tkt['status'] = 'new'
438
439                # Some defaults
440                #
441                tkt['milestone'] = self.get_config('ticket', 'default_milestone')
442                tkt['priority'] = self.get_config('ticket', 'default_priority')
443                tkt['severity'] = self.get_config('ticket', 'default_severity')
444                tkt['version'] = self.get_config('ticket', 'default_version')
445
446                if not msg['Subject']:
447                        tkt['summary'] = u'(No subject)'
448                else:
449                        tkt['summary'] = self.email_to_unicode(msg['Subject'])
450
451
452                if settings.has_key('component'):
453                        tkt['component'] = settings['component']
454                else:
455                        tkt['component'] = self.spam(msg)
456
457                # Discard SPAM messages.
458                #
459                if self.DROP_SPAM and (tkt['component'] == 'Spam'):
460                        if self.DEBUG > 2 :
461                          print 'This message is a SPAM. Automatic ticket insertion refused (SPAM level > %d' % self.SPAM_LEVEL
462                        return False   
463
464                # Set default owner for component
465                #
466                self.set_owner(tkt)
467                self.set_reply_fields(tkt, msg)
468
469                # produce e-mail like header
470                #
471                head = ''
472                if self.EMAIL_HEADER > 0:
473                        head = self.email_header_txt(msg)
474                       
475                body_text = self.get_body_text(msg)
476
477                tkt['description'] = '\r\n%s\r\n%s' \
478                        %(head, body_text)
479
480                when = int(time.time())
481                if self.VERSION == 0.8:
482                        ticket_id = tkt.insert(self.db)
483                else:
484                        ticket_id = tkt.insert()
485                        tkt['id'] = ticket_id
486
487                changed = False
488                comment = ''
489
490                # Rewrite the description if we have mailto enabled
491                #
492                if self.MAILTO:
493                        changed = True
494                        comment = u'\nadded mailto line\n'
495                        mailto = self.html_mailto_link(tkt['summary'], ticket_id, body_text)
496                        tkt['description'] = u'\r\n%s\r\n%s%s\r\n' \
497                                %(head, mailto, body_text)
498
499                str =  self.attachments(msg, tkt)
500                if str:
501                        changed = True
502                        comment = '%s\n%s\n' %(comment, str)
503
504                if changed:
505                        if self.VERSION  == 0.8:
506                                tkt.save_changes(self.db, self.author, comment)
507                        else:
508                                tkt.save_changes(self.author, comment)
509
510                #print tkt.get_changelog(self.db, when)
511
512                if self.notification:
513                        self.notify(tkt, True)
514                        #self.notify(tkt, False)
515
516        def parse(self, fp):
517                global m
518
519                m = email.message_from_file(fp)
520                if not m:
521                        return
522
523                if self.DEBUG > 1:        # save the entire e-mail message text
524                        self.save_email_for_debug(m)
525                        self.debug_attachments(m)
526
527                self.db = self.env.get_db_cnx()
528                self.get_author_emailaddrs(m)
529
530                if self.blacklisted_from():
531                        if self.DEBUG > 1 :
532                                print 'Message rejected : From: in blacklist'
533                        return False
534
535                if self.get_config('notification', 'smtp_enabled') in ['true']:
536                        self.notification = 1
537                else:
538                        self.notification = 0
539
540                # Must we update existing tickets
541                #
542                if self.TICKET_UPDATE > 0:
543                        if self.ticket_update(m):
544                                return True
545
546                self.new_ticket(m)
547
548        def strip_signature(self, text):
549                """
550                Strip signature from message, inspired by Mailman software
551                """
552                body = []
553                for line in text.splitlines():
554                        if line == '-- ':
555                                break
556                        body.append(line)
557
558                return ('\n'.join(body))
559
560
561        def wrap_text(self, text, replace_whitespace = False):
562                """
563                Will break a lines longer then given length into several small lines of size
564                given length
565                """
566                import textwrap
567
568                LINESEPARATOR = '\n'
569                reformat = ''
570
571                for s in text.split(LINESEPARATOR):
572                        tmp = textwrap.fill(s,self.USE_TEXTWRAP)
573                        if tmp:
574                                reformat = '%s\n%s' %(reformat,tmp)
575                        else:
576                                reformat = '%s\n' %reformat
577
578                return reformat
579
580                # Python2.4 and higher
581                #
582                #return LINESEPARATOR.join(textwrap.fill(s,width) for s in str.split(LINESEPARATOR))
583                #
584
585
586        def get_body_text(self, msg):
587                """
588                put the message text in the ticket description or in the changes field.
589                message text can be plain text or html or something else
590                """
591                has_description = 0
592                encoding = True
593                ubody_text = u'No plain text message'
594                for part in msg.walk():
595
596                        # 'multipart/*' is a container for multipart messages
597                        #
598                        if part.get_content_maintype() == 'multipart':
599                                continue
600
601                        if part.get_content_type() == 'text/plain':
602                                # Try to decode, if fails then do not decode
603                                #
604                                body_text = part.get_payload(decode=1)
605                                if not body_text:                       
606                                        body_text = part.get_payload(decode=0)
607       
608                                if self.STRIP_SIGNATURE:
609                                        body_text = self.strip_signature(body_text)
610
611                                if self.USE_TEXTWRAP:
612                                        body_text = self.wrap_text(body_text)
613
614                                # Get contents charset (iso-8859-15 if not defined in mail headers)
615                                #
616                                charset = part.get_content_charset()
617                                if not charset:
618                                        charset = 'iso-8859-15'
619
620                                try:
621                                        ubody_text = unicode(body_text, charset)
622
623                                except UnicodeError, detail:
624                                        ubody_text = unicode(body_text, 'iso-8859-15')
625
626                                except LookupError, detail:
627                                        ubody_text = 'ERROR: Could not find charset: %s, please install' %(charset)
628
629                        elif part.get_content_type() == 'text/html':
630                                ubody_text = '(see attachment for HTML mail message)'
631
632                        else:
633                                ubody_text = '(see attachment for message)'
634
635                        has_description = 1
636                        break           # we have the description, so break
637
638                if not has_description:
639                        ubody_text = '(see attachment for message)'
640
641                # A patch so that the web-interface will not update the description
642                # field of a ticket
643                #
644                ubody_text = ('\r\n'.join(ubody_text.splitlines()))
645
646                #  If we can unicode it try to encode it for trac
647                #  else we a lot of garbage
648                #
649                #if encoding:
650                #       ubody_text = ubody_text.encode('utf-8')
651
652                if self.VERBATIM_FORMAT:
653                        ubody_text = '{{{\r\n%s\r\n}}}' %ubody_text
654                else:
655                        ubody_text = '%s' %ubody_text
656
657                return ubody_text
658
659        def notify(self, tkt , new=True, modtime=0):
660                """
661                A wrapper for the TRAC notify function. So we can use templates
662                """
663                if tkt['component'] == 'Spam':
664                        return 
665
666                try:
667                        # create false {abs_}href properties, to trick Notify()
668                        #
669                        self.env.abs_href = Href(self.get_config('project', 'url'))
670                        self.env.href = Href(self.get_config('project', 'url'))
671
672                        tn = TicketNotifyEmail(self.env)
673                        if self.notify_template:
674                                tn.template_name = self.notify_template;
675
676                        tn.notify(tkt, new, modtime)
677
678                except Exception, e:
679                        print 'TD: Failure sending notification on creation of ticket #%s: %s' %(tkt['id'], e)
680
681        def mail_line(self, str):
682                return '%s %s' % (self.comment, str)
683
684
685        def html_mailto_link(self, subject, id, body):
686                if not self.author:
687                        author = self.email_addr
688                else:   
689                        author = self.author
690
691                # Must find a fix
692                #
693                #arr = string.split(body, '\n')
694                #arr = map(self.mail_line, arr)
695                #body = string.join(arr, '\n')
696                #body = '%s wrote:\n%s' %(author, body)
697
698                # Temporary fix
699                #
700                str = 'mailto:%s?Subject=%s&Cc=%s' %(
701                       urllib.quote(self.email_addr),
702                           urllib.quote('Re: #%s: %s' %(id, subject)),
703                           urllib.quote(self.MAILTO_CC)
704                           )
705
706                str = '\r\n{{{\r\n#!html\r\n<a href="%s">Reply to: %s</a>\r\n}}}\r\n' %(str, author)
707                return str
708
709        def attachments(self, message, ticket, update=False):
710                '''
711                save any attachments as files in the ticket's directory
712                '''
713                count = 0
714                first = 0
715                number = 0
716
717                # Get Maxium attachment size
718                #
719                max_size = int(self.get_config('attachment', 'max_size'))
720                status   = ''
721
722                for part in message.walk():
723                        if part.get_content_maintype() == 'multipart':          # multipart/* is just a container
724                                continue
725
726                        if not first:                                                                           # first content is the message
727                                first = 1
728                                if part.get_content_type() == 'text/plain':             # if first is text, is was already put in the description
729                                        continue
730
731                        filename = part.get_filename()
732                        if not filename:
733                                number = number + 1
734                                filename = 'part%04d' % number
735
736                                ext = mimetypes.guess_extension(part.get_content_type())
737                                if not ext:
738                                        ext = '.bin'
739
740                                filename = '%s%s' % (filename, ext)
741                        else:
742                                filename = self.email_to_unicode(filename)
743
744                        # From the trac code
745                        #
746                        filename = filename.replace('\\', '/').replace(':', '/')
747                        filename = os.path.basename(filename)
748
749                        # We try to normalize the filename to utf-8 NFC if we can.
750                        # Files uploaded from OS X might be in NFD.
751                        # Check python version and then try it
752                        #
753                        if sys.version_info[0] > 2 or (sys.version_info[0] == 2 and sys.version_info[1] >= 3):
754                                try:
755                                        filename = unicodedata.normalize('NFC', unicode(filename, 'utf-8')).encode('utf-8') 
756                                except TypeError:
757                                        pass
758
759                        url_filename = urllib.quote(filename)
760                        if self.VERSION == 0.8:
761                                dir = os.path.join(self.env.get_attachments_dir(), 'ticket',
762                                                        urllib.quote(str(ticket['id'])))
763                                if not os.path.exists(dir):
764                                        mkdir_p(dir, 0755)
765                        else:
766                                dir = '/tmp'
767
768                        path, fd =  util.create_unique_file(os.path.join(dir, url_filename))
769                        text = part.get_payload(decode=1)
770                        if not text:
771                                text = '(None)'
772                        fd.write(text)
773                        fd.close()
774
775                        # get the file_size
776                        #
777                        stats = os.lstat(path)
778                        file_size = stats[stat.ST_SIZE]
779
780                        # Check if the attachment size is allowed
781                        #
782                        if (max_size != -1) and (file_size > max_size):
783                                status = '%s\nFile %s is larger then allowed attachment size (%d > %d)\n\n' \
784                                        %(status, filename, file_size, max_size)
785
786                                os.unlink(path)
787                                continue
788                        else:
789                                count = count + 1
790                                       
791                        # Insert the attachment it differs for the different TRAC versions
792                        #
793                        if self.VERSION == 0.8:
794                                cursor = self.db.cursor()
795                                try:
796                                        cursor.execute('INSERT INTO attachment VALUES("%s","%s","%s",%d,%d,"%s","%s","%s")'
797                                                %('ticket', urllib.quote(str(ticket['id'])), filename + '?format=raw', file_size,
798                                                int(time.time()),'', self.author, 'e-mail') )
799
800                                # Attachment is already known
801                                #
802                                except sqlite.IntegrityError:   
803                                        #self.db.close()
804                                        return count
805
806                                self.db.commit()
807
808                        else:
809                                fd = open(path)
810                                att = attachment.Attachment(self.env, 'ticket', ticket['id'])
811
812                                # This will break the ticket_update system, the body_text is vaporized
813                                # ;-(
814                                #
815                                if not update:
816                                        att.author = self.author
817                                        att.description = self.email_to_unicode('Added by email2trac')
818
819                                att.insert(url_filename, fd, file_size)
820
821                                #except  util.TracError, detail:
822                                #       print detail
823
824                                fd.close()
825
826                        # Remove the created temporary filename
827                        #
828                        os.unlink(path)
829
830                # Return how many attachments
831                #
832                status = 'This message has %d attachment(s)\n%s' %(count, status)
833                return status
834
835
836def mkdir_p(dir, mode):
837        '''do a mkdir -p'''
838
839        arr = string.split(dir, '/')
840        path = ''
841        for part in arr:
842                path = '%s/%s' % (path, part)
843                try:
844                        stats = os.stat(path)
845                except OSError:
846                        os.mkdir(path, mode)
847
848
849def ReadConfig(file, name):
850        """
851        Parse the config file
852        """
853
854        if not os.path.isfile(file):
855                print 'File %s does not exist' %file
856                sys.exit(1)
857
858        config = ConfigParser.ConfigParser()
859        try:
860                config.read(file)
861        except ConfigParser.MissingSectionHeaderError,detail:
862                print detail
863                sys.exit(1)
864
865
866        # Use given project name else use defaults
867        #
868        if name:
869                if not config.has_section(name):
870                        print "Not a valid project name: %s" %name
871                        print "Valid names: %s" %config.sections()
872                        sys.exit(1)
873
874                project =  dict()
875                for option in  config.options(name):
876                        project[option] = config.get(name, option)
877
878        else:
879                project = config.defaults()
880
881        return project
882
883
884if __name__ == '__main__':
885        # Default config file
886        #
887        configfile = '@email2trac_conf@'
888        project = ''
889        component = ''
890        ENABLE_SYSLOG = 0
891               
892        try:
893                opts, args = getopt.getopt(sys.argv[1:], 'chf:p:', ['component=','help', 'file=', 'project='])
894        except getopt.error,detail:
895                print __doc__
896                print detail
897                sys.exit(1)
898       
899        project_name = None
900        for opt,value in opts:
901                if opt in [ '-h', '--help']:
902                        print __doc__
903                        sys.exit(0)
904                elif opt in ['-c', '--component']:
905                        component = value
906                elif opt in ['-f', '--file']:
907                        configfile = value
908                elif opt in ['-p', '--project']:
909                        project_name = value
910       
911        settings = ReadConfig(configfile, project_name)
912        if not settings.has_key('project'):
913                print __doc__
914                print 'No Trac project is defined in the email2trac config file.'
915                sys.exit(1)
916       
917        if component:
918                settings['component'] = component
919       
920        if settings.has_key('trac_version'):
921                version = float(settings['trac_version'])
922        else:
923                version = trac_default_version
924
925        if settings.has_key('enable_syslog'):
926                ENABLE_SYSLOG =  float(settings['enable_syslog'])
927                       
928        #debug HvB
929        #print settings
930       
931        try:
932                if version == 0.8:
933                        from trac.Environment import Environment
934                        from trac.Ticket import Ticket
935                        from trac.Notify import TicketNotifyEmail
936                        from trac.Href import Href
937                        from trac import util
938                        import sqlite
939                elif version == 0.9:
940                        from trac import attachment
941                        from trac.env import Environment
942                        from trac.ticket import Ticket
943                        from trac.web.href import Href
944                        from trac import util
945                        from trac.Notify import TicketNotifyEmail
946                elif version == 0.10:
947                        from trac import attachment
948                        from trac.env import Environment
949                        from trac.ticket import Ticket
950                        from trac.web.href import Href
951                        from trac import util
952                        #
953                        # return  util.text.to_unicode(str)
954                        #
955                        # see http://projects.edgewall.com/trac/changeset/2799
956                        from trac.ticket.notification import TicketNotifyEmail
957       
958                env = Environment(settings['project'], create=0)
959                tktparser = TicketEmailParser(env, settings, version)
960                tktparser.parse(sys.stdin)
961
962        # Catch all errors ans log to SYSLOG if we have enabled this
963        # else stdout
964        #
965        except Exception, error:
966                if ENABLE_SYSLOG:
967                        syslog.openlog('email2trac', syslog.LOG_NOWAIT)
968                        etype, evalue, etb = sys.exc_info()
969                        for e in traceback.format_exception(etype, evalue, etb):
970                                syslog.syslog(e)
971                        syslog.closelog()
972                else:
973                        traceback.print_exc()
974
975                if m:
976                        tktparser.save_email_for_debug(m, True)
977
978# EOB
Note: See TracBrowser for help on using the repository browser.