~bzr-pqm/bzr/bzr.dev

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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
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
3921.2.1 by Gavin Panella
Support Claws.
22
import urllib
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
23
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.
24
import bzrlib
2681.1.9 by Aaron Bentley
Add support for mail-from-editor
25
from bzrlib import (
26
    email_message,
27
    errors,
28
    msgeditor,
2681.3.4 by Lukáš Lalinsky
- Rename 'windows' to 'mapi'
29
    osutils,
2681.1.9 by Aaron Bentley
Add support for mail-from-editor
30
    urlutils,
3638.2.1 by Neil Martinsen-Burrell
Use a registry for mail clients.
31
    registry
2681.1.9 by Aaron Bentley
Add support for mail-from-editor
32
    )
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
33
3638.2.1 by Neil Martinsen-Burrell
Use a registry for mail clients.
34
mail_client_registry = registry.Registry()
35
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
36
37
class MailClient(object):
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
38
    """A mail client that can send messages with attachements."""
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
39
2681.1.9 by Aaron Bentley
Add support for mail-from-editor
40
    def __init__(self, config):
41
        self.config = config
42
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
43
    def compose(self, prompt, to, subject, attachment, mime_subtype,
4098.5.2 by Aaron Bentley
Push body support through bzr send.
44
                extension, basename=None, body=None):
2681.1.36 by Aaron Bentley
Update docs
45
        """Compose (and possibly send) an email message
46
47
        Must be implemented by subclasses.
48
49
        :param prompt: A message to tell the user what to do.  Supported by
50
            the Editor client, but ignored by others
51
        :param to: The address to send the message to
52
        :param subject: The contents of the subject line
53
        :param attachment: An email attachment, as a bytestring
54
        :param mime_subtype: The attachment is assumed to be a subtype of
55
            Text.  This allows the precise subtype to be specified, e.g.
56
            "plain", "x-patch", etc.
57
        :param extension: The file extension associated with the attachment
58
            type, e.g. ".patch"
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
59
        :param basename: The name to use for the attachment, e.g.
60
            "send-nick-3252"
2681.1.36 by Aaron Bentley
Update docs
61
        """
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
62
        raise NotImplementedError
63
4098.5.2 by Aaron Bentley
Push body support through bzr send.
64
    def compose_merge_request(self, to, subject, directive, basename=None,
65
                              body=None):
2681.1.36 by Aaron Bentley
Update docs
66
        """Compose (and possibly send) a merge request
67
68
        :param to: The address to send the request to
69
        :param subject: The subject line to use for the request
70
        :param directive: A merge directive representing the merge request, as
71
            a bytestring.
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
72
        :param basename: The name to use for the attachment, e.g.
73
            "send-nick-3252"
2681.1.36 by Aaron Bentley
Update docs
74
        """
2681.1.21 by Aaron Bentley
Refactor prompt generation to make it testable, test it with unicode
75
        prompt = self._get_merge_prompt("Please describe these changes:", to,
76
                                        subject, directive)
77
        self.compose(prompt, to, subject, directive,
4098.5.2 by Aaron Bentley
Push body support through bzr send.
78
            'x-patch', '.patch', basename, body)
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
79
2681.1.21 by Aaron Bentley
Refactor prompt generation to make it testable, test it with unicode
80
    def _get_merge_prompt(self, prompt, to, subject, attachment):
2681.1.36 by Aaron Bentley
Update docs
81
        """Generate a prompt string.  Overridden by Editor.
82
83
        :param prompt: A string suggesting what user should do
84
        :param to: The address the mail will be sent to
85
        :param subject: The subject line of the mail
86
        :param attachment: The attachment that will be used
87
        """
2681.1.21 by Aaron Bentley
Refactor prompt generation to make it testable, test it with unicode
88
        return ''
89
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
90
91
class Editor(MailClient):
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
92
    """DIY mail client that uses commit message editor"""
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
93
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
94
    supports_body = True
95
2681.1.21 by Aaron Bentley
Refactor prompt generation to make it testable, test it with unicode
96
    def _get_merge_prompt(self, prompt, to, subject, attachment):
2681.1.37 by Aaron Bentley
Update docstrings and string formatting
97
        """See MailClient._get_merge_prompt"""
98
        return (u"%s\n\n"
99
                u"To: %s\n"
100
                u"Subject: %s\n\n"
101
                u"%s" % (prompt, to, subject,
102
                         attachment.decode('utf-8', 'replace')))
2681.1.21 by Aaron Bentley
Refactor prompt generation to make it testable, test it with unicode
103
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
104
    def compose(self, prompt, to, subject, attachment, mime_subtype,
4098.5.2 by Aaron Bentley
Push body support through bzr send.
105
                extension, basename=None, body=None):
2681.1.37 by Aaron Bentley
Update docstrings and string formatting
106
        """See MailClient.compose"""
3042.1.1 by Lukáš Lalinský
Make mail-to address in ``bzr send`` optional for interactive mail clients.
107
        if not to:
108
            raise errors.NoMailAddressSpecified()
4098.5.2 by Aaron Bentley
Push body support through bzr send.
109
        body = msgeditor.edit_commit_message(prompt, start_message=body)
2681.1.9 by Aaron Bentley
Add support for mail-from-editor
110
        if body == '':
111
            raise errors.NoMessageSupplied()
112
        email_message.EmailMessage.send(self.config,
113
                                        self.config.username(),
114
                                        to,
115
                                        subject,
116
                                        body,
117
                                        attachment,
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
118
                                        attachment_mime_subtype=mime_subtype)
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
119
mail_client_registry.register('editor', Editor,
120
                              help=Editor.__doc__)
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
121
122
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
123
class BodyExternalMailClient(MailClient):
124
125
    supports_body = True
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
126
2681.4.1 by Alexander Belchenko
win32: looking for full path of mail client executable in registry
127
    def _get_client_commands(self):
2681.1.36 by Aaron Bentley
Update docs
128
        """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
129
        if sys.platform == 'win32':
2681.1.29 by Aaron Bentley
Make conditional import explicit
130
            import win32utils
2681.4.1 by Alexander Belchenko
win32: looking for full path of mail client executable in registry
131
            return [win32utils.get_app_path(i) for i in self._client_commands]
132
        else:
133
            return self._client_commands
134
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
135
    def compose(self, prompt, to, subject, attachment, mime_subtype,
4098.5.2 by Aaron Bentley
Push body support through bzr send.
136
                extension, basename=None, body=None):
2681.1.36 by Aaron Bentley
Update docs
137
        """See MailClient.compose.
138
139
        Writes the attachment to a temporary file, invokes _compose.
140
        """
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
141
        if basename is None:
142
            basename = 'attachment'
3638.3.2 by Vincent Ladeuil
Fix all calls to tempfile.mkdtemp to osutils.mkdtemp.
143
        pathname = osutils.mkdtemp(prefix='bzr-mail-')
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
144
        attach_path = osutils.pathjoin(pathname, basename + extension)
145
        outfile = open(attach_path, 'wb')
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
146
        try:
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
147
            outfile.write(attachment)
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
148
        finally:
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
149
            outfile.close()
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
150
        if body is not None:
151
            kwargs = {'body': body}
152
        else:
153
            kwargs = {}
3251.2.1 by Aaron Bentley
Use nick/revno-based names for merge directives
154
        self._compose(prompt, to, subject, attach_path, mime_subtype,
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
155
                      extension, **kwargs)
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
156
157
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
4098.5.6 by Aaron Bentley
Add body=None to support old mail clients better.
158
                extension, body=None):
2681.1.36 by Aaron Bentley
Update docs
159
        """Invoke a mail client as a commandline process.
160
161
        Overridden by MAPIClient.
162
        :param to: The address to send the mail to
163
        :param subject: The subject line for the mail
164
        :param pathname: The path to the attachment
165
        :param mime_subtype: The attachment is assumed to have a major type of
166
            "text", but the precise subtype can be specified here
167
        :param extension: A file extension (including period) associated with
168
            the attachment type.
169
        """
2681.4.1 by Alexander Belchenko
win32: looking for full path of mail client executable in registry
170
        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.
171
            cmdline = [self._encode_path(name, 'executable')]
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
172
            if body is not None:
173
                kwargs = {'body': body}
174
            else:
175
                kwargs = {}
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.
176
            cmdline.extend(self._get_compose_commandline(to, subject,
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
177
                                                         attach_path,
178
                                                         **kwargs))
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
179
            try:
180
                subprocess.call(cmdline)
181
            except OSError, e:
182
                if e.errno != errno.ENOENT:
183
                    raise
184
            else:
185
                break
186
        else:
187
            raise errors.MailClientNotFound(self._client_commands)
188
4098.5.2 by Aaron Bentley
Push body support through bzr send.
189
    def _get_compose_commandline(self, to, subject, attach_path, body):
2681.1.36 by Aaron Bentley
Update docs
190
        """Determine the commandline to use for composing a message
191
192
        Implemented by various subclasses
193
        :param to: The address to send the mail to
194
        :param subject: The subject line for the mail
195
        :param attach_path: The path to the attachment
196
        """
2681.3.4 by Lukáš Lalinsky
- Rename 'windows' to 'mapi'
197
        raise NotImplementedError
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
198
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.
199
    def _encode_safe(self, u):
200
        """Encode possible unicode string argument to 8-bit string
201
        in user_encoding. Unencodable characters will be replaced
202
        with '?'.
203
204
        :param  u:  possible unicode string.
205
        :return:    encoded string if u is unicode, u itself otherwise.
206
        """
207
        if isinstance(u, unicode):
3224.5.8 by Andrew Bennetts
Fix failing tests.
208
            return u.encode(osutils.get_user_encoding(), 'replace')
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.
209
        return u
210
211
    def _encode_path(self, path, kind):
212
        """Encode unicode path in user encoding.
213
214
        :param  path:   possible unicode path.
215
        :param  kind:   path kind ('executable' or 'attachment').
216
        :return:        encoded path if path is unicode,
217
                        path itself otherwise.
218
        :raise:         UnableEncodePath.
219
        """
220
        if isinstance(path, unicode):
221
            try:
3224.5.8 by Andrew Bennetts
Fix failing tests.
222
                return path.encode(osutils.get_user_encoding())
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.
223
            except UnicodeEncodeError:
224
                raise errors.UnableEncodePath(path, kind)
225
        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).
226
2681.1.18 by Aaron Bentley
Refactor to increase code sharing, allow multiple command names for tbird
227
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
228
class ExternalMailClient(BodyExternalMailClient):
229
    """An external mail client."""
230
231
    supports_body = False
232
233
234
class Evolution(BodyExternalMailClient):
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
235
    """Evolution mail client."""
236
237
    _client_commands = ['evolution']
238
4098.5.1 by Aaron Bentley
Allow specifying body for t-bird, evo and xdg
239
    def _get_compose_commandline(self, to, subject, attach_path, body=None):
2681.1.36 by Aaron Bentley
Update docs
240
        """See ExternalMailClient._get_compose_commandline"""
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
241
        message_options = {}
242
        if subject is not None:
243
            message_options['subject'] = subject
244
        if attach_path is not None:
245
            message_options['attach'] = attach_path
4098.5.1 by Aaron Bentley
Allow specifying body for t-bird, evo and xdg
246
        if body is not None:
247
            message_options['body'] = body
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
248
        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.
249
                        sorted(message_options.iteritems())]
250
        return ['mailto:%s?%s' % (self._encode_safe(to or ''),
251
            '&'.join(options_list))]
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
252
mail_client_registry.register('evolution', Evolution,
253
                              help=Evolution.__doc__)
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
254
255
2790.2.1 by Keir Mierle
Add Mutt as a supported client email program. Also rearranges various listings
256
class Mutt(ExternalMailClient):
257
    """Mutt mail client."""
258
259
    _client_commands = ['mutt']
260
261
    def _get_compose_commandline(self, to, subject, attach_path):
262
        """See ExternalMailClient._get_compose_commandline"""
263
        message_options = []
264
        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.
265
            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
266
        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.
267
            message_options.extend(['-a',
268
                self._encode_path(attach_path, 'attachment')])
2790.2.1 by Keir Mierle
Add Mutt as a supported client email program. Also rearranges various listings
269
        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.
270
            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
271
        return message_options
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
272
mail_client_registry.register('mutt', Mutt,
273
                              help=Mutt.__doc__)
2790.2.1 by Keir Mierle
Add Mutt as a supported client email program. Also rearranges various listings
274
275
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
276
class Thunderbird(BodyExternalMailClient):
2681.1.11 by Aaron Bentley
Add docstrings, add compose_merge_request
277
    """Mozilla Thunderbird (or Icedove)
278
279
    Note that Thunderbird 1.5 is buggy and does not support setting
280
    "to" simultaneously with including a attachment.
281
282
    There is a workaround if no attachment is present, but we always need to
283
    send attachments.
284
    """
285
2681.1.37 by Aaron Bentley
Update docstrings and string formatting
286
    _client_commands = ['thunderbird', 'mozilla-thunderbird', 'icedove',
3638.2.1 by Neil Martinsen-Burrell
Use a registry for mail clients.
287
        '/Applications/Mozilla/Thunderbird.app/Contents/MacOS/thunderbird-bin',
288
        '/Applications/Thunderbird.app/Contents/MacOS/thunderbird-bin']
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
289
4098.5.1 by Aaron Bentley
Allow specifying body for t-bird, evo and xdg
290
    def _get_compose_commandline(self, to, subject, attach_path, body=None):
2681.1.36 by Aaron Bentley
Update docs
291
        """See ExternalMailClient._get_compose_commandline"""
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
292
        message_options = {}
293
        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.
294
            message_options['to'] = self._encode_safe(to)
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
295
        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.
296
            message_options['subject'] = self._encode_safe(subject)
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
297
        if attach_path is not None:
3234.2.2 by Alexander Belchenko
[merge] URL is always ascii.
298
            message_options['attachment'] = urlutils.local_path_to_url(
299
                attach_path)
4098.5.1 by Aaron Bentley
Allow specifying body for t-bird, evo and xdg
300
        if body is not None:
301
            options_list = ['body=%s' % urllib.quote(self._encode_safe(body))]
302
        else:
303
            options_list = []
304
        options_list.extend(["%s='%s'" % (k, v) for k, v in
305
                        sorted(message_options.iteritems())])
2681.1.8 by Aaron Bentley
Add Thunderbird support to bzr send
306
        return ['-compose', ','.join(options_list)]
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
307
mail_client_registry.register('thunderbird', Thunderbird,
308
                              help=Thunderbird.__doc__)
2681.1.23 by Aaron Bentley
Add support for xdg-email
309
310
2681.5.3 by ghigo
Add KMail mail client
311
class KMail(ExternalMailClient):
2681.5.1 by ghigo
Add KMail support to bzr send
312
    """KDE mail client."""
313
314
    _client_commands = ['kmail']
315
316
    def _get_compose_commandline(self, to, subject, attach_path):
2681.1.36 by Aaron Bentley
Update docs
317
        """See ExternalMailClient._get_compose_commandline"""
2681.5.1 by ghigo
Add KMail support to bzr send
318
        message_options = []
319
        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.
320
            message_options.extend(['-s', self._encode_safe(subject)])
2681.5.1 by ghigo
Add KMail support to bzr send
321
        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.
322
            message_options.extend(['--attach',
323
                self._encode_path(attach_path, 'attachment')])
2681.5.1 by ghigo
Add KMail support to bzr send
324
        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.
325
            message_options.extend([self._encode_safe(to)])
2681.5.1 by ghigo
Add KMail support to bzr send
326
        return message_options
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
327
mail_client_registry.register('kmail', KMail,
328
                              help=KMail.__doc__)
2681.5.1 by ghigo
Add KMail support to bzr send
329
330
3921.2.1 by Gavin Panella
Support Claws.
331
class Claws(ExternalMailClient):
332
    """Claws mail client."""
333
334
    _client_commands = ['claws-mail']
335
336
    def _get_compose_commandline(self, to, subject, attach_path):
337
        """See ExternalMailClient._get_compose_commandline"""
3921.2.4 by Gavin Panella
Use the --attach option, and don't specify a From: header.
338
        compose_url = ['mailto:']
339
        if to is not None:
340
            compose_url.append(self._encode_safe(to))
341
        compose_url.append('?')
3921.2.1 by Gavin Panella
Support Claws.
342
        if subject is not None:
3921.2.4 by Gavin Panella
Use the --attach option, and don't specify a From: header.
343
            # Don't use urllib.quote_plus because Claws doesn't seem
344
            # to recognise spaces encoded as "+".
345
            compose_url.append(
346
                'subject=%s' % urllib.quote(self._encode_safe(subject)))
347
        # Collect command-line options.
348
        message_options = ['--compose', ''.join(compose_url)]
3921.2.1 by Gavin Panella
Support Claws.
349
        if attach_path is not None:
3921.2.4 by Gavin Panella
Use the --attach option, and don't specify a From: header.
350
            message_options.extend(
351
                ['--attach', self._encode_path(attach_path, 'attachment')])
352
        return message_options
3921.2.1 by Gavin Panella
Support Claws.
353
mail_client_registry.register('claws', Claws,
354
                              help=Claws.__doc__)
355
356
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
357
class XDGEmail(BodyExternalMailClient):
2681.1.23 by Aaron Bentley
Add support for xdg-email
358
    """xdg-email attempts to invoke the user's preferred mail client"""
359
360
    _client_commands = ['xdg-email']
361
4098.5.1 by Aaron Bentley
Allow specifying body for t-bird, evo and xdg
362
    def _get_compose_commandline(self, to, subject, attach_path, body=None):
2681.1.36 by Aaron Bentley
Update docs
363
        """See ExternalMailClient._get_compose_commandline"""
3042.1.1 by Lukáš Lalinský
Make mail-to address in ``bzr send`` optional for interactive mail clients.
364
        if not to:
365
            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.
366
        commandline = [self._encode_safe(to)]
2681.1.23 by Aaron Bentley
Add support for xdg-email
367
        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.
368
            commandline.extend(['--subject', self._encode_safe(subject)])
2681.1.23 by Aaron Bentley
Add support for xdg-email
369
        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.
370
            commandline.extend(['--attach',
371
                self._encode_path(attach_path, 'attachment')])
4098.5.1 by Aaron Bentley
Allow specifying body for t-bird, evo and xdg
372
        if body is not None:
373
            commandline.extend(['--body', self._encode_safe(body)])
2681.1.23 by Aaron Bentley
Add support for xdg-email
374
        return commandline
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
375
mail_client_registry.register('xdg-email', XDGEmail,
376
                              help=XDGEmail.__doc__)
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
377
378
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
379
class EmacsMail(ExternalMailClient):
380
    """Call emacsclient to have a mail buffer.
381
382
    This only work for emacs >= 22.1 due to recent -e/--eval support.
383
384
    The good news is that this implementation will work with all mail
385
    agents registered against ``mail-user-agent``. So there is no need
386
    to instantiate ExternalMailClient for each and every GNU Emacs
387
    MUA.
388
389
    Users just have to ensure that ``mail-user-agent`` is set according
390
    to their tastes.
3322.1.1 by Ian Clatworthy
Add mail-mode GNU Emacs mail package as a mail client option (Xavier Maillard)
391
    """
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
392
3302.6.1 by Xavier Maillard
Add mail-mode GNU Emacs mail package as a mail_client option.
393
    _client_commands = ['emacsclient']
394
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
395
    def _prepare_send_function(self):
396
        """Write our wrapper function into a temporary file.
397
398
        This temporary file will be loaded at runtime in
399
        _get_compose_commandline function.
400
3506.1.6 by Christophe Troestler
EmacsMail: _prepare_send_function: corrected doc and proper handling of
401
        This function does not remove the file.  That's a wanted
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
402
        behaviour since _get_compose_commandline won't run the send
403
        mail function directly but return the eligible command line.
404
        Removing our temporary file here would prevent our sendmail
3506.1.6 by Christophe Troestler
EmacsMail: _prepare_send_function: corrected doc and proper handling of
405
        function to work.  (The file is deleted by some elisp code
406
        after being read by Emacs.)
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
407
        """
408
409
        _defun = r"""(defun bzr-add-mime-att (file)
3506.1.1 by Christophe Troestler
Handled the MUA "mew" in the class EmacsMail.
410
  "Attach FILE to a mail buffer as a MIME attachment."
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
411
  (let ((agent mail-user-agent))
412
    (if (and file (file-exists-p file))
413
        (cond
414
         ((eq agent 'sendmail-user-agent)
3506.1.1 by Christophe Troestler
Handled the MUA "mew" in the class EmacsMail.
415
          (progn
3506.1.10 by Christophe Troestler
Removed TABS in mail_client.py and added a NEWS entry.
416
            (mail-text)
417
            (newline)
418
            (if (functionp 'etach-attach)
419
              (etach-attach file)
420
              (mail-attach-file file))))
4056.3.1 by epg at pretzelnet
Support MH-E in EmacsMail, using mml.
421
         ((or (eq agent 'message-user-agent)
422
              (eq agent 'gnus-user-agent)
423
              (eq agent 'mh-e-user-agent))
3506.1.1 by Christophe Troestler
Handled the MUA "mew" in the class EmacsMail.
424
          (progn
3506.1.10 by Christophe Troestler
Removed TABS in mail_client.py and added a NEWS entry.
425
            (mml-attach-file file "text/x-patch" "BZR merge" "inline")))
426
         ((eq agent 'mew-user-agent)
427
          (progn
428
            (mew-draft-prepare-attachments)
429
            (mew-attach-link file (file-name-nondirectory file))
430
            (let* ((nums (mew-syntax-nums))
431
                   (syntax (mew-syntax-get-entry mew-encode-syntax nums)))
432
              (mew-syntax-set-cd syntax "BZR merge")
433
              (mew-encode-syntax-print mew-encode-syntax))
434
            (mew-header-goto-body)))
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
435
         (t
3506.1.8 by Christophe Troestler
When the Emacs MUA is not supported, the error message encourage to report it.
436
          (message "Unhandled MUA, report it on bazaar@lists.canonical.com")))
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
437
      (error "File %s does not exist." file))))
438
"""
439
440
        fd, temp_file = tempfile.mkstemp(prefix="emacs-bzr-send-",
441
                                         suffix=".el")
442
        try:
443
            os.write(fd, _defun)
444
        finally:
445
            os.close(fd) # Just close the handle but do not remove the file.
446
        return temp_file
447
3322.1.1 by Ian Clatworthy
Add mail-mode GNU Emacs mail package as a mail client option (Xavier Maillard)
448
    def _get_compose_commandline(self, to, subject, attach_path):
3302.6.1 by Xavier Maillard
Add mail-mode GNU Emacs mail package as a mail_client option.
449
        commandline = ["--eval"]
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
450
3302.6.1 by Xavier Maillard
Add mail-mode GNU Emacs mail package as a mail_client option.
451
        _to = "nil"
452
        _subject = "nil"
453
454
        if to is not None:
3506.1.3 by Christophe Troestler
Better escaping of To and Subject in the class EmacsMail.
455
            _to = ("\"%s\"" % self._encode_safe(to).replace('"', '\\"'))
3302.6.1 by Xavier Maillard
Add mail-mode GNU Emacs mail package as a mail_client option.
456
        if subject is not None:
3506.1.3 by Christophe Troestler
Better escaping of To and Subject in the class EmacsMail.
457
            _subject = ("\"%s\"" %
458
                        self._encode_safe(subject).replace('"', '\\"'))
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
459
460
        # Funcall the default mail composition function
461
        # This will work with any mail mode including default mail-mode
462
        # User must tweak mail-user-agent variable to tell what function
463
        # will be called inside compose-mail.
464
        mail_cmd = "(compose-mail %s %s)" % (_to, _subject)
465
        commandline.append(mail_cmd)
466
467
        # Try to attach a MIME attachment using our wrapper function
3302.6.1 by Xavier Maillard
Add mail-mode GNU Emacs mail package as a mail_client option.
468
        if attach_path is not None:
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
469
            # Do not create a file if there is no attachment
3506.1.4 by Christophe Troestler
Remove the temporary elisp file created for attachments by EmacsMail.
470
            elisp = self._prepare_send_function()
471
            lmmform = '(load "%s")' % elisp
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
472
            mmform  = '(bzr-add-mime-att "%s")' % \
473
                self._encode_path(attach_path, 'attachment')
3506.1.4 by Christophe Troestler
Remove the temporary elisp file created for attachments by EmacsMail.
474
            rmform = '(delete-file "%s")' % elisp
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
475
            commandline.append(lmmform)
476
            commandline.append(mmform)
3506.1.4 by Christophe Troestler
Remove the temporary elisp file created for attachments by EmacsMail.
477
            commandline.append(rmform)
3324.4.1 by Xavier Maillard
Replace mail-mode call with compose-mail from GNU Emacs.
478
3302.6.1 by Xavier Maillard
Add mail-mode GNU Emacs mail package as a mail_client option.
479
        return commandline
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
480
mail_client_registry.register('emacsclient', EmacsMail,
481
                              help=EmacsMail.__doc__)
3302.6.1 by Xavier Maillard
Add mail-mode GNU Emacs mail package as a mail_client option.
482
483
4098.5.4 by Aaron Bentley
Cleaner support for mail clients lacking body support.
484
class MAPIClient(BodyExternalMailClient):
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
485
    """Default Windows mail client launched using MAPI."""
486
487
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
4098.5.3 by Aaron Bentley
Add Windows MAPI support for send --body
488
                 extension, body):
2681.1.36 by Aaron Bentley
Update docs
489
        """See ExternalMailClient._compose.
490
491
        This implementation uses MAPI via the simplemapi ctypes wrapper
492
        """
2681.3.4 by Lukáš Lalinsky
- Rename 'windows' to 'mapi'
493
        from bzrlib.util import simplemapi
494
        try:
4098.5.3 by Aaron Bentley
Add Windows MAPI support for send --body
495
            simplemapi.SendMail(to or '', subject or '', body or '',
496
                                attach_path)
2681.3.6 by Lukáš Lalinsky
New version of simplemapi.py with MIT license.
497
        except simplemapi.MAPIError, e:
498
            if e.code != simplemapi.MAPI_USER_ABORT:
499
                raise errors.MailClientNotFound(['MAPI supported mail client'
500
                                                 ' (error %d)' % (e.code,)])
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
501
mail_client_registry.register('mapi', MAPIClient,
502
                              help=MAPIClient.__doc__)
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
503
504
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
505
class DefaultMail(MailClient):
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
506
    """Default mail handling.  Tries XDGEmail (or MAPIClient on Windows),
507
    falls back to Editor"""
508
509
    def _mail_client(self):
2681.1.36 by Aaron Bentley
Update docs
510
        """Determine the preferred mail client for this platform"""
2681.3.4 by Lukáš Lalinsky
- Rename 'windows' to 'mapi'
511
        if osutils.supports_mapi():
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
512
            return MAPIClient(self.config)
513
        else:
514
            return XDGEmail(self.config)
2681.1.25 by Aaron Bentley
Cleanup
515
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
516
    def compose(self, prompt, to, subject, attachment, mime_subtype,
4098.5.2 by Aaron Bentley
Push body support through bzr send.
517
                extension, basename=None, body=None):
2681.1.36 by Aaron Bentley
Update docs
518
        """See MailClient.compose"""
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
519
        try:
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
520
            return self._mail_client().compose(prompt, to, subject,
521
                                               attachment, mimie_subtype,
4098.5.2 by Aaron Bentley
Push body support through bzr send.
522
                                               extension, basename, body)
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
523
        except errors.MailClientNotFound:
524
            return Editor(self.config).compose(prompt, to, subject,
4098.5.2 by Aaron Bentley
Push body support through bzr send.
525
                          attachment, mimie_subtype, extension, body)
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
526
4098.5.2 by Aaron Bentley
Push body support through bzr send.
527
    def compose_merge_request(self, to, subject, directive, basename=None,
528
                              body=None):
2681.1.36 by Aaron Bentley
Update docs
529
        """See MailClient.compose_merge_request"""
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
530
        try:
2681.3.1 by Lukáš Lalinsky
Support for sending bundles using MAPI on Windows.
531
            return self._mail_client().compose_merge_request(to, subject,
4098.5.2 by Aaron Bentley
Push body support through bzr send.
532
                    directive, basename=basename, body=body)
2681.1.24 by Aaron Bentley
Handle default mail client by trying xdg-email, falling back to editor
533
        except errors.MailClientNotFound:
534
            return Editor(self.config).compose_merge_request(to, subject,
4098.5.2 by Aaron Bentley
Push body support through bzr send.
535
                          directive, basename=basename, body=body)
3638.2.5 by Neil Martinsen-Burrell
use docstrings as help messages for registered mail clients
536
mail_client_registry.register('default', DefaultMail,
537
                              help=DefaultMail.__doc__)
3638.2.2 by Neil Martinsen-Burrell
put registry information with each class
538
mail_client_registry.default_key = 'default'