13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
41
41
self.config = config
43
43
def compose(self, prompt, to, subject, attachment, mime_subtype,
44
extension, basename=None):
44
extension, basename=None, body=None):
45
45
"""Compose (and possibly send) an email message
47
47
Must be implemented by subclasses.
62
62
raise NotImplementedError
64
def compose_merge_request(self, to, subject, directive, basename=None):
64
def compose_merge_request(self, to, subject, directive, basename=None,
65
66
"""Compose (and possibly send) a merge request
67
68
:param to: The address to send the request to
74
75
prompt = self._get_merge_prompt("Please describe these changes:", to,
75
76
subject, directive)
76
77
self.compose(prompt, to, subject, directive,
77
'x-patch', '.patch', basename)
78
'x-patch', '.patch', basename, body)
79
80
def _get_merge_prompt(self, prompt, to, subject, attachment):
80
81
"""Generate a prompt string. Overridden by Editor.
90
91
class Editor(MailClient):
91
92
"""DIY mail client that uses commit message editor"""
93
96
def _get_merge_prompt(self, prompt, to, subject, attachment):
94
97
"""See MailClient._get_merge_prompt"""
99
102
attachment.decode('utf-8', 'replace')))
101
104
def compose(self, prompt, to, subject, attachment, mime_subtype,
102
extension, basename=None):
105
extension, basename=None, body=None):
103
106
"""See MailClient.compose"""
105
108
raise errors.NoMailAddressSpecified()
106
body = msgeditor.edit_commit_message(prompt)
109
body = msgeditor.edit_commit_message(prompt, start_message=body)
108
111
raise errors.NoMessageSupplied()
109
112
email_message.EmailMessage.send(self.config,
129
133
return self._client_commands
131
135
def compose(self, prompt, to, subject, attachment, mime_subtype,
132
extension, basename=None):
136
extension, basename=None, body=None):
133
137
"""See MailClient.compose.
135
139
Writes the attachment to a temporary file, invokes _compose.
143
147
outfile.write(attachment)
151
kwargs = {'body': body}
146
154
self._compose(prompt, to, subject, attach_path, mime_subtype,
149
157
def _compose(self, prompt, to, subject, attach_path, mime_subtype,
158
extension, body=None):
151
159
"""Invoke a mail client as a commandline process.
153
161
Overridden by MAPIClient.
174
187
raise errors.MailClientNotFound(self._client_commands)
176
def _get_compose_commandline(self, to, subject, attach_path):
189
def _get_compose_commandline(self, to, subject, attach_path, body):
177
190
"""Determine the commandline to use for composing a message
179
192
Implemented by various subclasses
215
class Evolution(ExternalMailClient):
228
class ExternalMailClient(BodyExternalMailClient):
229
"""An external mail client."""
231
supports_body = False
234
class Evolution(BodyExternalMailClient):
216
235
"""Evolution mail client."""
218
237
_client_commands = ['evolution']
220
def _get_compose_commandline(self, to, subject, attach_path):
239
def _get_compose_commandline(self, to, subject, attach_path, body=None):
221
240
"""See ExternalMailClient._get_compose_commandline"""
222
241
message_options = {}
223
242
if subject is not None:
224
243
message_options['subject'] = subject
225
244
if attach_path is not None:
226
245
message_options['attach'] = attach_path
247
message_options['body'] = body
227
248
options_list = ['%s=%s' % (k, urlutils.escape(v)) for (k, v) in
228
249
sorted(message_options.iteritems())]
229
250
return ['mailto:%s?%s' % (self._encode_safe(to or ''),
266
287
'/Applications/Mozilla/Thunderbird.app/Contents/MacOS/thunderbird-bin',
267
288
'/Applications/Thunderbird.app/Contents/MacOS/thunderbird-bin']
269
def _get_compose_commandline(self, to, subject, attach_path):
290
def _get_compose_commandline(self, to, subject, attach_path, body=None):
270
291
"""See ExternalMailClient._get_compose_commandline"""
271
292
message_options = {}
272
293
if to is not None:
276
297
if attach_path is not None:
277
298
message_options['attachment'] = urlutils.local_path_to_url(
279
options_list = ["%s='%s'" % (k, v) for k, v in
280
sorted(message_options.iteritems())]
301
options_list = ['body=%s' % urllib.quote(self._encode_safe(body))]
304
options_list.extend(["%s='%s'" % (k, v) for k, v in
305
sorted(message_options.iteritems())])
281
306
return ['-compose', ','.join(options_list)]
282
307
mail_client_registry.register('thunderbird', Thunderbird,
283
308
help=Thunderbird.__doc__)
329
354
help=Claws.__doc__)
332
class XDGEmail(ExternalMailClient):
357
class XDGEmail(BodyExternalMailClient):
333
358
"""xdg-email attempts to invoke the user's preferred mail client"""
335
360
_client_commands = ['xdg-email']
337
def _get_compose_commandline(self, to, subject, attach_path):
362
def _get_compose_commandline(self, to, subject, attach_path, body=None):
338
363
"""See ExternalMailClient._get_compose_commandline"""
340
365
raise errors.NoMailAddressSpecified()
344
369
if attach_path is not None:
345
370
commandline.extend(['--attach',
346
371
self._encode_path(attach_path, 'attachment')])
373
commandline.extend(['--body', self._encode_safe(body)])
347
374
return commandline
348
375
mail_client_registry.register('xdg-email', XDGEmail,
349
376
help=XDGEmail.__doc__)
391
418
(if (functionp 'etach-attach)
392
419
(etach-attach file)
393
420
(mail-attach-file file))))
394
((or (eq agent 'message-user-agent)(eq agent 'gnus-user-agent))
421
((or (eq agent 'message-user-agent)
422
(eq agent 'gnus-user-agent)
423
(eq agent 'mh-e-user-agent))
396
425
(mml-attach-file file "text/x-patch" "BZR merge" "inline")))
397
426
((eq agent 'mew-user-agent)
452
481
help=EmacsMail.__doc__)
455
class MAPIClient(ExternalMailClient):
484
class MAPIClient(BodyExternalMailClient):
456
485
"""Default Windows mail client launched using MAPI."""
458
487
def _compose(self, prompt, to, subject, attach_path, mime_subtype,
460
489
"""See ExternalMailClient._compose.
462
491
This implementation uses MAPI via the simplemapi ctypes wrapper
464
493
from bzrlib.util import simplemapi
466
simplemapi.SendMail(to or '', subject or '', '', attach_path)
495
simplemapi.SendMail(to or '', subject or '', body or '',
467
497
except simplemapi.MAPIError, e:
468
498
if e.code != simplemapi.MAPI_USER_ABORT:
469
499
raise errors.MailClientNotFound(['MAPI supported mail client'
484
514
return XDGEmail(self.config)
486
516
def compose(self, prompt, to, subject, attachment, mime_subtype,
487
extension, basename=None):
517
extension, basename=None, body=None):
488
518
"""See MailClient.compose"""
490
520
return self._mail_client().compose(prompt, to, subject,
491
521
attachment, mimie_subtype,
522
extension, basename, body)
493
523
except errors.MailClientNotFound:
494
524
return Editor(self.config).compose(prompt, to, subject,
495
attachment, mimie_subtype, extension)
525
attachment, mimie_subtype, extension, body)
497
def compose_merge_request(self, to, subject, directive, basename=None):
527
def compose_merge_request(self, to, subject, directive, basename=None,
498
529
"""See MailClient.compose_merge_request"""
500
531
return self._mail_client().compose_merge_request(to, subject,
501
directive, basename=basename)
532
directive, basename=basename, body=body)
502
533
except errors.MailClientNotFound:
503
534
return Editor(self.config).compose_merge_request(to, subject,
504
directive, basename=basename)
535
directive, basename=basename, body=body)
505
536
mail_client_registry.register('default', DefaultMail,
506
537
help=DefaultMail.__doc__)
507
538
mail_client_registry.default_key = 'default'