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