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