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