/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 bzrlib/mail_client.py

  • Committer: Richard Wilbur
  • Date: 2016-02-04 19:07:28 UTC
  • mto: This revision was merged to the branch mainline in revision 6618.
  • Revision ID: richard.wilbur@gmail.com-20160204190728-p0zvfii6zase0fw7
Update COPYING.txt from the original http://www.gnu.org/licenses/gpl-2.0.txt  (Only differences were in whitespace.)  Thanks to Petr Stodulka for pointing out the discrepancy.

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
24
 
23
 
import breezy
24
 
from . import (
 
25
import bzrlib
 
26
from bzrlib import (
25
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
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:
203
184
                                                         **kwargs))
204
185
            try:
205
186
                subprocess.call(cmdline)
206
 
            except OSError as e:
 
187
            except OSError, e:
207
188
                if e.errno != errno.ENOENT:
208
189
                    raise
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
 
264
252
        if body is not None:
265
253
            message_options['body'] = body
266
254
        options_list = ['%s=%s' % (k, urlutils.escape(v)) for (k, v) in
267
 
                        sorted(message_options.items())]
 
255
                        sorted(message_options.iteritems())]
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.iteritems())])
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
 
556
529
 
557
530
        This implementation uses MAPI via the simplemapi ctypes wrapper
558
531
        """
559
 
        from .util import simplemapi
 
532
        from bzrlib.util import simplemapi
560
533
        try:
561
534
            simplemapi.SendMail(to or '', subject or '', body or '',
562
535
                                attach_path)
563
 
        except simplemapi.MAPIError as e:
 
536
        except simplemapi.MAPIError, 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')