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