source: emailtotracscript/trunk/email2trac.py.in @ 154

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

EmailToTracScript?
EmailtoTracScript?:

email2trac.py.in:

  • Fixed bugs in the text_wrap function and reformat it so we do not have to use python2.4
  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 24.1 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 154 2007-04-24 08:28:14Z 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        # X-Spam-Score: *** (3.255) BAYES_50,DNS_FROM_AHBL_RHSBL,HTML_
190        # Note if Spam_level then '*' are included
191        def spam(self, message):
192                if message.has_key('X-Spam-Score'):
193                        spam_l = string.split(message['X-Spam-Score'])
194                        number = spam_l[0].count('*')
195
196                        if number >= self.SPAM_LEVEL:
197                                return 'Spam'
198
199                elif message.has_key('X-Virus-found'):                  # treat virus mails as spam
200                        return 'Spam'
201
202                return self.get_config('ticket', 'default_component')
203
204        def blacklisted_from(self):
205                FROM_RE = re.compile(r"""
206                    MAILER-DAEMON@
207                    """, re.VERBOSE)
208                result =  FROM_RE.search(self.email_addr)
209                if result:
210                        return True
211                else:
212                        return False
213
214        def email_to_unicode(self, message_str):
215                """
216                Email has 7 bit ASCII code, convert it to unicode with the charset
217        that is encoded in 7-bit ASCII code and encode it as utf-8 so Trac
218                understands it.
219                """
220                results =  email.Header.decode_header(message_str)
221                str = None
222                for text,format in results:
223                        if format:
224                                try:
225                                        temp = unicode(text, format)
226                                except UnicodeError, detail:
227                                        # This always works
228                                        #
229                                        temp = unicode(text, 'iso-8859-15')
230                                except LookupError, detail:
231                                        #text = 'ERROR: Could not find charset: %s, please install' %format
232                                        #temp = unicode(text, 'iso-8859-15')
233                                        temp = message_str
234                                       
235                        else:
236                                temp = string.strip(text)
237                                temp = unicode(text, 'iso-8859-15')
238
239                        if str:
240                                str = '%s %s' %(str, temp)
241                        else:
242                                str = '%s' %temp
243
244                #str = str.encode('utf-8')
245                return str
246
247        def debug_attachments(self, message):
248                n = 0
249                for part in message.walk():
250                        if part.get_content_maintype() == 'multipart':      # multipart/* is just a container
251                                print 'TD: multipart container'
252                                continue
253
254                        n = n + 1
255                        print 'TD: part%d: Content-Type: %s' % (n, part.get_content_type())
256                        print 'TD: part%d: filename: %s' % (n, part.get_filename())
257
258                        if part.is_multipart():
259                                print 'TD: this part is multipart'
260                                payload = part.get_payload(decode=1)
261                                print 'TD: payload:', payload
262                        else:
263                                print 'TD: this part is not multipart'
264
265                        part_file = '/var/tmp/part%d' % n
266                        print 'TD: writing part%d (%s)' % (n,part_file)
267                        fx = open(part_file, 'wb')
268                        text = part.get_payload(decode=1)
269                        if not text:
270                                text = '(None)'
271                        fx.write(text)
272                        fx.close()
273                        try:
274                                os.chmod(part_file,S_IRWXU|S_IRWXG|S_IRWXO)
275                        except OSError:
276                                pass
277
278        def email_header_txt(self, m):
279                """
280                Display To and CC addresses in description field
281                """
282                str = ''
283                if m['To'] and len(m['To']) > 0 and m['To'] != 'hic@sara.nl':
284                        str = "'''To:''' %s [[BR]]" %(m['To'])
285                if m['Cc'] and len(m['Cc']) > 0:
286                        str = "%s'''Cc:''' %s [[BR]]" % (str, m['Cc'])
287
288                return  self.email_to_unicode(str)
289
290
291        def set_owner(self, ticket):
292                """
293                Select default owner for ticket component
294                """
295                cursor = self.db.cursor()
296                sql = "SELECT owner FROM component WHERE name='%s'" % ticket['component']
297                cursor.execute(sql)
298                try:
299                        ticket['owner'] = cursor.fetchone()[0]
300                except TypeError, detail:
301                        ticket['owner'] = "UNKNOWN"
302
303        def get_author_emailaddrs(self, message):
304                """
305                Get the default author name and email address from the message
306                """
307                temp = self.email_to_unicode(message['from'])
308                #print temp.encode('utf-8')
309
310                self.author, self.email_addr  = email.Utils.parseaddr(temp)
311                #print self.author.encode('utf-8', 'replace')
312
313                # Look for email address in registered trac users
314                #
315                if self.VERSION == 0.8:
316                        users = []
317                else:
318                        users = [ u for (u, n, e) in self.env.get_known_users(self.db)
319                                if e == self.email_addr ]
320
321                if len(users) == 1:
322                        self.email_field = users[0]
323                else:
324                        self.email_field =  self.email_to_unicode(message['from'])
325
326        def set_reply_fields(self, ticket, message):
327                """
328                Set all the right fields for a new ticket
329                """
330                ticket['reporter'] = self.email_field
331
332                # Put all CC-addresses in ticket CC field
333                #
334                if self.REPLY_ALL:
335                        #tos = message.get_all('to', [])
336                        ccs = message.get_all('cc', [])
337
338                        addrs = email.Utils.getaddresses(ccs)
339                        if not addrs:
340                                return
341
342                        # Remove reporter email address if notification is
343                        # on
344                        #
345                        if self.notification:
346                                try:
347                                        addrs.remove((self.author, self.email_addr))
348                                except ValueError, detail:
349                                        pass
350
351                        for name,mail in addrs:
352                                try:
353                                        mail_list = '%s, %s' %(mail_list, mail)
354                                except:
355                                        mail_list = mail
356
357                        if mail_list:
358                                ticket['cc'] = self.email_to_unicode(mail_list)
359
360        def save_email_for_debug(self, message, tempfile=False):
361                if tempfile:
362                        import tempfile
363                        msg_file = tempfile.mktemp('.email2trac')
364                else:
365                        msg_file = '/var/tmp/msg.txt'
366                print 'TD: saving email to %s' % msg_file
367                fx = open(msg_file, 'wb')
368                fx.write('%s' % message)
369                fx.close()
370                try:
371                        os.chmod(msg_file,S_IRWXU|S_IRWXG|S_IRWXO)
372                except OSError:
373                        pass
374
375        def ticket_update(self, m):
376                """
377                If the current email is a reply to an existing ticket, this function
378                will append the contents of this email to that ticket, instead of
379                creating a new one.
380                """
381                if not m['Subject']:
382                        return False
383                else:
384                        subject  = self.email_to_unicode(m['Subject'])
385
386                TICKET_RE = re.compile(r"""
387                                        (?P<ticketnr>[#][0-9]+:)
388                                        """, re.VERBOSE)
389
390                result =  TICKET_RE.search(subject)
391                if not result:
392                        return False
393
394                body_text = self.get_body_text(m)
395
396                # Strip '#' and ':' from ticket_id
397                #
398                ticket_id = result.group('ticketnr')
399                ticket_id = int(ticket_id[1:-1])
400
401                # Get current time
402                #
403                when = int(time.time())
404
405                if self.VERSION  == 0.8:
406                        tkt = Ticket(self.db, ticket_id)
407                        tkt.save_changes(self.db, self.author, body_text, when)
408                else:
409                        try:
410                                tkt = Ticket(self.env, ticket_id, self.db)
411                        except util.TracError, detail:
412                                return False
413
414                        tkt.save_changes(self.author, body_text, when)
415                        tkt['id'] = ticket_id
416
417                if self.VERSION  == 0.9:
418                        str = self.attachments(m, tkt, True)
419                else:
420                        str = self.attachments(m, tkt)
421
422                if self.notification:
423                        self.notify(tkt, False, when)
424
425                return True
426
427        def new_ticket(self, msg):
428                """
429                Create a new ticket
430                """
431                tkt = Ticket(self.env)
432                tkt['status'] = 'new'
433
434                # Some defaults
435                #
436                tkt['milestone'] = self.get_config('ticket', 'default_milestone')
437                tkt['priority'] = self.get_config('ticket', 'default_priority')
438                tkt['severity'] = self.get_config('ticket', 'default_severity')
439                tkt['version'] = self.get_config('ticket', 'default_version')
440
441                if not msg['Subject']:
442                        tkt['summary'] = u'(No subject)'
443                else:
444                        tkt['summary'] = self.email_to_unicode(msg['Subject'])
445
446
447                if settings.has_key('component'):
448                        tkt['component'] = settings['component']
449                else:
450                        tkt['component'] = self.spam(msg)
451
452                # Discard SPAM messages.
453                #
454                if self.DROP_SPAM and (tkt['component'] == 'Spam'):
455                        if self.DEBUG > 2 :
456                          print 'This message is a SPAM. Automatic ticket insertion refused (SPAM level > %d' % self.SPAM_LEVEL
457                        return False   
458
459                # Set default owner for component
460                #
461                self.set_owner(tkt)
462                self.set_reply_fields(tkt, msg)
463
464                # produce e-mail like header
465                #
466                head = ''
467                if self.EMAIL_HEADER > 0:
468                        head = self.email_header_txt(msg)
469                       
470                body_text = self.get_body_text(msg)
471
472                tkt['description'] = '\r\n%s\r\n%s' \
473                        %(head, body_text)
474
475                when = int(time.time())
476                if self.VERSION == 0.8:
477                        ticket_id = tkt.insert(self.db)
478                else:
479                        ticket_id = tkt.insert()
480                        tkt['id'] = ticket_id
481
482                changed = False
483                comment = ''
484
485                # Rewrite the description if we have mailto enabled
486                #
487                if self.MAILTO:
488                        changed = True
489                        comment = u'\nadded mailto line\n'
490                        mailto = self.html_mailto_link(tkt['summary'], ticket_id, body_text)
491                        tkt['description'] = u'\r\n%s\r\n%s%s\r\n' \
492                                %(head, mailto, body_text)
493
494                str =  self.attachments(msg, tkt)
495                if str:
496                        changed = True
497                        comment = '%s\n%s\n' %(comment, str)
498
499                if changed:
500                        if self.VERSION  == 0.8:
501                                tkt.save_changes(self.db, self.author, comment)
502                        else:
503                                tkt.save_changes(self.author, comment)
504
505                #print tkt.get_changelog(self.db, when)
506
507                if self.notification:
508                        self.notify(tkt, True)
509                        #self.notify(tkt, False)
510
511        def parse(self, fp):
512                global m
513
514                m = email.message_from_file(fp)
515                if not m:
516                        return
517
518                if self.DEBUG > 1:        # save the entire e-mail message text
519                        self.save_email_for_debug(m)
520                        self.debug_attachments(m)
521
522                self.db = self.env.get_db_cnx()
523                self.get_author_emailaddrs(m)
524
525                if self.blacklisted_from():
526                        if self.DEBUG > 1 :
527                                print 'Message rejected : From: in blacklist'
528                        return False
529
530                if self.get_config('notification', 'smtp_enabled') in ['true']:
531                        self.notification = 1
532                else:
533                        self.notification = 0
534
535                # Must we update existing tickets
536                #
537                if self.TICKET_UPDATE > 0:
538                        if self.ticket_update(m):
539                                return True
540
541                self.new_ticket(m)
542
543        def strip_signature(self, text):
544                """
545                Strip signature from message, inspired by Mailman software
546                """
547                body = []
548                for line in text.splitlines():
549                        if line == '-- ':
550                                break
551                        body.append(line)
552
553                return ('\n'.join(body))
554
555
556        def wrap_text(self, text, replace_whitespace = False):
557                """
558                Will break a lines longer then given length into several small lines of size
559                given length
560                """
561                import textwrap
562
563                LINESEPARATOR = '\n'
564                reformat = ''
565
566                for s in text.split(LINESEPARATOR):
567                        tmp = textwrap.fill(s,self.USE_TEXTWRAP)
568                        if tmp:
569                                reformat = '%s\n%s' %(reformat,tmp)
570                        else:
571                                reformat = '%s\n' %reformat
572
573                return reformat
574
575                # Python2.4 and higher
576                #
577                #return LINESEPARATOR.join(textwrap.fill(s,width) for s in str.split(LINESEPARATOR))
578                #
579
580
581        def get_body_text(self, msg):
582                """
583                put the message text in the ticket description or in the changes field.
584                message text can be plain text or html or something else
585                """
586                has_description = 0
587                encoding = True
588                ubody_text = u'No plain text message'
589                for part in msg.walk():
590
591                        # 'multipart/*' is a container for multipart messages
592                        #
593                        if part.get_content_maintype() == 'multipart':
594                                continue
595
596                        if part.get_content_type() == 'text/plain':
597                                # Try to decode, if fails then do not decode
598                                #
599                                body_text = part.get_payload(decode=1)
600                                if not body_text:                       
601                                        body_text = part.get_payload(decode=0)
602       
603                                if self.STRIP_SIGNATURE:
604                                        body_text = self.strip_signature(body_text)
605
606                                if self.USE_TEXTWRAP:
607                                        body_text = self.wrap_text(body_text)
608
609                                # Get contents charset (iso-8859-15 if not defined in mail headers)
610                                #
611                                charset = part.get_content_charset()
612                                if not charset:
613                                        charset = 'iso-8859-15'
614
615                                try:
616                                        ubody_text = unicode(body_text, charset)
617
618                                except UnicodeError, detail:
619                                        ubody_text = unicode(body_text, 'iso-8859-15')
620
621                                except LookupError, detail:
622                                        ubody_text = 'ERROR: Could not find charset: %s, please install' %(charset)
623
624                        elif part.get_content_type() == 'text/html':
625                                ubody_text = '(see attachment for HTML mail message)'
626
627                        else:
628                                ubody_text = '(see attachment for message)'
629
630                        has_description = 1
631                        break           # we have the description, so break
632
633                if not has_description:
634                        ubody_text = '(see attachment for message)'
635
636                # A patch so that the web-interface will not update the description
637                # field of a ticket
638                #
639                ubody_text = ('\r\n'.join(ubody_text.splitlines()))
640
641                #  If we can unicode it try to encode it for trac
642                #  else we a lot of garbage
643                #
644                #if encoding:
645                #       ubody_text = ubody_text.encode('utf-8')
646
647                if self.VERBATIM_FORMAT:
648                        ubody_text = '{{{\r\n%s\r\n}}}' %ubody_text
649                else:
650                        ubody_text = '%s' %ubody_text
651
652                return ubody_text
653
654        def notify(self, tkt , new=True, modtime=0):
655                """
656                A wrapper for the TRAC notify function. So we can use templates
657                """
658                if tkt['component'] == 'Spam':
659                        return 
660
661                try:
662                        # create false {abs_}href properties, to trick Notify()
663                        #
664                        self.env.abs_href = Href(self.get_config('project', 'url'))
665                        self.env.href = Href(self.get_config('project', 'url'))
666
667                        tn = TicketNotifyEmail(self.env)
668                        if self.notify_template:
669                                tn.template_name = self.notify_template;
670
671                        tn.notify(tkt, new, modtime)
672
673                except Exception, e:
674                        print 'TD: Failure sending notification on creation of ticket #%s: %s' %(tkt['id'], e)
675
676        def mail_line(self, str):
677                return '%s %s' % (self.comment, str)
678
679
680        def html_mailto_link(self, subject, id, body):
681                if not self.author:
682                        author = self.email_addr
683                else:   
684                        author = self.author
685
686                # Must find a fix
687                #
688                #arr = string.split(body, '\n')
689                #arr = map(self.mail_line, arr)
690                #body = string.join(arr, '\n')
691                #body = '%s wrote:\n%s' %(author, body)
692
693                # Temporary fix
694                #
695                str = 'mailto:%s?Subject=%s&Cc=%s' %(
696                       urllib.quote(self.email_addr),
697                           urllib.quote('Re: #%s: %s' %(id, subject)),
698                           urllib.quote(self.MAILTO_CC)
699                           )
700
701                str = '\r\n{{{\r\n#!html\r\n<a href="%s">Reply to: %s</a>\r\n}}}\r\n' %(str, author)
702                return str
703
704        def attachments(self, message, ticket, update=False):
705                '''
706                save any attachments as files in the ticket's directory
707                '''
708                count = 0
709                first = 0
710                number = 0
711
712                # Get Maxium attachment size
713                #
714                max_size = int(self.get_config('attachment', 'max_size'))
715                status   = ''
716
717                for part in message.walk():
718                        if part.get_content_maintype() == 'multipart':          # multipart/* is just a container
719                                continue
720
721                        if not first:                                                                           # first content is the message
722                                first = 1
723                                if part.get_content_type() == 'text/plain':             # if first is text, is was already put in the description
724                                        continue
725
726                        filename = part.get_filename()
727                        if not filename:
728                                number = number + 1
729                                filename = 'part%04d' % number
730
731                                ext = mimetypes.guess_extension(part.get_content_type())
732                                if not ext:
733                                        ext = '.bin'
734
735                                filename = '%s%s' % (filename, ext)
736                        else:
737                                filename = self.email_to_unicode(filename)
738
739                        # From the trac code
740                        #
741                        filename = filename.replace('\\', '/').replace(':', '/')
742                        filename = os.path.basename(filename)
743
744                        # We try to normalize the filename to utf-8 NFC if we can.
745                        # Files uploaded from OS X might be in NFD.
746                        # Check python version and then try it
747                        #
748                        if sys.version_info[0] > 2 or (sys.version_info[0] == 2 and sys.version_info[1] >= 3):
749                                try:
750                                        filename = unicodedata.normalize('NFC', unicode(filename, 'utf-8')).encode('utf-8') 
751                                except TypeError:
752                                        pass
753
754                        url_filename = urllib.quote(filename)
755                        if self.VERSION == 0.8:
756                                dir = os.path.join(self.env.get_attachments_dir(), 'ticket',
757                                                        urllib.quote(str(ticket['id'])))
758                                if not os.path.exists(dir):
759                                        mkdir_p(dir, 0755)
760                        else:
761                                dir = '/tmp'
762
763                        path, fd =  util.create_unique_file(os.path.join(dir, url_filename))
764                        text = part.get_payload(decode=1)
765                        if not text:
766                                text = '(None)'
767                        fd.write(text)
768                        fd.close()
769
770                        # get the file_size
771                        #
772                        stats = os.lstat(path)
773                        file_size = stats[stat.ST_SIZE]
774
775                        # Check if the attachment size is allowed
776                        #
777                        if (max_size != -1) and (file_size > max_size):
778                                status = '%s\nFile %s is larger then allowed attachment size (%d > %d)\n\n' \
779                                        %(status, filename, file_size, max_size)
780
781                                os.unlink(path)
782                                continue
783                        else:
784                                count = count + 1
785                                       
786                        # Insert the attachment it differs for the different TRAC versions
787                        #
788                        if self.VERSION == 0.8:
789                                cursor = self.db.cursor()
790                                try:
791                                        cursor.execute('INSERT INTO attachment VALUES("%s","%s","%s",%d,%d,"%s","%s","%s")'
792                                                %('ticket', urllib.quote(str(ticket['id'])), filename + '?format=raw', file_size,
793                                                int(time.time()),'', self.author, 'e-mail') )
794
795                                # Attachment is already known
796                                #
797                                except sqlite.IntegrityError:   
798                                        #self.db.close()
799                                        return count
800
801                                self.db.commit()
802
803                        else:
804                                fd = open(path)
805                                att = attachment.Attachment(self.env, 'ticket', ticket['id'])
806
807                                # This will break the ticket_update system, the body_text is vaporized
808                                # ;-(
809                                #
810                                if not update:
811                                        att.author = self.author
812                                        att.description = self.email_to_unicode('Added by email2trac')
813
814                                att.insert(url_filename, fd, file_size)
815
816                                #except  util.TracError, detail:
817                                #       print detail
818
819                                fd.close()
820
821                        # Remove the created temporary filename
822                        #
823                        os.unlink(path)
824
825                # Return how many attachments
826                #
827                status = 'This message has %d attachment(s)\n%s' %(count, status)
828                return status
829
830
831def mkdir_p(dir, mode):
832        '''do a mkdir -p'''
833
834        arr = string.split(dir, '/')
835        path = ''
836        for part in arr:
837                path = '%s/%s' % (path, part)
838                try:
839                        stats = os.stat(path)
840                except OSError:
841                        os.mkdir(path, mode)
842
843
844def ReadConfig(file, name):
845        """
846        Parse the config file
847        """
848
849        if not os.path.isfile(file):
850                print 'File %s does not exist' %file
851                sys.exit(1)
852
853        config = ConfigParser.ConfigParser()
854        try:
855                config.read(file)
856        except ConfigParser.MissingSectionHeaderError,detail:
857                print detail
858                sys.exit(1)
859
860
861        # Use given project name else use defaults
862        #
863        if name:
864                if not config.has_section(name):
865                        print "Not a valid project name: %s" %name
866                        print "Valid names: %s" %config.sections()
867                        sys.exit(1)
868
869                project =  dict()
870                for option in  config.options(name):
871                        project[option] = config.get(name, option)
872
873        else:
874                project = config.defaults()
875
876        return project
877
878
879if __name__ == '__main__':
880        # Default config file
881        #
882        configfile = '@email2trac_conf@'
883        project = ''
884        component = ''
885        ENABLE_SYSLOG = 0
886               
887        try:
888                opts, args = getopt.getopt(sys.argv[1:], 'chf:p:', ['component=','help', 'file=', 'project='])
889        except getopt.error,detail:
890                print __doc__
891                print detail
892                sys.exit(1)
893       
894        project_name = None
895        for opt,value in opts:
896                if opt in [ '-h', '--help']:
897                        print __doc__
898                        sys.exit(0)
899                elif opt in ['-c', '--component']:
900                        component = value
901                elif opt in ['-f', '--file']:
902                        configfile = value
903                elif opt in ['-p', '--project']:
904                        project_name = value
905       
906        settings = ReadConfig(configfile, project_name)
907        if not settings.has_key('project'):
908                print __doc__
909                print 'No Trac project is defined in the email2trac config file.'
910                sys.exit(1)
911       
912        if component:
913                settings['component'] = component
914       
915        if settings.has_key('trac_version'):
916                version = float(settings['trac_version'])
917        else:
918                version = trac_default_version
919
920        if settings.has_key('enable_syslog'):
921                ENABLE_SYSLOG =  float(settings['enable_syslog'])
922                       
923        #debug HvB
924        #print settings
925       
926        try:
927                if version == 0.8:
928                        from trac.Environment import Environment
929                        from trac.Ticket import Ticket
930                        from trac.Notify import TicketNotifyEmail
931                        from trac.Href import Href
932                        from trac import util
933                        import sqlite
934                elif version == 0.9:
935                        from trac import attachment
936                        from trac.env import Environment
937                        from trac.ticket import Ticket
938                        from trac.web.href import Href
939                        from trac import util
940                        from trac.Notify import TicketNotifyEmail
941                elif version == 0.10:
942                        from trac import attachment
943                        from trac.env import Environment
944                        from trac.ticket import Ticket
945                        from trac.web.href import Href
946                        from trac import util
947                        #
948                        # return  util.text.to_unicode(str)
949                        #
950                        # see http://projects.edgewall.com/trac/changeset/2799
951                        from trac.ticket.notification import TicketNotifyEmail
952       
953                env = Environment(settings['project'], create=0)
954                tktparser = TicketEmailParser(env, settings, version)
955                tktparser.parse(sys.stdin)
956
957        # Catch all errors ans log to SYSLOG if we have enabled this
958        # else stdout
959        #
960        except Exception, error:
961                if ENABLE_SYSLOG:
962                        syslog.openlog('email2trac', syslog.LOG_NOWAIT)
963                        etype, evalue, etb = sys.exc_info()
964                        for e in traceback.format_exception(etype, evalue, etb):
965                                syslog.syslog(e)
966                        syslog.closelog()
967                else:
968                        traceback.print_exc()
969
970                if m:
971                        tktparser.save_email_for_debug(m, True)
972
973# EOB
Note: See TracBrowser for help on using the repository browser.