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
17
from __future__ import absolute_import
27
config as _mod_config,
34
40
mail_client_registry = registry.Registry()
43
class MailClientNotFound(errors.BzrError):
45
_fmt = "Unable to find mail client with the following names:"\
46
" %(mail_command_list_string)s"
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)
55
class NoMessageSupplied(errors.BzrError):
57
_fmt = "No message supplied."
60
class NoMailAddressSpecified(errors.BzrError):
62
_fmt = "No mail-to address (--mail-to) or output (-o) specified."
37
65
class MailClient(object):
38
66
"""A mail client that can send messages with attachements."""
105
133
extension, basename=None, body=None):
106
134
"""See MailClient.compose"""
108
raise errors.NoMailAddressSpecified()
136
raise NoMailAddressSpecified()
109
137
body = msgeditor.edit_commit_message(prompt, start_message=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'),
118
146
attachment_mime_subtype=mime_subtype)
119
149
mail_client_registry.register('editor', Editor,
120
150
help=Editor.__doc__)
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
284
318
mail_client_registry.register('mutt', Mutt,
285
319
help=Mutt.__doc__)
352
391
"""See ExternalMailClient._get_compose_commandline"""
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.
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.
394
433
def _get_compose_commandline(self, to, subject, attach_path, body=None):
395
434
"""See ExternalMailClient._get_compose_commandline"""
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
407
448
mail_client_registry.register('xdg-email', XDGEmail,
408
449
help=XDGEmail.__doc__)
528
571
This implementation uses MAPI via the simplemapi ctypes wrapper
530
from bzrlib.util import simplemapi
573
from .util import simplemapi
532
575
simplemapi.SendMail(to or '', subject or '', body or '',
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,)])
538
583
mail_client_registry.register('mapi', MAPIClient,
539
584
help=MAPIClient.__doc__)
552
597
_client_commands = ['osascript']
554
599
def _get_compose_commandline(self, to, subject, attach_path, body=None,
556
"""See ExternalMailClient._get_compose_commandline"""
558
fd, self.temp_file = tempfile.mkstemp(prefix="bzr-send-",
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')
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('"', '\\"'))
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
579
os.write(fd, 'set content to "%s\\n\n"\n' %
580
body.replace('"', '\\"').replace('\n', '\\n'))
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
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')
595
os.close(fd) # Just close the handle but do not remove the file.
596
return [self.temp_file]
601
"""See ExternalMailClient._get_compose_commandline"""
603
fd, self.temp_file = tempfile.mkstemp(prefix="bzr-send-",
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')
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('"', '\\"'))
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
624
os.write(fd, 'set content to "%s\\n\n"\n' %
625
body.replace('"', '\\"').replace('\n', '\\n'))
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
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')
640
os.close(fd) # Just close the handle but do not remove the file.
641
return [self.temp_file]
597
644
mail_client_registry.register('mail.app', MailApp,
598
645
help=MailApp.__doc__)
616
663
"""See MailClient.compose"""
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)
625
672
def compose_merge_request(self, to, subject, directive, basename=None,
627
674
"""See MailClient.compose_merge_request"""
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)
683
mail_client_registry.register(u'default', DefaultMail,
635
684
help=DefaultMail.__doc__)
636
mail_client_registry.default_key = 'default'
685
mail_client_registry.default_key = u'default'
687
opt_mail_client = _mod_config.RegistryOption('mail_client',
688
mail_client_registry, help='E-mail client to use.', invalid='error')