/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/mail_client.py

  • Committer: Lukáš Lalinsky
  • Date: 2007-08-10 20:51:08 UTC
  • mto: (2681.1.26 send-bundle)
  • mto: This revision was merged to the branch mainline in revision 2736.
  • Revision ID: lalinsky@gmail.com-20070810205108-b3lewiumf2r4kriy
Convert to UNIX line endings.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
import os
 
18
import sys
 
19
 
 
20
from bzrlib.lazy_import import lazy_import
 
21
lazy_import(globals(), '''
 
22
import errno
 
23
import subprocess
 
24
import tempfile
 
25
 
 
26
from bzrlib import (
 
27
    email_message,
 
28
    errors,
 
29
    msgeditor,
 
30
    urlutils,
 
31
    )
 
32
from bzrlib.util import simplemapi
 
33
''')
 
34
 
 
35
 
 
36
class MailClient(object):
 
37
    """A mail client that can send messages with attachements."""
 
38
 
 
39
    def __init__(self, config):
 
40
        self.config = config
 
41
 
 
42
    def compose(self, prompt, to, subject, attachment, mime_subtype,
 
43
                extension):
 
44
        raise NotImplementedError
 
45
 
 
46
    def compose_merge_request(self, to, subject, directive):
 
47
        prompt = self._get_merge_prompt("Please describe these changes:", to,
 
48
                                        subject, directive)
 
49
        self.compose(prompt, to, subject, directive,
 
50
            'x-patch', '.patch')
 
51
 
 
52
    def _get_merge_prompt(self, prompt, to, subject, attachment):
 
53
        return ''
 
54
 
 
55
 
 
56
class Editor(MailClient):
 
57
    """DIY mail client that uses commit message editor"""
 
58
 
 
59
    def _get_merge_prompt(self, prompt, to, subject, attachment):
 
60
        return "%s\n\nTo: %s\nSubject: %s\n\n%s" % (prompt, to, subject,
 
61
                attachment.decode('utf-8', 'replace'))
 
62
 
 
63
    def compose(self, prompt, to, subject, attachment, mime_subtype,
 
64
                extension):
 
65
        body = msgeditor.edit_commit_message(prompt)
 
66
        if body == '':
 
67
            raise errors.NoMessageSupplied()
 
68
        email_message.EmailMessage.send(self.config,
 
69
                                        self.config.username(),
 
70
                                        to,
 
71
                                        subject,
 
72
                                        body,
 
73
                                        attachment,
 
74
                                        attachment_mime_subtype=mime_subtype)
 
75
 
 
76
 
 
77
class ExternalMailClient(MailClient):
 
78
    """An external mail client."""
 
79
 
 
80
    _client_commands = []
 
81
 
 
82
    def compose(self, prompt, to, subject, attachment, mime_subtype,
 
83
                extension):
 
84
        fd, pathname = tempfile.mkstemp(extension, 'bzr-mail-')
 
85
        try:
 
86
            os.write(fd, attachment)
 
87
        finally:
 
88
            os.close(fd)
 
89
        self._compose(prompt, to, subject, pathname, mime_subtype, extension)
 
90
 
 
91
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
 
92
                extension):
 
93
        for name in self._client_commands:
 
94
            cmdline = [name]
 
95
            cmdline.extend(self._get_compose_commandline(to, subject, 
 
96
                                                         attach_path))
 
97
            try:
 
98
                subprocess.call(cmdline)
 
99
            except OSError, e:
 
100
                if e.errno != errno.ENOENT:
 
101
                    raise
 
102
            else:
 
103
                break
 
104
        else:
 
105
            raise errors.MailClientNotFound(self._client_commands)
 
106
 
 
107
    def _get_compose_commandline(self, to, subject, attach_path):
 
108
        message_options = {}
 
109
        if subject is not None:
 
110
            message_options['subject'] = subject
 
111
        if attach_path is not None:
 
112
            message_options['attach'] = attach_path
 
113
        options_list = ['%s=%s' % (k, urlutils.escape(v)) for (k, v) in
 
114
                        message_options.iteritems()]
 
115
        return ['mailto:%s?%s' % (to or '', '&'.join(options_list))]
 
116
 
 
117
 
 
118
class Evolution(ExternalMailClient):
 
119
    """Evolution mail client."""
 
120
 
 
121
    _client_commands = ['evolution']
 
122
 
 
123
    def _get_compose_commandline(self, to, subject, attach_path):
 
124
        message_options = {}
 
125
        if subject is not None:
 
126
            message_options['subject'] = subject
 
127
        if attach_path is not None:
 
128
            message_options['attach'] = attach_path
 
129
        options_list = ['%s=%s' % (k, urlutils.escape(v)) for (k, v) in
 
130
                        message_options.iteritems()]
 
131
        return ['mailto:%s?%s' % (to or '', '&'.join(options_list))]
 
132
 
 
133
 
 
134
class Thunderbird(ExternalMailClient):
 
135
    """Mozilla Thunderbird (or Icedove)
 
136
 
 
137
    Note that Thunderbird 1.5 is buggy and does not support setting
 
138
    "to" simultaneously with including a attachment.
 
139
 
 
140
    There is a workaround if no attachment is present, but we always need to
 
141
    send attachments.
 
142
    """
 
143
 
 
144
    _client_commands = ['thunderbird', 'mozilla-thunderbird', 'icedove']
 
145
 
 
146
    def _get_compose_commandline(self, to, subject, attach_path):
 
147
        message_options = {}
 
148
        if to is not None:
 
149
            message_options['to'] = to
 
150
        if subject is not None:
 
151
            message_options['subject'] = subject
 
152
        if attach_path is not None:
 
153
            message_options['attachment'] = urlutils.local_path_to_url(
 
154
                attach_path)
 
155
        options_list = ["%s='%s'" % (k, v) for k, v in
 
156
                        sorted(message_options.iteritems())]
 
157
        return ['-compose', ','.join(options_list)]
 
158
 
 
159
 
 
160
class XDGEmail(ExternalMailClient):
 
161
    """xdg-email attempts to invoke the user's preferred mail client"""
 
162
 
 
163
    _client_commands = ['xdg-email']
 
164
 
 
165
    def _get_compose_commandline(self, to, subject, attach_path):
 
166
        commandline = [to]
 
167
        if subject is not None:
 
168
            commandline.extend(['--subject', subject])
 
169
        if attach_path is not None:
 
170
            commandline.extend(['--attach', attach_path])
 
171
        return commandline
 
172
 
 
173
 
 
174
class MAPIClient(ExternalMailClient):
 
175
    """Default Windows mail client launched using MAPI."""
 
176
 
 
177
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
 
178
                 extension):
 
179
        simplemapi.SendMail(to or '', subject or '', '', attach_path)
 
180
 
 
181
 
 
182
class DefaultMail(MailClient):
 
183
    """Default mail handling.  Tries XDGEmail (or MAPIClient on Windows),
 
184
    falls back to Editor"""
 
185
 
 
186
    def _mail_client(self):
 
187
        if sys.platform == 'win32':
 
188
            return MAPIClient(self.config)
 
189
        else:
 
190
            return XDGEmail(self.config)
 
191
 
 
192
    def compose(self, prompt, to, subject, attachment, mime_subtype,
 
193
                extension):
 
194
        try:
 
195
            return self._mail_client().compose(prompt, to, subject,
 
196
                                               attachment, mimie_subtype,
 
197
                                               extension)
 
198
        except errors.MailClientNotFound:
 
199
            return Editor(self.config).compose(prompt, to, subject,
 
200
                          attachment, mimie_subtype, extension)
 
201
 
 
202
    def compose_merge_request(self, to, subject, directive):
 
203
        try:
 
204
            return self._mail_client().compose_merge_request(to, subject,
 
205
                                                             directive)
 
206
        except errors.MailClientNotFound:
 
207
            return Editor(self.config).compose_merge_request(to, subject,
 
208
                          directive)