~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/mail_client.py

  • Committer: Vincent Ladeuil
  • Date: 2017-01-30 14:30:10 UTC
  • mfrom: (6615.3.7 merges)
  • mto: This revision was merged to the branch mainline in revision 6621.
  • Revision ID: v.ladeuil+lp@free.fr-20170130143010-p31t1ranfeqbaeki
Merge  2.7 into trunk including fix for bug #1657238

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007 Canonical Ltd
 
1
# Copyright (C) 2007-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
 
17
from __future__ import absolute_import
 
18
 
17
19
import errno
18
20
import os
19
21
import subprocess
20
22
import sys
21
23
import tempfile
22
 
import urllib
23
24
 
24
25
import bzrlib
25
26
from bzrlib import (
 
27
    config as _mod_config,
26
28
    email_message,
27
29
    errors,
28
30
    msgeditor,
89
91
 
90
92
 
91
93
class Editor(MailClient):
92
 
    """DIY mail client that uses commit message editor"""
 
94
    __doc__ = """DIY mail client that uses commit message editor"""
93
95
 
94
96
    supports_body = True
95
97
 
110
112
        if body == '':
111
113
            raise errors.NoMessageSupplied()
112
114
        email_message.EmailMessage.send(self.config,
113
 
                                        self.config.username(),
 
115
                                        self.config.get('email'),
114
116
                                        to,
115
117
                                        subject,
116
118
                                        body,
230
232
 
231
233
 
232
234
class ExternalMailClient(BodyExternalMailClient):
233
 
    """An external mail client."""
 
235
    __doc__ = """An external mail client."""
234
236
 
235
237
    supports_body = False
236
238
 
237
239
 
238
240
class Evolution(BodyExternalMailClient):
239
 
    """Evolution mail client."""
 
241
    __doc__ = """Evolution mail client."""
240
242
 
241
243
    _client_commands = ['evolution']
242
244
 
258
260
 
259
261
 
260
262
class Mutt(BodyExternalMailClient):
261
 
    """Mutt mail client."""
 
263
    __doc__ = """Mutt mail client."""
262
264
 
263
265
    _client_commands = ['mutt']
264
266
 
286
288
 
287
289
 
288
290
class Thunderbird(BodyExternalMailClient):
289
 
    """Mozilla Thunderbird (or Icedove)
 
291
    __doc__ = """Mozilla Thunderbird (or Icedove)
290
292
 
291
293
    Note that Thunderbird 1.5 is buggy and does not support setting
292
294
    "to" simultaneously with including a attachment.
310
312
            message_options['attachment'] = urlutils.local_path_to_url(
311
313
                attach_path)
312
314
        if body is not None:
313
 
            options_list = ['body=%s' % urllib.quote(self._encode_safe(body))]
 
315
            options_list = ['body=%s' % urlutils.quote(self._encode_safe(body))]
314
316
        else:
315
317
            options_list = []
316
318
        options_list.extend(["%s='%s'" % (k, v) for k, v in
321
323
 
322
324
 
323
325
class KMail(ExternalMailClient):
324
 
    """KDE mail client."""
 
326
    __doc__ = """KDE mail client."""
325
327
 
326
328
    _client_commands = ['kmail']
327
329
 
341
343
 
342
344
 
343
345
class Claws(ExternalMailClient):
344
 
    """Claws mail client."""
 
346
    __doc__ = """Claws mail client."""
345
347
 
346
348
    supports_body = True
347
349
 
352
354
        """See ExternalMailClient._get_compose_commandline"""
353
355
        compose_url = []
354
356
        if from_ is not None:
355
 
            compose_url.append('from=' + urllib.quote(from_))
 
357
            compose_url.append('from=' + urlutils.quote(from_))
356
358
        if subject is not None:
357
 
            # Don't use urllib.quote_plus because Claws doesn't seem
 
359
            # Don't use urlutils.quote_plus because Claws doesn't seem
358
360
            # to recognise spaces encoded as "+".
359
361
            compose_url.append(
360
 
                'subject=' + urllib.quote(self._encode_safe(subject)))
 
362
                'subject=' + urlutils.quote(self._encode_safe(subject)))
361
363
        if body is not None:
362
364
            compose_url.append(
363
 
                'body=' + urllib.quote(self._encode_safe(body)))
 
365
                'body=' + urlutils.quote(self._encode_safe(body)))
364
366
        # to must be supplied for the claws-mail --compose syntax to work.
365
367
        if to is None:
366
368
            raise errors.NoMailAddressSpecified()
377
379
                 extension, body=None, from_=None):
378
380
        """See ExternalMailClient._compose"""
379
381
        if from_ is None:
380
 
            from_ = self.config.get_user_option('email')
 
382
            from_ = self.config.get('email')
381
383
        super(Claws, self)._compose(prompt, to, subject, attach_path,
382
384
                                    mime_subtype, extension, body, from_)
383
385
 
387
389
 
388
390
 
389
391
class XDGEmail(BodyExternalMailClient):
390
 
    """xdg-email attempts to invoke the user's preferred mail client"""
 
392
    __doc__ = """xdg-email attempts to invoke the user's preferred mail client"""
391
393
 
392
394
    _client_commands = ['xdg-email']
393
395
 
409
411
 
410
412
 
411
413
class EmacsMail(ExternalMailClient):
412
 
    """Call emacsclient to have a mail buffer.
 
414
    __doc__ = """Call emacsclient to have a mail buffer.
413
415
 
414
416
    This only work for emacs >= 22.1 due to recent -e/--eval support.
415
417
 
424
426
 
425
427
    _client_commands = ['emacsclient']
426
428
 
 
429
    def __init__(self, config):
 
430
        super(EmacsMail, self).__init__(config)
 
431
        self.elisp_tmp_file = None
 
432
 
427
433
    def _prepare_send_function(self):
428
434
        """Write our wrapper function into a temporary file.
429
435
 
500
506
        if attach_path is not None:
501
507
            # Do not create a file if there is no attachment
502
508
            elisp = self._prepare_send_function()
 
509
            self.elisp_tmp_file = elisp
503
510
            lmmform = '(load "%s")' % elisp
504
511
            mmform  = '(bzr-add-mime-att "%s")' % \
505
512
                self._encode_path(attach_path, 'attachment')
514
521
 
515
522
 
516
523
class MAPIClient(BodyExternalMailClient):
517
 
    """Default Windows mail client launched using MAPI."""
 
524
    __doc__ = """Default Windows mail client launched using MAPI."""
518
525
 
519
526
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
520
527
                 extension, body=None):
534
541
                              help=MAPIClient.__doc__)
535
542
 
536
543
 
 
544
class MailApp(BodyExternalMailClient):
 
545
    __doc__ = """Use MacOS X's Mail.app for sending email messages.
 
546
 
 
547
    Although it would be nice to use appscript, it's not installed
 
548
    with the shipped Python installations.  We instead build an
 
549
    AppleScript and invoke the script using osascript(1).  We don't
 
550
    use the _encode_safe() routines as it's not clear what encoding
 
551
    osascript expects the script to be in.
 
552
    """
 
553
 
 
554
    _client_commands = ['osascript']
 
555
 
 
556
    def _get_compose_commandline(self, to, subject, attach_path, body=None,
 
557
                                from_=None):
 
558
       """See ExternalMailClient._get_compose_commandline"""
 
559
 
 
560
       fd, self.temp_file = tempfile.mkstemp(prefix="bzr-send-",
 
561
                                         suffix=".scpt")
 
562
       try:
 
563
           os.write(fd, 'tell application "Mail"\n')
 
564
           os.write(fd, 'set newMessage to make new outgoing message\n')
 
565
           os.write(fd, 'tell newMessage\n')
 
566
           if to is not None:
 
567
               os.write(fd, 'make new to recipient with properties'
 
568
                   ' {address:"%s"}\n' % to)
 
569
           if from_ is not None:
 
570
               # though from_ doesn't actually seem to be used
 
571
               os.write(fd, 'set sender to "%s"\n'
 
572
                   % sender.replace('"', '\\"'))
 
573
           if subject is not None:
 
574
               os.write(fd, 'set subject to "%s"\n'
 
575
                   % subject.replace('"', '\\"'))
 
576
           if body is not None:
 
577
               # FIXME: would be nice to prepend the body to the
 
578
               # existing content (e.g., preserve signature), but
 
579
               # can't seem to figure out the right applescript
 
580
               # incantation.
 
581
               os.write(fd, 'set content to "%s\\n\n"\n' %
 
582
                   body.replace('"', '\\"').replace('\n', '\\n'))
 
583
 
 
584
           if attach_path is not None:
 
585
               # FIXME: would be nice to first append a newline to
 
586
               # ensure the attachment is on a new paragraph, but
 
587
               # can't seem to figure out the right applescript
 
588
               # incantation.
 
589
               os.write(fd, 'tell content to make new attachment'
 
590
                   ' with properties {file name:"%s"}'
 
591
                   ' at after the last paragraph\n'
 
592
                   % self._encode_path(attach_path, 'attachment'))
 
593
           os.write(fd, 'set visible to true\n')
 
594
           os.write(fd, 'end tell\n')
 
595
           os.write(fd, 'end tell\n')
 
596
       finally:
 
597
           os.close(fd) # Just close the handle but do not remove the file.
 
598
       return [self.temp_file]
 
599
mail_client_registry.register('mail.app', MailApp,
 
600
                              help=MailApp.__doc__)
 
601
 
 
602
 
537
603
class DefaultMail(MailClient):
538
 
    """Default mail handling.  Tries XDGEmail (or MAPIClient on Windows),
 
604
    __doc__ = """Default mail handling.  Tries XDGEmail (or MAPIClient on Windows),
539
605
    falls back to Editor"""
540
606
 
541
607
    supports_body = True
552
618
        """See MailClient.compose"""
553
619
        try:
554
620
            return self._mail_client().compose(prompt, to, subject,
555
 
                                               attachment, mimie_subtype,
 
621
                                               attachment, mime_subtype,
556
622
                                               extension, basename, body)
557
623
        except errors.MailClientNotFound:
558
624
            return Editor(self.config).compose(prompt, to, subject,
559
 
                          attachment, mimie_subtype, extension, body)
 
625
                          attachment, mime_subtype, extension, body)
560
626
 
561
627
    def compose_merge_request(self, to, subject, directive, basename=None,
562
628
                              body=None):
570
636
mail_client_registry.register('default', DefaultMail,
571
637
                              help=DefaultMail.__doc__)
572
638
mail_client_registry.default_key = 'default'
 
639
 
 
640
opt_mail_client = _mod_config.RegistryOption('mail_client',
 
641
        mail_client_registry, help='E-mail client to use.', invalid='error')