/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/mail_client.py

  • Committer: Jelmer Vernooij
  • Date: 2018-11-11 04:08:32 UTC
  • mto: (7143.16.20 even-more-cleanups)
  • mto: This revision was merged to the branch mainline in revision 7175.
  • Revision ID: jelmer@jelmer.uk-20181111040832-nsljjynzzwmznf3h
Run autopep8.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
import bzrlib
25
 
from bzrlib import (
 
25
import breezy
 
26
from . import (
 
27
    config as _mod_config,
26
28
    email_message,
27
29
    errors,
28
30
    msgeditor,
29
31
    osutils,
30
32
    urlutils,
31
 
    registry
 
33
    registry,
 
34
    )
 
35
from .sixish import (
 
36
    PY3,
 
37
    text_type,
32
38
    )
33
39
 
34
40
mail_client_registry = registry.Registry()
35
41
 
36
42
 
 
43
class MailClientNotFound(errors.BzrError):
 
44
 
 
45
    _fmt = "Unable to find mail client with the following names:"\
 
46
        " %(mail_command_list_string)s"
 
47
 
 
48
    def __init__(self, mail_command_list):
 
49
        mail_command_list_string = ', '.join(mail_command_list)
 
50
        errors.BzrError.__init__(
 
51
            self, mail_command_list=mail_command_list,
 
52
            mail_command_list_string=mail_command_list_string)
 
53
 
 
54
 
 
55
class NoMessageSupplied(errors.BzrError):
 
56
 
 
57
    _fmt = "No message supplied."
 
58
 
 
59
 
 
60
class NoMailAddressSpecified(errors.BzrError):
 
61
 
 
62
    _fmt = "No mail-to address (--mail-to) or output (-o) specified."
 
63
 
 
64
 
37
65
class MailClient(object):
38
66
    """A mail client that can send messages with attachements."""
39
67
 
75
103
        prompt = self._get_merge_prompt("Please describe these changes:", to,
76
104
                                        subject, directive)
77
105
        self.compose(prompt, to, subject, directive,
78
 
            'x-patch', '.patch', basename, body)
 
106
                     'x-patch', '.patch', basename, body)
79
107
 
80
108
    def _get_merge_prompt(self, prompt, to, subject, attachment):
81
109
        """Generate a prompt string.  Overridden by Editor.
105
133
                extension, basename=None, body=None):
106
134
        """See MailClient.compose"""
107
135
        if not to:
108
 
            raise errors.NoMailAddressSpecified()
 
136
            raise NoMailAddressSpecified()
109
137
        body = msgeditor.edit_commit_message(prompt, start_message=body)
110
138
        if body == '':
111
 
            raise errors.NoMessageSupplied()
 
139
            raise NoMessageSupplied()
112
140
        email_message.EmailMessage.send(self.config,
113
 
                                        self.config.username(),
 
141
                                        self.config.get('email'),
114
142
                                        to,
115
143
                                        subject,
116
144
                                        body,
117
145
                                        attachment,
118
146
                                        attachment_mime_subtype=mime_subtype)
 
147
 
 
148
 
119
149
mail_client_registry.register('editor', Editor,
120
150
                              help=Editor.__doc__)
121
151
 
182
212
                                                         **kwargs))
183
213
            try:
184
214
                subprocess.call(cmdline)
185
 
            except OSError, e:
 
215
            except OSError as e:
186
216
                if e.errno != errno.ENOENT:
187
217
                    raise
188
218
            else:
189
219
                break
190
220
        else:
191
 
            raise errors.MailClientNotFound(self._client_commands)
 
221
            raise MailClientNotFound(self._client_commands)
192
222
 
193
223
    def _get_compose_commandline(self, to, subject, attach_path, body):
194
224
        """Determine the commandline to use for composing a message
208
238
        :param  u:  possible unicode string.
209
239
        :return:    encoded string if u is unicode, u itself otherwise.
210
240
        """
211
 
        if isinstance(u, unicode):
 
241
        if not PY3 and isinstance(u, text_type):
212
242
            return u.encode(osutils.get_user_encoding(), 'replace')
213
243
        return u
214
244
 
221
251
                        path itself otherwise.
222
252
        :raise:         UnableEncodePath.
223
253
        """
224
 
        if isinstance(path, unicode):
 
254
        if not PY3 and isinstance(path, text_type):
225
255
            try:
226
256
                return path.encode(osutils.get_user_encoding())
227
257
            except UnicodeEncodeError:
250
280
        if body is not None:
251
281
            message_options['body'] = body
252
282
        options_list = ['%s=%s' % (k, urlutils.escape(v)) for (k, v) in
253
 
                        sorted(message_options.iteritems())]
 
283
                        sorted(message_options.items())]
254
284
        return ['mailto:%s?%s' % (self._encode_safe(to or ''),
255
 
            '&'.join(options_list))]
 
285
                                  '&'.join(options_list))]
 
286
 
 
287
 
256
288
mail_client_registry.register('evolution', Evolution,
257
289
                              help=Evolution.__doc__)
258
290
 
269
301
            message_options.extend(['-s', self._encode_safe(subject)])
270
302
        if attach_path is not None:
271
303
            message_options.extend(['-a',
272
 
                self._encode_path(attach_path, 'attachment')])
 
304
                                    self._encode_path(attach_path, 'attachment')])
273
305
        if body is not None:
274
306
            # Store the temp file object in self, so that it does not get
275
307
            # garbage collected and delete the file before mutt can read it.
276
308
            self._temp_file = tempfile.NamedTemporaryFile(
277
 
                prefix="mutt-body-", suffix=".txt")
 
309
                prefix="mutt-body-", suffix=".txt", mode="w+")
278
310
            self._temp_file.write(body)
279
311
            self._temp_file.flush()
280
312
            message_options.extend(['-i', self._temp_file.name])
281
313
        if to is not None:
282
314
            message_options.extend(['--', self._encode_safe(to)])
283
315
        return message_options
 
316
 
 
317
 
284
318
mail_client_registry.register('mutt', Mutt,
285
319
                              help=Mutt.__doc__)
286
320
 
296
330
    """
297
331
 
298
332
    _client_commands = ['thunderbird', 'mozilla-thunderbird', 'icedove',
299
 
        '/Applications/Mozilla/Thunderbird.app/Contents/MacOS/thunderbird-bin',
300
 
        '/Applications/Thunderbird.app/Contents/MacOS/thunderbird-bin']
 
333
                        '/Applications/Mozilla/Thunderbird.app/Contents/MacOS/thunderbird-bin',
 
334
                        '/Applications/Thunderbird.app/Contents/MacOS/thunderbird-bin']
301
335
 
302
336
    def _get_compose_commandline(self, to, subject, attach_path, body=None):
303
337
        """See ExternalMailClient._get_compose_commandline"""
310
344
            message_options['attachment'] = urlutils.local_path_to_url(
311
345
                attach_path)
312
346
        if body is not None:
313
 
            options_list = ['body=%s' % urllib.quote(self._encode_safe(body))]
 
347
            options_list = ['body=%s' %
 
348
                            urlutils.quote(self._encode_safe(body))]
314
349
        else:
315
350
            options_list = []
316
351
        options_list.extend(["%s='%s'" % (k, v) for k, v in
317
 
                        sorted(message_options.iteritems())])
 
352
                             sorted(message_options.items())])
318
353
        return ['-compose', ','.join(options_list)]
 
354
 
 
355
 
319
356
mail_client_registry.register('thunderbird', Thunderbird,
320
357
                              help=Thunderbird.__doc__)
321
358
 
332
369
            message_options.extend(['-s', self._encode_safe(subject)])
333
370
        if attach_path is not None:
334
371
            message_options.extend(['--attach',
335
 
                self._encode_path(attach_path, 'attachment')])
 
372
                                    self._encode_path(attach_path, 'attachment')])
336
373
        if to is not None:
337
374
            message_options.extend([self._encode_safe(to)])
338
375
        return message_options
 
376
 
 
377
 
339
378
mail_client_registry.register('kmail', KMail,
340
379
                              help=KMail.__doc__)
341
380
 
352
391
        """See ExternalMailClient._get_compose_commandline"""
353
392
        compose_url = []
354
393
        if from_ is not None:
355
 
            compose_url.append('from=' + urllib.quote(from_))
 
394
            compose_url.append('from=' + urlutils.quote(from_))
356
395
        if subject is not None:
357
 
            # Don't use urllib.quote_plus because Claws doesn't seem
 
396
            # Don't use urlutils.quote_plus because Claws doesn't seem
358
397
            # to recognise spaces encoded as "+".
359
398
            compose_url.append(
360
 
                'subject=' + urllib.quote(self._encode_safe(subject)))
 
399
                'subject=' + urlutils.quote(self._encode_safe(subject)))
361
400
        if body is not None:
362
401
            compose_url.append(
363
 
                'body=' + urllib.quote(self._encode_safe(body)))
 
402
                'body=' + urlutils.quote(self._encode_safe(body)))
364
403
        # to must be supplied for the claws-mail --compose syntax to work.
365
404
        if to is None:
366
 
            raise errors.NoMailAddressSpecified()
 
405
            raise NoMailAddressSpecified()
367
406
        compose_url = 'mailto:%s?%s' % (
368
407
            self._encode_safe(to), '&'.join(compose_url))
369
408
        # Collect command-line options.
377
416
                 extension, body=None, from_=None):
378
417
        """See ExternalMailClient._compose"""
379
418
        if from_ is None:
380
 
            from_ = self.config.get_user_option('email')
 
419
            from_ = self.config.get('email')
381
420
        super(Claws, self)._compose(prompt, to, subject, attach_path,
382
421
                                    mime_subtype, extension, body, from_)
383
422
 
394
433
    def _get_compose_commandline(self, to, subject, attach_path, body=None):
395
434
        """See ExternalMailClient._get_compose_commandline"""
396
435
        if not to:
397
 
            raise errors.NoMailAddressSpecified()
 
436
            raise NoMailAddressSpecified()
398
437
        commandline = [self._encode_safe(to)]
399
438
        if subject is not None:
400
439
            commandline.extend(['--subject', self._encode_safe(subject)])
401
440
        if attach_path is not None:
402
441
            commandline.extend(['--attach',
403
 
                self._encode_path(attach_path, 'attachment')])
 
442
                                self._encode_path(attach_path, 'attachment')])
404
443
        if body is not None:
405
444
            commandline.extend(['--body', self._encode_safe(body)])
406
445
        return commandline
 
446
 
 
447
 
407
448
mail_client_registry.register('xdg-email', XDGEmail,
408
449
                              help=XDGEmail.__doc__)
409
450
 
442
483
        after being read by Emacs.)
443
484
        """
444
485
 
445
 
        _defun = r"""(defun bzr-add-mime-att (file)
 
486
        _defun = br"""(defun bzr-add-mime-att (file)
446
487
  "Attach FILE to a mail buffer as a MIME attachment."
447
488
  (let ((agent mail-user-agent))
448
489
    (if (and file (file-exists-p file))
478
519
        try:
479
520
            os.write(fd, _defun)
480
521
        finally:
481
 
            os.close(fd) # Just close the handle but do not remove the file.
 
522
            os.close(fd)  # Just close the handle but do not remove the file.
482
523
        return temp_file
483
524
 
484
525
    def _get_compose_commandline(self, to, subject, attach_path):
506
547
            elisp = self._prepare_send_function()
507
548
            self.elisp_tmp_file = elisp
508
549
            lmmform = '(load "%s")' % elisp
509
 
            mmform  = '(bzr-add-mime-att "%s")' % \
 
550
            mmform = '(bzr-add-mime-att "%s")' % \
510
551
                self._encode_path(attach_path, 'attachment')
511
552
            rmform = '(delete-file "%s")' % elisp
512
553
            commandline.append(lmmform)
514
555
            commandline.append(rmform)
515
556
 
516
557
        return commandline
 
558
 
 
559
 
517
560
mail_client_registry.register('emacsclient', EmacsMail,
518
561
                              help=EmacsMail.__doc__)
519
562
 
527
570
 
528
571
        This implementation uses MAPI via the simplemapi ctypes wrapper
529
572
        """
530
 
        from bzrlib.util import simplemapi
 
573
        from .util import simplemapi
531
574
        try:
532
575
            simplemapi.SendMail(to or '', subject or '', body or '',
533
576
                                attach_path)
534
 
        except simplemapi.MAPIError, e:
 
577
        except simplemapi.MAPIError as e:
535
578
            if e.code != simplemapi.MAPI_USER_ABORT:
536
 
                raise errors.MailClientNotFound(['MAPI supported mail client'
537
 
                                                 ' (error %d)' % (e.code,)])
 
579
                raise MailClientNotFound(['MAPI supported mail client'
 
580
                                          ' (error %d)' % (e.code,)])
 
581
 
 
582
 
538
583
mail_client_registry.register('mapi', MAPIClient,
539
584
                              help=MAPIClient.__doc__)
540
585
 
552
597
    _client_commands = ['osascript']
553
598
 
554
599
    def _get_compose_commandline(self, to, subject, attach_path, body=None,
555
 
                                from_=None):
556
 
       """See ExternalMailClient._get_compose_commandline"""
557
 
 
558
 
       fd, self.temp_file = tempfile.mkstemp(prefix="bzr-send-",
559
 
                                         suffix=".scpt")
560
 
       try:
561
 
           os.write(fd, 'tell application "Mail"\n')
562
 
           os.write(fd, 'set newMessage to make new outgoing message\n')
563
 
           os.write(fd, 'tell newMessage\n')
564
 
           if to is not None:
565
 
               os.write(fd, 'make new to recipient with properties'
566
 
                   ' {address:"%s"}\n' % to)
567
 
           if from_ is not None:
568
 
               # though from_ doesn't actually seem to be used
569
 
               os.write(fd, 'set sender to "%s"\n'
570
 
                   % sender.replace('"', '\\"'))
571
 
           if subject is not None:
572
 
               os.write(fd, 'set subject to "%s"\n'
573
 
                   % subject.replace('"', '\\"'))
574
 
           if body is not None:
575
 
               # FIXME: would be nice to prepend the body to the
576
 
               # existing content (e.g., preserve signature), but
577
 
               # can't seem to figure out the right applescript
578
 
               # incantation.
579
 
               os.write(fd, 'set content to "%s\\n\n"\n' %
580
 
                   body.replace('"', '\\"').replace('\n', '\\n'))
581
 
 
582
 
           if attach_path is not None:
583
 
               # FIXME: would be nice to first append a newline to
584
 
               # ensure the attachment is on a new paragraph, but
585
 
               # can't seem to figure out the right applescript
586
 
               # incantation.
587
 
               os.write(fd, 'tell content to make new attachment'
588
 
                   ' with properties {file name:"%s"}'
589
 
                   ' at after the last paragraph\n'
590
 
                   % self._encode_path(attach_path, 'attachment'))
591
 
           os.write(fd, 'set visible to true\n')
592
 
           os.write(fd, 'end tell\n')
593
 
           os.write(fd, 'end tell\n')
594
 
       finally:
595
 
           os.close(fd) # Just close the handle but do not remove the file.
596
 
       return [self.temp_file]
 
600
                                 from_=None):
 
601
        """See ExternalMailClient._get_compose_commandline"""
 
602
 
 
603
        fd, self.temp_file = tempfile.mkstemp(prefix="bzr-send-",
 
604
                                              suffix=".scpt")
 
605
        try:
 
606
            os.write(fd, 'tell application "Mail"\n')
 
607
            os.write(fd, 'set newMessage to make new outgoing message\n')
 
608
            os.write(fd, 'tell newMessage\n')
 
609
            if to is not None:
 
610
                os.write(fd, 'make new to recipient with properties'
 
611
                         ' {address:"%s"}\n' % to)
 
612
            if from_ is not None:
 
613
                # though from_ doesn't actually seem to be used
 
614
                os.write(fd, 'set sender to "%s"\n'
 
615
                         % sender.replace('"', '\\"'))
 
616
            if subject is not None:
 
617
                os.write(fd, 'set subject to "%s"\n'
 
618
                         % subject.replace('"', '\\"'))
 
619
            if body is not None:
 
620
                # FIXME: would be nice to prepend the body to the
 
621
                # existing content (e.g., preserve signature), but
 
622
                # can't seem to figure out the right applescript
 
623
                # incantation.
 
624
                os.write(fd, 'set content to "%s\\n\n"\n' %
 
625
                         body.replace('"', '\\"').replace('\n', '\\n'))
 
626
 
 
627
            if attach_path is not None:
 
628
                # FIXME: would be nice to first append a newline to
 
629
                # ensure the attachment is on a new paragraph, but
 
630
                # can't seem to figure out the right applescript
 
631
                # incantation.
 
632
                os.write(fd, 'tell content to make new attachment'
 
633
                         ' with properties {file name:"%s"}'
 
634
                         ' at after the last paragraph\n'
 
635
                         % self._encode_path(attach_path, 'attachment'))
 
636
            os.write(fd, 'set visible to true\n')
 
637
            os.write(fd, 'end tell\n')
 
638
            os.write(fd, 'end tell\n')
 
639
        finally:
 
640
            os.close(fd)  # Just close the handle but do not remove the file.
 
641
        return [self.temp_file]
 
642
 
 
643
 
597
644
mail_client_registry.register('mail.app', MailApp,
598
645
                              help=MailApp.__doc__)
599
646
 
616
663
        """See MailClient.compose"""
617
664
        try:
618
665
            return self._mail_client().compose(prompt, to, subject,
619
 
                                               attachment, mimie_subtype,
 
666
                                               attachment, mime_subtype,
620
667
                                               extension, basename, body)
621
 
        except errors.MailClientNotFound:
 
668
        except MailClientNotFound:
622
669
            return Editor(self.config).compose(prompt, to, subject,
623
 
                          attachment, mimie_subtype, extension, body)
 
670
                                               attachment, mime_subtype, extension, body)
624
671
 
625
672
    def compose_merge_request(self, to, subject, directive, basename=None,
626
673
                              body=None):
627
674
        """See MailClient.compose_merge_request"""
628
675
        try:
629
676
            return self._mail_client().compose_merge_request(to, subject,
630
 
                    directive, basename=basename, body=body)
631
 
        except errors.MailClientNotFound:
 
677
                                                             directive, basename=basename, body=body)
 
678
        except MailClientNotFound:
632
679
            return Editor(self.config).compose_merge_request(to, subject,
633
 
                          directive, basename=basename, body=body)
634
 
mail_client_registry.register('default', DefaultMail,
 
680
                                                             directive, basename=basename, body=body)
 
681
 
 
682
 
 
683
mail_client_registry.register(u'default', DefaultMail,
635
684
                              help=DefaultMail.__doc__)
636
 
mail_client_registry.default_key = 'default'
637
 
 
638
 
 
 
685
mail_client_registry.default_key = u'default'
 
686
 
 
687
opt_mail_client = _mod_config.RegistryOption('mail_client',
 
688
                                             mail_client_registry, help='E-mail client to use.', invalid='error')