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,
40
34
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."
65
37
class MailClient(object):
66
38
"""A mail client that can send messages with attachements."""
133
105
extension, basename=None, body=None):
134
106
"""See MailClient.compose"""
136
raise NoMailAddressSpecified()
108
raise errors.NoMailAddressSpecified()
137
109
body = msgeditor.edit_commit_message(prompt, start_message=body)
139
raise NoMessageSupplied()
111
raise errors.NoMessageSupplied()
140
112
email_message.EmailMessage.send(self.config,
141
self.config.get('email'),
113
self.config.username(),
146
118
attachment_mime_subtype=mime_subtype)
149
119
mail_client_registry.register('editor', Editor,
150
120
help=Editor.__doc__)
295
266
"""See ExternalMailClient._get_compose_commandline"""
296
267
message_options = []
297
268
if subject is not None:
298
message_options.extend(
299
['-s', self._encode_safe(subject)])
269
message_options.extend(['-s', self._encode_safe(subject)])
300
270
if attach_path is not None:
301
message_options.extend(
302
['-a', self._encode_path(attach_path, 'attachment')])
271
message_options.extend(['-a',
272
self._encode_path(attach_path, 'attachment')])
303
273
if body is not None:
304
274
# Store the temp file object in self, so that it does not get
305
275
# garbage collected and delete the file before mutt can read it.
306
276
self._temp_file = tempfile.NamedTemporaryFile(
307
prefix="mutt-body-", suffix=".txt", mode="w+")
277
prefix="mutt-body-", suffix=".txt")
308
278
self._temp_file.write(body)
309
279
self._temp_file.flush()
310
280
message_options.extend(['-i', self._temp_file.name])
311
281
if to is not None:
312
282
message_options.extend(['--', self._encode_safe(to)])
313
283
return message_options
316
284
mail_client_registry.register('mutt', Mutt,
317
285
help=Mutt.__doc__)
390
352
"""See ExternalMailClient._get_compose_commandline"""
392
354
if from_ is not None:
393
compose_url.append('from=' + urlutils.quote(from_))
355
compose_url.append('from=' + urllib.quote(from_))
394
356
if subject is not None:
395
# Don't use urlutils.quote_plus because Claws doesn't seem
357
# Don't use urllib.quote_plus because Claws doesn't seem
396
358
# to recognise spaces encoded as "+".
397
359
compose_url.append(
398
'subject=' + urlutils.quote(self._encode_safe(subject)))
360
'subject=' + urllib.quote(self._encode_safe(subject)))
399
361
if body is not None:
400
362
compose_url.append(
401
'body=' + urlutils.quote(self._encode_safe(body)))
363
'body=' + urllib.quote(self._encode_safe(body)))
402
364
# to must be supplied for the claws-mail --compose syntax to work.
404
raise NoMailAddressSpecified()
366
raise errors.NoMailAddressSpecified()
405
367
compose_url = 'mailto:%s?%s' % (
406
368
self._encode_safe(to), '&'.join(compose_url))
407
369
# Collect command-line options.
427
389
class XDGEmail(BodyExternalMailClient):
428
__doc__ = """xdg-email attempts to invoke the preferred mail client"""
390
__doc__ = """xdg-email attempts to invoke the user's preferred mail client"""
430
392
_client_commands = ['xdg-email']
432
394
def _get_compose_commandline(self, to, subject, attach_path, body=None):
433
395
"""See ExternalMailClient._get_compose_commandline"""
435
raise NoMailAddressSpecified()
397
raise errors.NoMailAddressSpecified()
436
398
commandline = [self._encode_safe(to)]
437
399
if subject is not None:
438
400
commandline.extend(['--subject', self._encode_safe(subject)])
439
401
if attach_path is not None:
440
402
commandline.extend(['--attach',
441
self._encode_path(attach_path, 'attachment')])
403
self._encode_path(attach_path, 'attachment')])
442
404
if body is not None:
443
405
commandline.extend(['--body', self._encode_safe(body)])
444
406
return commandline
447
407
mail_client_registry.register('xdg-email', XDGEmail,
448
408
help=XDGEmail.__doc__)
570
528
This implementation uses MAPI via the simplemapi ctypes wrapper
572
from .util import simplemapi
530
from bzrlib.util import simplemapi
574
532
simplemapi.SendMail(to or '', subject or '', body or '',
576
except simplemapi.MAPIError as e:
534
except simplemapi.MAPIError, e:
577
535
if e.code != simplemapi.MAPI_USER_ABORT:
578
raise MailClientNotFound(['MAPI supported mail client'
579
' (error %d)' % (e.code,)])
536
raise errors.MailClientNotFound(['MAPI supported mail client'
537
' (error %d)' % (e.code,)])
582
538
mail_client_registry.register('mapi', MAPIClient,
583
539
help=MAPIClient.__doc__)
596
552
_client_commands = ['osascript']
598
554
def _get_compose_commandline(self, to, subject, attach_path, body=None,
600
"""See ExternalMailClient._get_compose_commandline"""
602
fd, self.temp_file = tempfile.mkstemp(prefix="bzr-send-",
605
os.write(fd, 'tell application "Mail"\n')
606
os.write(fd, 'set newMessage to make new outgoing message\n')
607
os.write(fd, 'tell newMessage\n')
609
os.write(fd, 'make new to recipient with properties'
610
' {address:"%s"}\n' % to)
611
if from_ is not None:
612
# though from_ doesn't actually seem to be used
613
os.write(fd, 'set sender to "%s"\n'
614
% from_.replace('"', '\\"'))
615
if subject is not None:
616
os.write(fd, 'set subject to "%s"\n'
617
% subject.replace('"', '\\"'))
619
# FIXME: would be nice to prepend the body to the
620
# existing content (e.g., preserve signature), but
621
# can't seem to figure out the right applescript
623
os.write(fd, 'set content to "%s\\n\n"\n' %
624
body.replace('"', '\\"').replace('\n', '\\n'))
626
if attach_path is not None:
627
# FIXME: would be nice to first append a newline to
628
# ensure the attachment is on a new paragraph, but
629
# can't seem to figure out the right applescript
631
os.write(fd, 'tell content to make new attachment'
632
' with properties {file name:"%s"}'
633
' at after the last paragraph\n'
634
% self._encode_path(attach_path, 'attachment'))
635
os.write(fd, 'set visible to true\n')
636
os.write(fd, 'end tell\n')
637
os.write(fd, 'end tell\n')
639
os.close(fd) # Just close the handle but do not remove the file.
640
return [self.temp_file]
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]
643
597
mail_client_registry.register('mail.app', MailApp,
644
598
help=MailApp.__doc__)
662
616
"""See MailClient.compose"""
664
618
return self._mail_client().compose(prompt, to, subject,
665
attachment, mime_subtype,
619
attachment, mimie_subtype,
666
620
extension, basename, body)
667
except MailClientNotFound:
668
return Editor(self.config).compose(
669
prompt, to, subject, attachment, mime_subtype, extension, body)
621
except errors.MailClientNotFound:
622
return Editor(self.config).compose(prompt, to, subject,
623
attachment, mimie_subtype, extension, body)
671
625
def compose_merge_request(self, to, subject, directive, basename=None,
673
627
"""See MailClient.compose_merge_request"""
675
return self._mail_client().compose_merge_request(
676
to, subject, directive, basename=basename, body=body)
677
except MailClientNotFound:
678
return Editor(self.config).compose_merge_request(
679
to, subject, directive, basename=basename, body=body)
682
mail_client_registry.register(u'default', DefaultMail,
629
return self._mail_client().compose_merge_request(to, subject,
630
directive, basename=basename, body=body)
631
except errors.MailClientNotFound:
632
return Editor(self.config).compose_merge_request(to, subject,
633
directive, basename=basename, body=body)
634
mail_client_registry.register('default', DefaultMail,
683
635
help=DefaultMail.__doc__)
684
mail_client_registry.default_key = u'default'
686
opt_mail_client = _mod_config.RegistryOption(
687
'mail_client', mail_client_registry, help='E-mail client to use.',
636
mail_client_registry.default_key = 'default'