~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/mail_client.py

  • Committer: Andrew Bennetts
  • Date: 2008-03-27 06:10:18 UTC
  • mfrom: (3309 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3320.
  • Revision ID: andrew.bennetts@canonical.com-20080327061018-dxztpxyv6yoeg3am
Merge from bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
import sys
21
21
import tempfile
22
22
 
 
23
import bzrlib
23
24
from bzrlib import (
24
25
    email_message,
25
26
    errors,
36
37
        self.config = config
37
38
 
38
39
    def compose(self, prompt, to, subject, attachment, mime_subtype,
39
 
                extension):
 
40
                extension, basename=None):
40
41
        """Compose (and possibly send) an email message
41
42
 
42
43
        Must be implemented by subclasses.
51
52
            "plain", "x-patch", etc.
52
53
        :param extension: The file extension associated with the attachment
53
54
            type, e.g. ".patch"
 
55
        :param basename: The name to use for the attachment, e.g.
 
56
            "send-nick-3252"
54
57
        """
55
58
        raise NotImplementedError
56
59
 
57
 
    def compose_merge_request(self, to, subject, directive):
 
60
    def compose_merge_request(self, to, subject, directive, basename=None):
58
61
        """Compose (and possibly send) a merge request
59
62
 
60
63
        :param to: The address to send the request to
61
64
        :param subject: The subject line to use for the request
62
65
        :param directive: A merge directive representing the merge request, as
63
66
            a bytestring.
 
67
        :param basename: The name to use for the attachment, e.g.
 
68
            "send-nick-3252"
64
69
        """
65
70
        prompt = self._get_merge_prompt("Please describe these changes:", to,
66
71
                                        subject, directive)
67
72
        self.compose(prompt, to, subject, directive,
68
 
            'x-patch', '.patch')
 
73
            'x-patch', '.patch', basename)
69
74
 
70
75
    def _get_merge_prompt(self, prompt, to, subject, attachment):
71
76
        """Generate a prompt string.  Overridden by Editor.
90
95
                         attachment.decode('utf-8', 'replace')))
91
96
 
92
97
    def compose(self, prompt, to, subject, attachment, mime_subtype,
93
 
                extension):
 
98
                extension, basename=None):
94
99
        """See MailClient.compose"""
95
100
        if not to:
96
101
            raise errors.NoMailAddressSpecified()
118
123
            return self._client_commands
119
124
 
120
125
    def compose(self, prompt, to, subject, attachment, mime_subtype,
121
 
                extension):
 
126
                extension, basename=None):
122
127
        """See MailClient.compose.
123
128
 
124
129
        Writes the attachment to a temporary file, invokes _compose.
125
130
        """
126
 
        fd, pathname = tempfile.mkstemp(extension, 'bzr-mail-')
 
131
        if basename is None:
 
132
            basename = 'attachment'
 
133
        pathname = tempfile.mkdtemp(prefix='bzr-mail-')
 
134
        attach_path = osutils.pathjoin(pathname, basename + extension)
 
135
        outfile = open(attach_path, 'wb')
127
136
        try:
128
 
            os.write(fd, attachment)
 
137
            outfile.write(attachment)
129
138
        finally:
130
 
            os.close(fd)
131
 
        self._compose(prompt, to, subject, pathname, mime_subtype, extension)
 
139
            outfile.close()
 
140
        self._compose(prompt, to, subject, attach_path, mime_subtype,
 
141
                      extension)
132
142
 
133
143
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
134
144
                extension):
144
154
            the attachment type.
145
155
        """
146
156
        for name in self._get_client_commands():
147
 
            cmdline = [name]
 
157
            cmdline = [self._encode_path(name, 'executable')]
148
158
            cmdline.extend(self._get_compose_commandline(to, subject,
149
159
                                                         attach_path))
150
160
            try:
167
177
        """
168
178
        raise NotImplementedError
169
179
 
 
180
    def _encode_safe(self, u):
 
181
        """Encode possible unicode string argument to 8-bit string
 
182
        in user_encoding. Unencodable characters will be replaced
 
183
        with '?'.
 
184
 
 
185
        :param  u:  possible unicode string.
 
186
        :return:    encoded string if u is unicode, u itself otherwise.
 
187
        """
 
188
        if isinstance(u, unicode):
 
189
            return u.encode(bzrlib.user_encoding, 'replace')
 
190
        return u
 
191
 
 
192
    def _encode_path(self, path, kind):
 
193
        """Encode unicode path in user encoding.
 
194
 
 
195
        :param  path:   possible unicode path.
 
196
        :param  kind:   path kind ('executable' or 'attachment').
 
197
        :return:        encoded path if path is unicode,
 
198
                        path itself otherwise.
 
199
        :raise:         UnableEncodePath.
 
200
        """
 
201
        if isinstance(path, unicode):
 
202
            try:
 
203
                return path.encode(bzrlib.user_encoding)
 
204
            except UnicodeEncodeError:
 
205
                raise errors.UnableEncodePath(path, kind)
 
206
        return path
 
207
 
170
208
 
171
209
class Evolution(ExternalMailClient):
172
210
    """Evolution mail client."""
181
219
        if attach_path is not None:
182
220
            message_options['attach'] = attach_path
183
221
        options_list = ['%s=%s' % (k, urlutils.escape(v)) for (k, v) in
184
 
                        message_options.iteritems()]
185
 
        return ['mailto:%s?%s' % (to or '', '&'.join(options_list))]
 
222
                        sorted(message_options.iteritems())]
 
223
        return ['mailto:%s?%s' % (self._encode_safe(to or ''),
 
224
            '&'.join(options_list))]
186
225
 
187
226
 
188
227
class Mutt(ExternalMailClient):
194
233
        """See ExternalMailClient._get_compose_commandline"""
195
234
        message_options = []
196
235
        if subject is not None:
197
 
            message_options.extend(['-s', subject ])
 
236
            message_options.extend(['-s', self._encode_safe(subject)])
198
237
        if attach_path is not None:
199
 
            message_options.extend(['-a', attach_path])
 
238
            message_options.extend(['-a',
 
239
                self._encode_path(attach_path, 'attachment')])
200
240
        if to is not None:
201
 
            message_options.append(to)
 
241
            message_options.append(self._encode_safe(to))
202
242
        return message_options
203
243
 
204
244
 
219
259
        """See ExternalMailClient._get_compose_commandline"""
220
260
        message_options = {}
221
261
        if to is not None:
222
 
            message_options['to'] = to
 
262
            message_options['to'] = self._encode_safe(to)
223
263
        if subject is not None:
224
 
            message_options['subject'] = subject
 
264
            message_options['subject'] = self._encode_safe(subject)
225
265
        if attach_path is not None:
226
266
            message_options['attachment'] = urlutils.local_path_to_url(
227
267
                attach_path)
239
279
        """See ExternalMailClient._get_compose_commandline"""
240
280
        message_options = []
241
281
        if subject is not None:
242
 
            message_options.extend( ['-s', subject ] )
 
282
            message_options.extend(['-s', self._encode_safe(subject)])
243
283
        if attach_path is not None:
244
 
            message_options.extend( ['--attach', attach_path] )
 
284
            message_options.extend(['--attach',
 
285
                self._encode_path(attach_path, 'attachment')])
245
286
        if to is not None:
246
 
            message_options.extend( [ to ] )
247
 
 
 
287
            message_options.extend([self._encode_safe(to)])
248
288
        return message_options
249
289
 
250
290
 
257
297
        """See ExternalMailClient._get_compose_commandline"""
258
298
        if not to:
259
299
            raise errors.NoMailAddressSpecified()
260
 
        commandline = [to]
 
300
        commandline = [self._encode_safe(to)]
261
301
        if subject is not None:
262
 
            commandline.extend(['--subject', subject])
 
302
            commandline.extend(['--subject', self._encode_safe(subject)])
263
303
        if attach_path is not None:
264
 
            commandline.extend(['--attach', attach_path])
 
304
            commandline.extend(['--attach',
 
305
                self._encode_path(attach_path, 'attachment')])
265
306
        return commandline
266
307
 
267
308
 
295
336
            return XDGEmail(self.config)
296
337
 
297
338
    def compose(self, prompt, to, subject, attachment, mime_subtype,
298
 
                extension):
 
339
                extension, basename=None):
299
340
        """See MailClient.compose"""
300
341
        try:
301
342
            return self._mail_client().compose(prompt, to, subject,
302
343
                                               attachment, mimie_subtype,
303
 
                                               extension)
 
344
                                               extension, basename)
304
345
        except errors.MailClientNotFound:
305
346
            return Editor(self.config).compose(prompt, to, subject,
306
347
                          attachment, mimie_subtype, extension)
307
348
 
308
 
    def compose_merge_request(self, to, subject, directive):
 
349
    def compose_merge_request(self, to, subject, directive, basename=None):
309
350
        """See MailClient.compose_merge_request"""
310
351
        try:
311
352
            return self._mail_client().compose_merge_request(to, subject,
312
 
                                                             directive)
 
353
                    directive, basename=basename)
313
354
        except errors.MailClientNotFound:
314
355
            return Editor(self.config).compose_merge_request(to, subject,
315
 
                          directive)
 
356
                          directive, basename=basename)