/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: 2017-06-08 23:30:31 UTC
  • mto: This revision was merged to the branch mainline in revision 6690.
  • Revision ID: jelmer@jelmer.uk-20170608233031-3qavls2o7a1pqllj
Update imports.

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
28
30
    msgeditor,
29
31
    osutils,
30
32
    urlutils,
31
 
    registry,
 
33
    registry
32
34
    )
33
35
 
34
36
mail_client_registry = registry.Registry()
35
37
 
36
38
 
37
 
class MailClientNotFound(errors.BzrError):
38
 
 
39
 
    _fmt = "Unable to find mail client with the following names:"\
40
 
        " %(mail_command_list_string)s"
41
 
 
42
 
    def __init__(self, mail_command_list):
43
 
        mail_command_list_string = ', '.join(mail_command_list)
44
 
        errors.BzrError.__init__(
45
 
            self, mail_command_list=mail_command_list,
46
 
            mail_command_list_string=mail_command_list_string)
47
 
 
48
 
 
49
 
class NoMessageSupplied(errors.BzrError):
50
 
 
51
 
    _fmt = "No message supplied."
52
 
 
53
 
 
54
 
class NoMailAddressSpecified(errors.BzrError):
55
 
 
56
 
    _fmt = "No mail-to address (--mail-to) or output (-o) specified."
57
 
 
58
 
 
59
39
class MailClient(object):
60
40
    """A mail client that can send messages with attachements."""
61
41
 
97
77
        prompt = self._get_merge_prompt("Please describe these changes:", to,
98
78
                                        subject, directive)
99
79
        self.compose(prompt, to, subject, directive,
100
 
                     'x-patch', '.patch', basename, body)
 
80
            'x-patch', '.patch', basename, body)
101
81
 
102
82
    def _get_merge_prompt(self, prompt, to, subject, attachment):
103
83
        """Generate a prompt string.  Overridden by Editor.
127
107
                extension, basename=None, body=None):
128
108
        """See MailClient.compose"""
129
109
        if not to:
130
 
            raise NoMailAddressSpecified()
 
110
            raise errors.NoMailAddressSpecified()
131
111
        body = msgeditor.edit_commit_message(prompt, start_message=body)
132
112
        if body == '':
133
 
            raise NoMessageSupplied()
 
113
            raise errors.NoMessageSupplied()
134
114
        email_message.EmailMessage.send(self.config,
135
115
                                        self.config.get('email'),
136
116
                                        to,
138
118
                                        body,
139
119
                                        attachment,
140
120
                                        attachment_mime_subtype=mime_subtype)
141
 
 
142
 
 
143
121
mail_client_registry.register('editor', Editor,
144
122
                              help=Editor.__doc__)
145
123
 
166
144
            basename = 'attachment'
167
145
        pathname = osutils.mkdtemp(prefix='bzr-mail-')
168
146
        attach_path = osutils.pathjoin(pathname, basename + extension)
169
 
        with open(attach_path, 'wb') as outfile:
 
147
        outfile = open(attach_path, 'wb')
 
148
        try:
170
149
            outfile.write(attachment)
 
150
        finally:
 
151
            outfile.close()
171
152
        if body is not None:
172
153
            kwargs = {'body': body}
173
154
        else:
209
190
            else:
210
191
                break
211
192
        else:
212
 
            raise MailClientNotFound(self._client_commands)
 
193
            raise errors.MailClientNotFound(self._client_commands)
213
194
 
214
195
    def _get_compose_commandline(self, to, subject, attach_path, body):
215
196
        """Determine the commandline to use for composing a message
229
210
        :param  u:  possible unicode string.
230
211
        :return:    encoded string if u is unicode, u itself otherwise.
231
212
        """
 
213
        if isinstance(u, unicode):
 
214
            return u.encode(osutils.get_user_encoding(), 'replace')
232
215
        return u
233
216
 
234
217
    def _encode_path(self, path, kind):
240
223
                        path itself otherwise.
241
224
        :raise:         UnableEncodePath.
242
225
        """
 
226
        if isinstance(path, unicode):
 
227
            try:
 
228
                return path.encode(osutils.get_user_encoding())
 
229
            except UnicodeEncodeError:
 
230
                raise errors.UnableEncodePath(path, kind)
243
231
        return path
244
232
 
245
233
 
266
254
        options_list = ['%s=%s' % (k, urlutils.escape(v)) for (k, v) in
267
255
                        sorted(message_options.items())]
268
256
        return ['mailto:%s?%s' % (self._encode_safe(to or ''),
269
 
                                  '&'.join(options_list))]
270
 
 
271
 
 
 
257
            '&'.join(options_list))]
272
258
mail_client_registry.register('evolution', Evolution,
273
259
                              help=Evolution.__doc__)
274
260
 
282
268
        """See ExternalMailClient._get_compose_commandline"""
283
269
        message_options = []
284
270
        if subject is not None:
285
 
            message_options.extend(
286
 
                ['-s', self._encode_safe(subject)])
 
271
            message_options.extend(['-s', self._encode_safe(subject)])
287
272
        if attach_path is not None:
288
 
            message_options.extend(
289
 
                ['-a', self._encode_path(attach_path, 'attachment')])
 
273
            message_options.extend(['-a',
 
274
                self._encode_path(attach_path, 'attachment')])
290
275
        if body is not None:
291
276
            # Store the temp file object in self, so that it does not get
292
277
            # garbage collected and delete the file before mutt can read it.
293
278
            self._temp_file = tempfile.NamedTemporaryFile(
294
 
                prefix="mutt-body-", suffix=".txt", mode="w+")
 
279
                prefix="mutt-body-", suffix=".txt")
295
280
            self._temp_file.write(body)
296
281
            self._temp_file.flush()
297
282
            message_options.extend(['-i', self._temp_file.name])
298
283
        if to is not None:
299
284
            message_options.extend(['--', self._encode_safe(to)])
300
285
        return message_options
301
 
 
302
 
 
303
286
mail_client_registry.register('mutt', Mutt,
304
287
                              help=Mutt.__doc__)
305
288
 
314
297
    send attachments.
315
298
    """
316
299
 
317
 
    _client_commands = [
318
 
        'thunderbird', 'mozilla-thunderbird', 'icedove',
 
300
    _client_commands = ['thunderbird', 'mozilla-thunderbird', 'icedove',
319
301
        '/Applications/Mozilla/Thunderbird.app/Contents/MacOS/thunderbird-bin',
320
302
        '/Applications/Thunderbird.app/Contents/MacOS/thunderbird-bin']
321
303
 
330
312
            message_options['attachment'] = urlutils.local_path_to_url(
331
313
                attach_path)
332
314
        if body is not None:
333
 
            options_list = ['body=%s' %
334
 
                            urlutils.quote(self._encode_safe(body))]
 
315
            options_list = ['body=%s' % urlutils.quote(self._encode_safe(body))]
335
316
        else:
336
317
            options_list = []
337
318
        options_list.extend(["%s='%s'" % (k, v) for k, v in
338
 
                             sorted(message_options.items())])
 
319
                        sorted(message_options.items())])
339
320
        return ['-compose', ','.join(options_list)]
340
 
 
341
 
 
342
321
mail_client_registry.register('thunderbird', Thunderbird,
343
322
                              help=Thunderbird.__doc__)
344
323
 
354
333
        if subject is not None:
355
334
            message_options.extend(['-s', self._encode_safe(subject)])
356
335
        if attach_path is not None:
357
 
            message_options.extend(
358
 
                ['--attach', self._encode_path(attach_path, 'attachment')])
 
336
            message_options.extend(['--attach',
 
337
                self._encode_path(attach_path, 'attachment')])
359
338
        if to is not None:
360
339
            message_options.extend([self._encode_safe(to)])
361
340
        return message_options
362
 
 
363
 
 
364
341
mail_client_registry.register('kmail', KMail,
365
342
                              help=KMail.__doc__)
366
343
 
388
365
                'body=' + urlutils.quote(self._encode_safe(body)))
389
366
        # to must be supplied for the claws-mail --compose syntax to work.
390
367
        if to is None:
391
 
            raise NoMailAddressSpecified()
 
368
            raise errors.NoMailAddressSpecified()
392
369
        compose_url = 'mailto:%s?%s' % (
393
370
            self._encode_safe(to), '&'.join(compose_url))
394
371
        # Collect command-line options.
412
389
 
413
390
 
414
391
class XDGEmail(BodyExternalMailClient):
415
 
    __doc__ = """xdg-email attempts to invoke the preferred mail client"""
 
392
    __doc__ = """xdg-email attempts to invoke the user's preferred mail client"""
416
393
 
417
394
    _client_commands = ['xdg-email']
418
395
 
419
396
    def _get_compose_commandline(self, to, subject, attach_path, body=None):
420
397
        """See ExternalMailClient._get_compose_commandline"""
421
398
        if not to:
422
 
            raise NoMailAddressSpecified()
 
399
            raise errors.NoMailAddressSpecified()
423
400
        commandline = [self._encode_safe(to)]
424
401
        if subject is not None:
425
402
            commandline.extend(['--subject', self._encode_safe(subject)])
426
403
        if attach_path is not None:
427
404
            commandline.extend(['--attach',
428
 
                                self._encode_path(attach_path, 'attachment')])
 
405
                self._encode_path(attach_path, 'attachment')])
429
406
        if body is not None:
430
407
            commandline.extend(['--body', self._encode_safe(body)])
431
408
        return commandline
432
 
 
433
 
 
434
409
mail_client_registry.register('xdg-email', XDGEmail,
435
410
                              help=XDGEmail.__doc__)
436
411
 
469
444
        after being read by Emacs.)
470
445
        """
471
446
 
472
 
        _defun = br"""(defun bzr-add-mime-att (file)
 
447
        _defun = r"""(defun bzr-add-mime-att (file)
473
448
  "Attach FILE to a mail buffer as a MIME attachment."
474
449
  (let ((agent mail-user-agent))
475
450
    (if (and file (file-exists-p file))
505
480
        try:
506
481
            os.write(fd, _defun)
507
482
        finally:
508
 
            os.close(fd)  # Just close the handle but do not remove the file.
 
483
            os.close(fd) # Just close the handle but do not remove the file.
509
484
        return temp_file
510
485
 
511
486
    def _get_compose_commandline(self, to, subject, attach_path):
533
508
            elisp = self._prepare_send_function()
534
509
            self.elisp_tmp_file = elisp
535
510
            lmmform = '(load "%s")' % elisp
536
 
            mmform = '(bzr-add-mime-att "%s")' % \
 
511
            mmform  = '(bzr-add-mime-att "%s")' % \
537
512
                self._encode_path(attach_path, 'attachment')
538
513
            rmform = '(delete-file "%s")' % elisp
539
514
            commandline.append(lmmform)
541
516
            commandline.append(rmform)
542
517
 
543
518
        return commandline
544
 
 
545
 
 
546
519
mail_client_registry.register('emacsclient', EmacsMail,
547
520
                              help=EmacsMail.__doc__)
548
521
 
562
535
                                attach_path)
563
536
        except simplemapi.MAPIError as e:
564
537
            if e.code != simplemapi.MAPI_USER_ABORT:
565
 
                raise MailClientNotFound(['MAPI supported mail client'
566
 
                                          ' (error %d)' % (e.code,)])
567
 
 
568
 
 
 
538
                raise errors.MailClientNotFound(['MAPI supported mail client'
 
539
                                                 ' (error %d)' % (e.code,)])
569
540
mail_client_registry.register('mapi', MAPIClient,
570
541
                              help=MAPIClient.__doc__)
571
542
 
583
554
    _client_commands = ['osascript']
584
555
 
585
556
    def _get_compose_commandline(self, to, subject, attach_path, body=None,
586
 
                                 from_=None):
587
 
        """See ExternalMailClient._get_compose_commandline"""
588
 
 
589
 
        fd, self.temp_file = tempfile.mkstemp(prefix="bzr-send-",
590
 
                                              suffix=".scpt")
591
 
        try:
592
 
            os.write(fd, 'tell application "Mail"\n')
593
 
            os.write(fd, 'set newMessage to make new outgoing message\n')
594
 
            os.write(fd, 'tell newMessage\n')
595
 
            if to is not None:
596
 
                os.write(fd, 'make new to recipient with properties'
597
 
                         ' {address:"%s"}\n' % to)
598
 
            if from_ is not None:
599
 
                # though from_ doesn't actually seem to be used
600
 
                os.write(fd, 'set sender to "%s"\n'
601
 
                         % from_.replace('"', '\\"'))
602
 
            if subject is not None:
603
 
                os.write(fd, 'set subject to "%s"\n'
604
 
                         % subject.replace('"', '\\"'))
605
 
            if body is not None:
606
 
                # FIXME: would be nice to prepend the body to the
607
 
                # existing content (e.g., preserve signature), but
608
 
                # can't seem to figure out the right applescript
609
 
                # incantation.
610
 
                os.write(fd, 'set content to "%s\\n\n"\n' %
611
 
                         body.replace('"', '\\"').replace('\n', '\\n'))
612
 
 
613
 
            if attach_path is not None:
614
 
                # FIXME: would be nice to first append a newline to
615
 
                # ensure the attachment is on a new paragraph, but
616
 
                # can't seem to figure out the right applescript
617
 
                # incantation.
618
 
                os.write(fd, 'tell content to make new attachment'
619
 
                         ' with properties {file name:"%s"}'
620
 
                         ' at after the last paragraph\n'
621
 
                         % self._encode_path(attach_path, 'attachment'))
622
 
            os.write(fd, 'set visible to true\n')
623
 
            os.write(fd, 'end tell\n')
624
 
            os.write(fd, 'end tell\n')
625
 
        finally:
626
 
            os.close(fd)  # Just close the handle but do not remove the file.
627
 
        return [self.temp_file]
628
 
 
629
 
 
 
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]
630
599
mail_client_registry.register('mail.app', MailApp,
631
600
                              help=MailApp.__doc__)
632
601
 
651
620
            return self._mail_client().compose(prompt, to, subject,
652
621
                                               attachment, mime_subtype,
653
622
                                               extension, basename, body)
654
 
        except MailClientNotFound:
655
 
            return Editor(self.config).compose(
656
 
                prompt, to, subject, attachment, mime_subtype, extension, body)
 
623
        except errors.MailClientNotFound:
 
624
            return Editor(self.config).compose(prompt, to, subject,
 
625
                          attachment, mime_subtype, extension, body)
657
626
 
658
627
    def compose_merge_request(self, to, subject, directive, basename=None,
659
628
                              body=None):
660
629
        """See MailClient.compose_merge_request"""
661
630
        try:
662
 
            return self._mail_client().compose_merge_request(
663
 
                to, subject, directive, basename=basename, body=body)
664
 
        except MailClientNotFound:
665
 
            return Editor(self.config).compose_merge_request(
666
 
                to, subject, directive, basename=basename, body=body)
667
 
 
668
 
 
669
 
mail_client_registry.register(u'default', DefaultMail,
 
631
            return self._mail_client().compose_merge_request(to, subject,
 
632
                    directive, basename=basename, body=body)
 
633
        except errors.MailClientNotFound:
 
634
            return Editor(self.config).compose_merge_request(to, subject,
 
635
                          directive, basename=basename, body=body)
 
636
mail_client_registry.register('default', DefaultMail,
670
637
                              help=DefaultMail.__doc__)
671
 
mail_client_registry.default_key = u'default'
 
638
mail_client_registry.default_key = 'default'
672
639
 
673
 
opt_mail_client = _mod_config.RegistryOption(
674
 
    'mail_client', mail_client_registry, help='E-mail client to use.',
675
 
    invalid='error')
 
640
opt_mail_client = _mod_config.RegistryOption('mail_client',
 
641
        mail_client_registry, help='E-mail client to use.', invalid='error')