~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/mail_client.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-03-06 06:48:25 UTC
  • mfrom: (4070.8.6 debug-config)
  • Revision ID: pqm@pqm.ubuntu.com-20090306064825-kbpwggw21dygeix6
(mbp) debug_flags configuration option

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
import errno
18
18
import os
41
41
        self.config = config
42
42
 
43
43
    def compose(self, prompt, to, subject, attachment, mime_subtype,
44
 
                extension, basename=None, body=None):
 
44
                extension, basename=None):
45
45
        """Compose (and possibly send) an email message
46
46
 
47
47
        Must be implemented by subclasses.
61
61
        """
62
62
        raise NotImplementedError
63
63
 
64
 
    def compose_merge_request(self, to, subject, directive, basename=None,
65
 
                              body=None):
 
64
    def compose_merge_request(self, to, subject, directive, basename=None):
66
65
        """Compose (and possibly send) a merge request
67
66
 
68
67
        :param to: The address to send the request to
75
74
        prompt = self._get_merge_prompt("Please describe these changes:", to,
76
75
                                        subject, directive)
77
76
        self.compose(prompt, to, subject, directive,
78
 
            'x-patch', '.patch', basename, body)
 
77
            'x-patch', '.patch', basename)
79
78
 
80
79
    def _get_merge_prompt(self, prompt, to, subject, attachment):
81
80
        """Generate a prompt string.  Overridden by Editor.
91
90
class Editor(MailClient):
92
91
    """DIY mail client that uses commit message editor"""
93
92
 
94
 
    supports_body = True
95
 
 
96
93
    def _get_merge_prompt(self, prompt, to, subject, attachment):
97
94
        """See MailClient._get_merge_prompt"""
98
95
        return (u"%s\n\n"
102
99
                         attachment.decode('utf-8', 'replace')))
103
100
 
104
101
    def compose(self, prompt, to, subject, attachment, mime_subtype,
105
 
                extension, basename=None, body=None):
 
102
                extension, basename=None):
106
103
        """See MailClient.compose"""
107
104
        if not to:
108
105
            raise errors.NoMailAddressSpecified()
109
 
        body = msgeditor.edit_commit_message(prompt, start_message=body)
 
106
        body = msgeditor.edit_commit_message(prompt)
110
107
        if body == '':
111
108
            raise errors.NoMessageSupplied()
112
109
        email_message.EmailMessage.send(self.config,
120
117
                              help=Editor.__doc__)
121
118
 
122
119
 
123
 
class BodyExternalMailClient(MailClient):
124
 
 
125
 
    supports_body = True
 
120
class ExternalMailClient(MailClient):
 
121
    """An external mail client."""
126
122
 
127
123
    def _get_client_commands(self):
128
124
        """Provide a list of commands that may invoke the mail client"""
133
129
            return self._client_commands
134
130
 
135
131
    def compose(self, prompt, to, subject, attachment, mime_subtype,
136
 
                extension, basename=None, body=None):
 
132
                extension, basename=None):
137
133
        """See MailClient.compose.
138
134
 
139
135
        Writes the attachment to a temporary file, invokes _compose.
147
143
            outfile.write(attachment)
148
144
        finally:
149
145
            outfile.close()
150
 
        if body is not None:
151
 
            kwargs = {'body': body}
152
 
        else:
153
 
            kwargs = {}
154
146
        self._compose(prompt, to, subject, attach_path, mime_subtype,
155
 
                      extension, **kwargs)
 
147
                      extension)
156
148
 
157
149
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
158
 
                extension, body=None):
 
150
                extension):
159
151
        """Invoke a mail client as a commandline process.
160
152
 
161
153
        Overridden by MAPIClient.
169
161
        """
170
162
        for name in self._get_client_commands():
171
163
            cmdline = [self._encode_path(name, 'executable')]
172
 
            if body is not None:
173
 
                kwargs = {'body': body}
174
 
            else:
175
 
                kwargs = {}
176
164
            cmdline.extend(self._get_compose_commandline(to, subject,
177
 
                                                         attach_path,
178
 
                                                         **kwargs))
 
165
                                                         attach_path))
179
166
            try:
180
167
                subprocess.call(cmdline)
181
168
            except OSError, e:
186
173
        else:
187
174
            raise errors.MailClientNotFound(self._client_commands)
188
175
 
189
 
    def _get_compose_commandline(self, to, subject, attach_path, body):
 
176
    def _get_compose_commandline(self, to, subject, attach_path):
190
177
        """Determine the commandline to use for composing a message
191
178
 
192
179
        Implemented by various subclasses
225
212
        return path
226
213
 
227
214
 
228
 
class ExternalMailClient(BodyExternalMailClient):
229
 
    """An external mail client."""
230
 
 
231
 
    supports_body = False
232
 
 
233
 
 
234
 
class Evolution(BodyExternalMailClient):
 
215
class Evolution(ExternalMailClient):
235
216
    """Evolution mail client."""
236
217
 
237
218
    _client_commands = ['evolution']
238
219
 
239
 
    def _get_compose_commandline(self, to, subject, attach_path, body=None):
 
220
    def _get_compose_commandline(self, to, subject, attach_path):
240
221
        """See ExternalMailClient._get_compose_commandline"""
241
222
        message_options = {}
242
223
        if subject is not None:
243
224
            message_options['subject'] = subject
244
225
        if attach_path is not None:
245
226
            message_options['attach'] = attach_path
246
 
        if body is not None:
247
 
            message_options['body'] = body
248
227
        options_list = ['%s=%s' % (k, urlutils.escape(v)) for (k, v) in
249
228
                        sorted(message_options.iteritems())]
250
229
        return ['mailto:%s?%s' % (self._encode_safe(to or ''),
273
252
                              help=Mutt.__doc__)
274
253
 
275
254
 
276
 
class Thunderbird(BodyExternalMailClient):
 
255
class Thunderbird(ExternalMailClient):
277
256
    """Mozilla Thunderbird (or Icedove)
278
257
 
279
258
    Note that Thunderbird 1.5 is buggy and does not support setting
287
266
        '/Applications/Mozilla/Thunderbird.app/Contents/MacOS/thunderbird-bin',
288
267
        '/Applications/Thunderbird.app/Contents/MacOS/thunderbird-bin']
289
268
 
290
 
    def _get_compose_commandline(self, to, subject, attach_path, body=None):
 
269
    def _get_compose_commandline(self, to, subject, attach_path):
291
270
        """See ExternalMailClient._get_compose_commandline"""
292
271
        message_options = {}
293
272
        if to is not None:
297
276
        if attach_path is not None:
298
277
            message_options['attachment'] = urlutils.local_path_to_url(
299
278
                attach_path)
300
 
        if body is not None:
301
 
            options_list = ['body=%s' % urllib.quote(self._encode_safe(body))]
302
 
        else:
303
 
            options_list = []
304
 
        options_list.extend(["%s='%s'" % (k, v) for k, v in
305
 
                        sorted(message_options.iteritems())])
 
279
        options_list = ["%s='%s'" % (k, v) for k, v in
 
280
                        sorted(message_options.iteritems())]
306
281
        return ['-compose', ','.join(options_list)]
307
282
mail_client_registry.register('thunderbird', Thunderbird,
308
283
                              help=Thunderbird.__doc__)
354
329
                              help=Claws.__doc__)
355
330
 
356
331
 
357
 
class XDGEmail(BodyExternalMailClient):
 
332
class XDGEmail(ExternalMailClient):
358
333
    """xdg-email attempts to invoke the user's preferred mail client"""
359
334
 
360
335
    _client_commands = ['xdg-email']
361
336
 
362
 
    def _get_compose_commandline(self, to, subject, attach_path, body=None):
 
337
    def _get_compose_commandline(self, to, subject, attach_path):
363
338
        """See ExternalMailClient._get_compose_commandline"""
364
339
        if not to:
365
340
            raise errors.NoMailAddressSpecified()
369
344
        if attach_path is not None:
370
345
            commandline.extend(['--attach',
371
346
                self._encode_path(attach_path, 'attachment')])
372
 
        if body is not None:
373
 
            commandline.extend(['--body', self._encode_safe(body)])
374
347
        return commandline
375
348
mail_client_registry.register('xdg-email', XDGEmail,
376
349
                              help=XDGEmail.__doc__)
481
454
                              help=EmacsMail.__doc__)
482
455
 
483
456
 
484
 
class MAPIClient(BodyExternalMailClient):
 
457
class MAPIClient(ExternalMailClient):
485
458
    """Default Windows mail client launched using MAPI."""
486
459
 
487
460
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
488
 
                 extension, body):
 
461
                 extension):
489
462
        """See ExternalMailClient._compose.
490
463
 
491
464
        This implementation uses MAPI via the simplemapi ctypes wrapper
492
465
        """
493
466
        from bzrlib.util import simplemapi
494
467
        try:
495
 
            simplemapi.SendMail(to or '', subject or '', body or '',
496
 
                                attach_path)
 
468
            simplemapi.SendMail(to or '', subject or '', '', attach_path)
497
469
        except simplemapi.MAPIError, e:
498
470
            if e.code != simplemapi.MAPI_USER_ABORT:
499
471
                raise errors.MailClientNotFound(['MAPI supported mail client'
514
486
            return XDGEmail(self.config)
515
487
 
516
488
    def compose(self, prompt, to, subject, attachment, mime_subtype,
517
 
                extension, basename=None, body=None):
 
489
                extension, basename=None):
518
490
        """See MailClient.compose"""
519
491
        try:
520
492
            return self._mail_client().compose(prompt, to, subject,
521
493
                                               attachment, mimie_subtype,
522
 
                                               extension, basename, body)
 
494
                                               extension, basename)
523
495
        except errors.MailClientNotFound:
524
496
            return Editor(self.config).compose(prompt, to, subject,
525
 
                          attachment, mimie_subtype, extension, body)
 
497
                          attachment, mimie_subtype, extension)
526
498
 
527
 
    def compose_merge_request(self, to, subject, directive, basename=None,
528
 
                              body=None):
 
499
    def compose_merge_request(self, to, subject, directive, basename=None):
529
500
        """See MailClient.compose_merge_request"""
530
501
        try:
531
502
            return self._mail_client().compose_merge_request(to, subject,
532
 
                    directive, basename=basename, body=body)
 
503
                    directive, basename=basename)
533
504
        except errors.MailClientNotFound:
534
505
            return Editor(self.config).compose_merge_request(to, subject,
535
 
                          directive, basename=basename, body=body)
 
506
                          directive, basename=basename)
536
507
mail_client_registry.register('default', DefaultMail,
537
508
                              help=DefaultMail.__doc__)
538
509
mail_client_registry.default_key = 'default'