/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/mail_client.py

  • Committer: Lukáš Lalinsky
  • Date: 2007-08-12 21:08:27 UTC
  • mto: (2681.1.26 send-bundle)
  • mto: This revision was merged to the branch mainline in revision 2736.
  • Revision ID: lalinsky@gmail.com-20070812210827-sfljgm5tvv6f0k9j
New version of simplemapi.py with MIT license.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
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
 
16
 
 
17
import errno
 
18
import os
 
19
import subprocess
 
20
import sys
 
21
import tempfile
 
22
 
 
23
from bzrlib import (
 
24
    email_message,
 
25
    errors,
 
26
    msgeditor,
 
27
    osutils,
 
28
    urlutils,
 
29
    )
 
30
 
 
31
 
 
32
class MailClient(object):
 
33
    """A mail client that can send messages with attachements."""
 
34
 
 
35
    def __init__(self, config):
 
36
        self.config = config
 
37
 
 
38
    def compose(self, prompt, to, subject, attachment, mime_subtype,
 
39
                extension):
 
40
        raise NotImplementedError
 
41
 
 
42
    def compose_merge_request(self, to, subject, directive):
 
43
        prompt = self._get_merge_prompt("Please describe these changes:", to,
 
44
                                        subject, directive)
 
45
        self.compose(prompt, to, subject, directive,
 
46
            'x-patch', '.patch')
 
47
 
 
48
    def _get_merge_prompt(self, prompt, to, subject, attachment):
 
49
        return ''
 
50
 
 
51
 
 
52
class Editor(MailClient):
 
53
    """DIY mail client that uses commit message editor"""
 
54
 
 
55
    def _get_merge_prompt(self, prompt, to, subject, attachment):
 
56
        return "%s\n\nTo: %s\nSubject: %s\n\n%s" % (prompt, to, subject,
 
57
                attachment.decode('utf-8', 'replace'))
 
58
 
 
59
    def compose(self, prompt, to, subject, attachment, mime_subtype,
 
60
                extension):
 
61
        body = msgeditor.edit_commit_message(prompt)
 
62
        if body == '':
 
63
            raise errors.NoMessageSupplied()
 
64
        email_message.EmailMessage.send(self.config,
 
65
                                        self.config.username(),
 
66
                                        to,
 
67
                                        subject,
 
68
                                        body,
 
69
                                        attachment,
 
70
                                        attachment_mime_subtype=mime_subtype)
 
71
 
 
72
 
 
73
class ExternalMailClient(MailClient):
 
74
    """An external mail client."""
 
75
 
 
76
    def compose(self, prompt, to, subject, attachment, mime_subtype,
 
77
                extension):
 
78
        fd, pathname = tempfile.mkstemp(extension, 'bzr-mail-')
 
79
        try:
 
80
            os.write(fd, attachment)
 
81
        finally:
 
82
            os.close(fd)
 
83
        self._compose(prompt, to, subject, pathname, mime_subtype, extension)
 
84
 
 
85
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
 
86
                extension):
 
87
        for name in self._client_commands:
 
88
            cmdline = [name]
 
89
            cmdline.extend(self._get_compose_commandline(to, subject, 
 
90
                                                         attach_path))
 
91
            try:
 
92
                subprocess.call(cmdline)
 
93
            except OSError, e:
 
94
                if e.errno != errno.ENOENT:
 
95
                    raise
 
96
            else:
 
97
                break
 
98
        else:
 
99
            raise errors.MailClientNotFound(self._client_commands)
 
100
 
 
101
    def _get_compose_commandline(self, to, subject, attach_path):
 
102
        raise NotImplementedError
 
103
 
 
104
 
 
105
class Evolution(ExternalMailClient):
 
106
    """Evolution mail client."""
 
107
 
 
108
    _client_commands = ['evolution']
 
109
 
 
110
    def _get_compose_commandline(self, to, subject, attach_path):
 
111
        message_options = {}
 
112
        if subject is not None:
 
113
            message_options['subject'] = subject
 
114
        if attach_path is not None:
 
115
            message_options['attach'] = attach_path
 
116
        options_list = ['%s=%s' % (k, urlutils.escape(v)) for (k, v) in
 
117
                        message_options.iteritems()]
 
118
        return ['mailto:%s?%s' % (to or '', '&'.join(options_list))]
 
119
 
 
120
 
 
121
class Thunderbird(ExternalMailClient):
 
122
    """Mozilla Thunderbird (or Icedove)
 
123
 
 
124
    Note that Thunderbird 1.5 is buggy and does not support setting
 
125
    "to" simultaneously with including a attachment.
 
126
 
 
127
    There is a workaround if no attachment is present, but we always need to
 
128
    send attachments.
 
129
    """
 
130
 
 
131
    _client_commands = ['thunderbird', 'mozilla-thunderbird', 'icedove']
 
132
 
 
133
    def _get_compose_commandline(self, to, subject, attach_path):
 
134
        message_options = {}
 
135
        if to is not None:
 
136
            message_options['to'] = to
 
137
        if subject is not None:
 
138
            message_options['subject'] = subject
 
139
        if attach_path is not None:
 
140
            message_options['attachment'] = urlutils.local_path_to_url(
 
141
                attach_path)
 
142
        options_list = ["%s='%s'" % (k, v) for k, v in
 
143
                        sorted(message_options.iteritems())]
 
144
        return ['-compose', ','.join(options_list)]
 
145
 
 
146
 
 
147
class XDGEmail(ExternalMailClient):
 
148
    """xdg-email attempts to invoke the user's preferred mail client"""
 
149
 
 
150
    _client_commands = ['xdg-email']
 
151
 
 
152
    def _get_compose_commandline(self, to, subject, attach_path):
 
153
        commandline = [to]
 
154
        if subject is not None:
 
155
            commandline.extend(['--subject', subject])
 
156
        if attach_path is not None:
 
157
            commandline.extend(['--attach', attach_path])
 
158
        return commandline
 
159
 
 
160
 
 
161
class MAPIClient(ExternalMailClient):
 
162
    """Default Windows mail client launched using MAPI."""
 
163
 
 
164
    def _compose(self, prompt, to, subject, attach_path, mime_subtype,
 
165
                 extension):
 
166
        from bzrlib.util import simplemapi
 
167
        try:
 
168
            simplemapi.SendMail(to or '', subject or '', '', attach_path)
 
169
        except simplemapi.MAPIError, e:
 
170
            if e.code != simplemapi.MAPI_USER_ABORT:
 
171
                raise errors.MailClientNotFound(['MAPI supported mail client'
 
172
                                                 ' (error %d)' % (e.code,)])
 
173
 
 
174
 
 
175
class DefaultMail(MailClient):
 
176
    """Default mail handling.  Tries XDGEmail (or MAPIClient on Windows),
 
177
    falls back to Editor"""
 
178
 
 
179
    def _mail_client(self):
 
180
        if osutils.supports_mapi():
 
181
            return MAPIClient(self.config)
 
182
        else:
 
183
            return XDGEmail(self.config)
 
184
 
 
185
    def compose(self, prompt, to, subject, attachment, mime_subtype,
 
186
                extension):
 
187
        try:
 
188
            return self._mail_client().compose(prompt, to, subject,
 
189
                                               attachment, mimie_subtype,
 
190
                                               extension)
 
191
        except errors.MailClientNotFound:
 
192
            return Editor(self.config).compose(prompt, to, subject,
 
193
                          attachment, mimie_subtype, extension)
 
194
 
 
195
    def compose_merge_request(self, to, subject, directive):
 
196
        try:
 
197
            return self._mail_client().compose_merge_request(to, subject,
 
198
                                                             directive)
 
199
        except errors.MailClientNotFound:
 
200
            return Editor(self.config).compose_merge_request(to, subject,
 
201
                          directive)