/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
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
2681.3.4 by Lukáš Lalinsky
- Rename 'windows' to 'mapi'
17
import errno
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
18
import os
2681.3.4 by Lukáš Lalinsky
- Rename 'windows' to 'mapi'
19
import subprocess
2681.4.1 by Alexander Belchenko
win32: looking for full path of mail client executable in registry
20
import sys
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
21
import tempfile
22
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
23
import bzrlib
2681.1.9 by Aaron Bentley
Add support for mail-from-editor
24
from bzrlib import (
25
    email_message,
26
    errors,
27
    msgeditor,
2681.3.4 by Lukáš Lalinsky
- Rename 'windows' to 'mapi'
28
    osutils,
2681.1.9 by Aaron Bentley
Add support for mail-from-editor
29
    urlutils,
30
    )
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
31
32
33
class MailClient(object):
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
34
    """A mail client that can send messages with attachements."""
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
35
2681.1.9 by Aaron Bentley
Add support for mail-from-editor
36
    def __init__(self, config):
37
        self.config = config
38
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
39
    def compose(self, prompt, to, subject, attachment, mime_subtype,
40
                extension):
2681.1.36 by Aaron Bentley
Update docs
41
        """Compose (and possibly send) an email message
42
43
        Must be implemented by subclasses.
44
45
        :param prompt: A message to tell the user what to do.  Supported by
46
            the Editor client, but ignored by others
47
        :param to: The address to send the message to
48
        :param subject: The contents of the subject line
49
        :param attachment: An email attachment, as a bytestring
50
        :param mime_subtype: The attachment is assumed to be a subtype of
51
            Text.  This allows the precise subtype to be specified, e.g.
52
            "plain", "x-patch", etc.
53
        :param extension: The file extension associated with the attachment
54
            type, e.g. ".patch"
55
        """
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
56
        raise NotImplementedError
57
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
58
    def compose_merge_request(self, to, subject, directive):
2681.1.36 by Aaron Bentley
Update docs
59
        """Compose (and possibly send) a merge request
60
61
        :param to: The address to send the request to
62
        :param subject: The subject line to use for the request
63
        :param directive: A merge directive representing the merge request, as
64
            a bytestring.
65
        """
2681.1.21 by Aaron Bentley
Refactor prompt generation to make it testable, test it with unicode
66
        prompt = self._get_merge_prompt("Please describe these changes:", to,
67
                                        subject, directive)
68
        self.compose(prompt, to, subject, directive,
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
69
            'x-patch', '.patch')
70
2681.1.21 by Aaron Bentley
Refactor prompt generation to make it testable, test it with unicode
71
    def _get_merge_prompt(self, prompt, to, subject, attachment):
2681.1.36 by Aaron Bentley
Update docs
72
        """Generate a prompt string.  Overridden by Editor.
73
74
        :param prompt: A string suggesting what user should do
75
        :param to: The address the mail will be sent to
76
        :param subject: The subject line of the mail
77
        :param attachment: The attachment that will be used
78
        """
2681.1.21 by Aaron Bentley
Refactor prompt generation to make it testable, test it with unicode
79
        return ''
80
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
81
82
class Editor(MailClient):
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
83
    """DIY mail client that uses commit message editor"""
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
84
2681.1.21 by Aaron Bentley
Refactor prompt generation to make it testable, test it with unicode
85
    def _get_merge_prompt(self, prompt, to, subject, attachment):
2681.1.37 by Aaron Bentley
Update docstrings and string formatting
86
        """See MailClient._get_merge_prompt"""
87
        return (u"%s\n\n"
88
                u"To: %s\n"
89
                u"Subject: %s\n\n"
90
                u"%s" % (prompt, to, subject,
91
                         attachment.decode('utf-8', 'replace')))
2681.1.21 by Aaron Bentley
Refactor prompt generation to make it testable, test it with unicode
92
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
93
    def compose(self, prompt, to, subject, attachment, mime_subtype,
94
                extension):
2681.1.37 by Aaron Bentley
Update docstrings and string formatting
95
        """See MailClient.compose"""
3042.1.1 by Lukáš Lalinský
Make mail-to address in ``bzr send`` optional for interactive mail clients.
96
        if not to:
97
            raise errors.NoMailAddressSpecified()
2681.1.21 by Aaron Bentley
Refactor prompt generation to make it testable, test it with unicode
98
        body = msgeditor.edit_commit_message(prompt)
2681.1.9 by Aaron Bentley
Add support for mail-from-editor
99
        if body == '':
100
            raise errors.NoMessageSupplied()
101
        email_message.EmailMessage.send(self.config,
102
                                        self.config.username(),
103
                                        to,
104
                                        subject,
105
                                        body,
106
                                        attachment,
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
107
                                        attachment_mime_subtype=mime_subtype)
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
108
109
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
110
class ExternalMailClient(MailClient):
111
    """An external mail client."""
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
112
2681.4.1 by Alexander Belchenko
win32: looking for full path of mail client executable in registry
113
    def _get_client_commands(self):
2681.1.36 by Aaron Bentley
Update docs
114
        """Provide a list of commands that may invoke the mail client"""
2681.4.1 by Alexander Belchenko
win32: looking for full path of mail client executable in registry
115
        if sys.platform == 'win32':
2681.1.29 by Aaron Bentley
Make conditional import explicit
116
            import win32utils
2681.4.1 by Alexander Belchenko
win32: looking for full path of mail client executable in registry
117
            return [win32utils.get_app_path(i) for i in self._client_commands]
118
        else:
119
            return self._client_commands
120
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
121
    def compose(self, prompt, to, subject, attachment, mime_subtype,
122
                extension):
2681.1.36 by Aaron Bentley
Update docs
123
        """See MailClient.compose.
124
125
        Writes the attachment to a temporary file, invokes _compose.
126
        """
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
127
        fd, pathname = tempfile.mkstemp(extension, 'bzr-mail-')
128
        try:
129
            os.write(fd, attachment)
130
        finally:
131
            os.close(fd)
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
132
        self._compose(prompt, to, subject, pathname, mime_subtype, extension)
133
134
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
135
                extension):
2681.1.36 by Aaron Bentley
Update docs
136
        """Invoke a mail client as a commandline process.
137
138
        Overridden by MAPIClient.
139
        :param to: The address to send the mail to
140
        :param subject: The subject line for the mail
141
        :param pathname: The path to the attachment
142
        :param mime_subtype: The attachment is assumed to have a major type of
143
            "text", but the precise subtype can be specified here
144
        :param extension: A file extension (including period) associated with
145
            the attachment type.
146
        """
2681.4.1 by Alexander Belchenko
win32: looking for full path of mail client executable in registry
147
        for name in self._get_client_commands():
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
148
            cmdline = [self._encode_path(name, 'executable')]
149
            cmdline.extend(self._get_compose_commandline(to, subject,
150
                                                         attach_path))
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
151
            try:
152
                subprocess.call(cmdline)
153
            except OSError, e:
154
                if e.errno != errno.ENOENT:
155
                    raise
156
            else:
157
                break
158
        else:
159
            raise errors.MailClientNotFound(self._client_commands)
160
161
    def _get_compose_commandline(self, to, subject, attach_path):
2681.1.36 by Aaron Bentley
Update docs
162
        """Determine the commandline to use for composing a message
163
164
        Implemented by various subclasses
165
        :param to: The address to send the mail to
166
        :param subject: The subject line for the mail
167
        :param attach_path: The path to the attachment
168
        """
2681.3.4 by Lukáš Lalinsky
- Rename 'windows' to 'mapi'
169
        raise NotImplementedError
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
170
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
171
    def _encode_safe(self, u):
172
        """Encode possible unicode string argument to 8-bit string
173
        in user_encoding. Unencodable characters will be replaced
174
        with '?'.
175
176
        :param  u:  possible unicode string.
177
        :return:    encoded string if u is unicode, u itself otherwise.
178
        """
179
        if isinstance(u, unicode):
180
            return u.encode(bzrlib.user_encoding, 'replace')
181
        return u
182
183
    def _encode_path(self, path, kind):
184
        """Encode unicode path in user encoding.
185
186
        :param  path:   possible unicode path.
187
        :param  kind:   path kind ('executable' or 'attachment').
188
        :return:        encoded path if path is unicode,
189
                        path itself otherwise.
190
        :raise:         UnableEncodePath.
191
        """
192
        if isinstance(path, unicode):
193
            try:
194
                return path.encode(bzrlib.user_encoding)
195
            except UnicodeEncodeError:
196
                raise errors.UnableEncodePath(path, kind)
197
        return path
3234.2.3 by Alexander Belchenko
mail_client.py: provide new private method ExternalMailClient._get_compose_8bit_commandline to make bug #139318 testable (as Aaron requested).
198
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
199
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
200
class Evolution(ExternalMailClient):
201
    """Evolution mail client."""
202
203
    _client_commands = ['evolution']
204
205
    def _get_compose_commandline(self, to, subject, attach_path):
2681.1.36 by Aaron Bentley
Update docs
206
        """See ExternalMailClient._get_compose_commandline"""
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
207
        message_options = {}
208
        if subject is not None:
209
            message_options['subject'] = subject
210
        if attach_path is not None:
211
            message_options['attach'] = attach_path
212
        options_list = ['%s=%s' % (k, urlutils.escape(v)) for (k, v) in
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
213
                        sorted(message_options.iteritems())]
214
        return ['mailto:%s?%s' % (self._encode_safe(to or ''),
215
            '&'.join(options_list))]
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
216
217
2790.2.1 by Keir Mierle
Add Mutt as a supported client email program. Also rearranges various listings
218
class Mutt(ExternalMailClient):
219
    """Mutt mail client."""
220
221
    _client_commands = ['mutt']
222
223
    def _get_compose_commandline(self, to, subject, attach_path):
224
        """See ExternalMailClient._get_compose_commandline"""
225
        message_options = []
226
        if subject is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
227
            message_options.extend(['-s', self._encode_safe(subject)])
2790.2.1 by Keir Mierle
Add Mutt as a supported client email program. Also rearranges various listings
228
        if attach_path is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
229
            message_options.extend(['-a',
230
                self._encode_path(attach_path, 'attachment')])
2790.2.1 by Keir Mierle
Add Mutt as a supported client email program. Also rearranges various listings
231
        if to is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
232
            message_options.append(self._encode_safe(to))
2790.2.1 by Keir Mierle
Add Mutt as a supported client email program. Also rearranges various listings
233
        return message_options
234
235
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
236
class Thunderbird(ExternalMailClient):
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
237
    """Mozilla Thunderbird (or Icedove)
238
239
    Note that Thunderbird 1.5 is buggy and does not support setting
240
    "to" simultaneously with including a attachment.
241
242
    There is a workaround if no attachment is present, but we always need to
243
    send attachments.
244
    """
245
2681.1.37 by Aaron Bentley
Update docstrings and string formatting
246
    _client_commands = ['thunderbird', 'mozilla-thunderbird', 'icedove',
247
        '/Applications/Mozilla/Thunderbird.app/Contents/MacOS/thunderbird-bin']
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
248
249
    def _get_compose_commandline(self, to, subject, attach_path):
2681.1.36 by Aaron Bentley
Update docs
250
        """See ExternalMailClient._get_compose_commandline"""
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
251
        message_options = {}
252
        if to is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
253
            message_options['to'] = self._encode_safe(to)
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
254
        if subject is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
255
            message_options['subject'] = self._encode_safe(subject)
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
256
        if attach_path is not None:
3234.2.2 by Alexander Belchenko
[merge] URL is always ascii.
257
            message_options['attachment'] = urlutils.local_path_to_url(
258
                attach_path)
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
259
        options_list = ["%s='%s'" % (k, v) for k, v in
260
                        sorted(message_options.iteritems())]
261
        return ['-compose', ','.join(options_list)]
2681.1.23 by Aaron Bentley
Add support for xdg-email
262
263
2681.5.3 by ghigo
Add KMail mail client
264
class KMail(ExternalMailClient):
2681.5.1 by ghigo
Add KMail support to bzr send
265
    """KDE mail client."""
266
267
    _client_commands = ['kmail']
268
269
    def _get_compose_commandline(self, to, subject, attach_path):
2681.1.36 by Aaron Bentley
Update docs
270
        """See ExternalMailClient._get_compose_commandline"""
2681.5.1 by ghigo
Add KMail support to bzr send
271
        message_options = []
272
        if subject is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
273
            message_options.extend(['-s', self._encode_safe(subject)])
2681.5.1 by ghigo
Add KMail support to bzr send
274
        if attach_path is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
275
            message_options.extend(['--attach',
276
                self._encode_path(attach_path, 'attachment')])
2681.5.1 by ghigo
Add KMail support to bzr send
277
        if to is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
278
            message_options.extend([self._encode_safe(to)])
2681.5.1 by ghigo
Add KMail support to bzr send
279
        return message_options
280
281
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
282
class XDGEmail(ExternalMailClient):
2681.1.23 by Aaron Bentley
Add support for xdg-email
283
    """xdg-email attempts to invoke the user's preferred mail client"""
284
285
    _client_commands = ['xdg-email']
286
287
    def _get_compose_commandline(self, to, subject, attach_path):
2681.1.36 by Aaron Bentley
Update docs
288
        """See ExternalMailClient._get_compose_commandline"""
3042.1.1 by Lukáš Lalinský
Make mail-to address in ``bzr send`` optional for interactive mail clients.
289
        if not to:
290
            raise errors.NoMailAddressSpecified()
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
291
        commandline = [self._encode_safe(to)]
2681.1.23 by Aaron Bentley
Add support for xdg-email
292
        if subject is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
293
            commandline.extend(['--subject', self._encode_safe(subject)])
2681.1.23 by Aaron Bentley
Add support for xdg-email
294
        if attach_path is not None:
3234.2.6 by Alexander Belchenko
because every mail client has different rules to compose command line we should encode arguments to 8 bit string only when needed.
295
            commandline.extend(['--attach',
296
                self._encode_path(attach_path, 'attachment')])
2681.1.23 by Aaron Bentley
Add support for xdg-email
297
        return commandline
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
298
299
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
300
class MAPIClient(ExternalMailClient):
301
    """Default Windows mail client launched using MAPI."""
302
303
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
304
                 extension):
2681.1.36 by Aaron Bentley
Update docs
305
        """See ExternalMailClient._compose.
306
307
        This implementation uses MAPI via the simplemapi ctypes wrapper
308
        """
2681.3.4 by Lukáš Lalinsky
- Rename 'windows' to 'mapi'
309
        from bzrlib.util import simplemapi
310
        try:
311
            simplemapi.SendMail(to or '', subject or '', '', attach_path)
2681.3.6 by Lukáš Lalinsky
New version of simplemapi.py with MIT license.
312
        except simplemapi.MAPIError, e:
313
            if e.code != simplemapi.MAPI_USER_ABORT:
314
                raise errors.MailClientNotFound(['MAPI supported mail client'
315
                                                 ' (error %d)' % (e.code,)])
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
316
317
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
318
class DefaultMail(MailClient):
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
319
    """Default mail handling.  Tries XDGEmail (or MAPIClient on Windows),
320
    falls back to Editor"""
321
322
    def _mail_client(self):
2681.1.36 by Aaron Bentley
Update docs
323
        """Determine the preferred mail client for this platform"""
2681.3.4 by Lukáš Lalinsky
- Rename 'windows' to 'mapi'
324
        if osutils.supports_mapi():
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
325
            return MAPIClient(self.config)
326
        else:
327
            return XDGEmail(self.config)
2681.1.25 by Aaron Bentley
Cleanup
328
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
329
    def compose(self, prompt, to, subject, attachment, mime_subtype,
330
                extension):
2681.1.36 by Aaron Bentley
Update docs
331
        """See MailClient.compose"""
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
332
        try:
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
333
            return self._mail_client().compose(prompt, to, subject,
334
                                               attachment, mimie_subtype,
335
                                               extension)
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
336
        except errors.MailClientNotFound:
337
            return Editor(self.config).compose(prompt, to, subject,
338
                          attachment, mimie_subtype, extension)
339
340
    def compose_merge_request(self, to, subject, directive):
2681.1.36 by Aaron Bentley
Update docs
341
        """See MailClient.compose_merge_request"""
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
342
        try:
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
343
            return self._mail_client().compose_merge_request(to, subject,
344
                                                             directive)
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
345
        except errors.MailClientNotFound:
346
            return Editor(self.config).compose_merge_request(to, subject,
347
                          directive)