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