/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/transport/memory.py

  • Committer: Adeodato Simó
  • Date: 2007-07-18 15:51:52 UTC
  • mto: (2639.1.1 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 2640.
  • Revision ID: dato@net.com.org.es-20070718155152-pv6rwq41eckqyxem
New EmailMessage class, façade around email.Message and MIMEMultipart.

* bzrlib/email_message.py,
  bzrlib/tests/test_email_message.py:
  New files.

* bzrlib/tests/__init__.py:
  (test_suite): add bzrlib.tests.test_email_message.

* bzrlib/merge_directive.py:
  (MergeDirective.to_email): Use EmailMessage instead of email.Message.

* bzrlib/tests/test_merge_directive.py,
  bzrlib/tests/blackbox/test_merge_directive.py:
  (__main__): adjust EMAIL1 and EMAIL2 strings to how EmailMessage
  formats itself.

* bzrlib/smtp_connection.py:
  (SMTPConnection.get_message_addresses): do not use methods present in
  email.Message but not in EmailMessage (get_all). Use get() instead of
  __getitem__ to make explicit that None is returned if the header does
  not exist.

* bzrlib/tests/test_smtp_connection.py:
  (TestSMTPConnection.test_get_message_addresses, 
   TestSMTPConnection.test_destination_address_required): test the
   functions against EmailMessage in addition to email.Message.

* NEWS:
  Mention EmailMessage in INTERNALS.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 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
"""Implementation of Transport that uses memory for its storage.
 
18
 
 
19
The contents of the transport will be lost when the object is discarded,
 
20
so this is primarily useful for testing.
 
21
"""
 
22
 
 
23
import os
 
24
import errno
 
25
import re
 
26
from stat import S_IFREG, S_IFDIR
 
27
from cStringIO import StringIO
 
28
import warnings
 
29
 
 
30
from bzrlib.errors import TransportError, NoSuchFile, FileExists, LockError
 
31
from bzrlib.trace import mutter
 
32
from bzrlib.transport import (
 
33
    LateReadError,
 
34
    register_transport,
 
35
    Server,
 
36
    Transport,
 
37
    )
 
38
import bzrlib.urlutils as urlutils
 
39
 
 
40
 
 
41
 
 
42
class MemoryStat(object):
 
43
 
 
44
    def __init__(self, size, is_dir, perms):
 
45
        self.st_size = size
 
46
        if not is_dir:
 
47
            if perms is None:
 
48
                perms = 0644
 
49
            self.st_mode = S_IFREG | perms
 
50
        else:
 
51
            if perms is None:
 
52
                perms = 0755
 
53
            self.st_mode = S_IFDIR | perms
 
54
 
 
55
 
 
56
class MemoryTransport(Transport):
 
57
    """This is an in memory file system for transient data storage."""
 
58
 
 
59
    def __init__(self, url=""):
 
60
        """Set the 'base' path where files will be stored."""
 
61
        if url == "":
 
62
            url = "memory:///"
 
63
        if url[-1] != '/':
 
64
            url = url + '/'
 
65
        super(MemoryTransport, self).__init__(url)
 
66
        split = url.find(':') + 3
 
67
        self._scheme = url[:split]
 
68
        self._cwd = url[split:]
 
69
        # dictionaries from absolute path to file mode
 
70
        self._dirs = {'/':None}
 
71
        self._files = {}
 
72
        self._locks = {}
 
73
 
 
74
    def clone(self, offset=None):
 
75
        """See Transport.clone()."""
 
76
        path = self._combine_paths(self._cwd, offset)
 
77
        if len(path) == 0 or path[-1] != '/':
 
78
            path += '/'
 
79
        url = self._scheme + path
 
80
        result = MemoryTransport(url)
 
81
        result._dirs = self._dirs
 
82
        result._files = self._files
 
83
        result._locks = self._locks
 
84
        return result
 
85
 
 
86
    def abspath(self, relpath):
 
87
        """See Transport.abspath()."""
 
88
        # while a little slow, this is sufficiently fast to not matter in our
 
89
        # current environment - XXX RBC 20060404 move the clone '..' handling
 
90
        # into here and call abspath from clone
 
91
        temp_t = self.clone(relpath)
 
92
        if temp_t.base.count('/') == 3:
 
93
            return temp_t.base
 
94
        else:
 
95
            return temp_t.base[:-1]
 
96
 
 
97
    def append_file(self, relpath, f, mode=None):
 
98
        """See Transport.append_file()."""
 
99
        _abspath = self._abspath(relpath)
 
100
        self._check_parent(_abspath)
 
101
        orig_content, orig_mode = self._files.get(_abspath, ("", None))
 
102
        if mode is None:
 
103
            mode = orig_mode
 
104
        self._files[_abspath] = (orig_content + f.read(), mode)
 
105
        return len(orig_content)
 
106
 
 
107
    def _check_parent(self, _abspath):
 
108
        dir = os.path.dirname(_abspath)
 
109
        if dir != '/':
 
110
            if not dir in self._dirs:
 
111
                raise NoSuchFile(_abspath)
 
112
 
 
113
    def has(self, relpath):
 
114
        """See Transport.has()."""
 
115
        _abspath = self._abspath(relpath)
 
116
        return (_abspath in self._files) or (_abspath in self._dirs)
 
117
 
 
118
    def delete(self, relpath):
 
119
        """See Transport.delete()."""
 
120
        _abspath = self._abspath(relpath)
 
121
        if not _abspath in self._files:
 
122
            raise NoSuchFile(relpath)
 
123
        del self._files[_abspath]
 
124
 
 
125
    def get(self, relpath):
 
126
        """See Transport.get()."""
 
127
        _abspath = self._abspath(relpath)
 
128
        if not _abspath in self._files:
 
129
            if _abspath in self._dirs:
 
130
                return LateReadError(relpath)
 
131
            else:
 
132
                raise NoSuchFile(relpath)
 
133
        return StringIO(self._files[_abspath][0])
 
134
 
 
135
    def put_file(self, relpath, f, mode=None):
 
136
        """See Transport.put_file()."""
 
137
        _abspath = self._abspath(relpath)
 
138
        self._check_parent(_abspath)
 
139
        bytes = f.read()
 
140
        if type(bytes) is not str:
 
141
            # Although not strictly correct, we raise UnicodeEncodeError to be
 
142
            # compatible with other transports.
 
143
            raise UnicodeEncodeError(
 
144
                'undefined', bytes, 0, 1,
 
145
                'put_file must be given a file of bytes, not unicode.')
 
146
        self._files[_abspath] = (bytes, mode)
 
147
 
 
148
    def mkdir(self, relpath, mode=None):
 
149
        """See Transport.mkdir()."""
 
150
        _abspath = self._abspath(relpath)
 
151
        self._check_parent(_abspath)
 
152
        if _abspath in self._dirs:
 
153
            raise FileExists(relpath)
 
154
        self._dirs[_abspath]=mode
 
155
 
 
156
    def listable(self):
 
157
        """See Transport.listable."""
 
158
        return True
 
159
 
 
160
    def iter_files_recursive(self):
 
161
        for file in self._files:
 
162
            if file.startswith(self._cwd):
 
163
                yield urlutils.escape(file[len(self._cwd):])
 
164
    
 
165
    def list_dir(self, relpath):
 
166
        """See Transport.list_dir()."""
 
167
        _abspath = self._abspath(relpath)
 
168
        if _abspath != '/' and _abspath not in self._dirs:
 
169
            raise NoSuchFile(relpath)
 
170
        result = []
 
171
 
 
172
        if not _abspath.endswith('/'):
 
173
            _abspath += '/'
 
174
 
 
175
        for path_group in self._files, self._dirs:
 
176
            for path in path_group:
 
177
                if path.startswith(_abspath):
 
178
                    trailing = path[len(_abspath):]
 
179
                    if trailing and '/' not in trailing:
 
180
                        result.append(trailing)
 
181
        return map(urlutils.escape, result)
 
182
 
 
183
    def rename(self, rel_from, rel_to):
 
184
        """Rename a file or directory; fail if the destination exists"""
 
185
        abs_from = self._abspath(rel_from)
 
186
        abs_to = self._abspath(rel_to)
 
187
        def replace(x):
 
188
            if x == abs_from:
 
189
                x = abs_to
 
190
            elif x.startswith(abs_from + '/'):
 
191
                x = abs_to + x[len(abs_from):]
 
192
            return x
 
193
        def do_renames(container):
 
194
            for path in container:
 
195
                new_path = replace(path)
 
196
                if new_path != path:
 
197
                    if new_path in container:
 
198
                        raise FileExists(new_path)
 
199
                    container[new_path] = container[path]
 
200
                    del container[path]
 
201
        do_renames(self._files)
 
202
        do_renames(self._dirs)
 
203
    
 
204
    def rmdir(self, relpath):
 
205
        """See Transport.rmdir."""
 
206
        _abspath = self._abspath(relpath)
 
207
        if _abspath in self._files:
 
208
            self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
 
209
        for path in self._files:
 
210
            if path.startswith(_abspath + '/'):
 
211
                self._translate_error(IOError(errno.ENOTEMPTY, relpath),
 
212
                                      relpath)
 
213
        for path in self._dirs:
 
214
            if path.startswith(_abspath + '/') and path != _abspath:
 
215
                self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
 
216
        if not _abspath in self._dirs:
 
217
            raise NoSuchFile(relpath)
 
218
        del self._dirs[_abspath]
 
219
 
 
220
    def stat(self, relpath):
 
221
        """See Transport.stat()."""
 
222
        _abspath = self._abspath(relpath)
 
223
        if _abspath in self._files:
 
224
            return MemoryStat(len(self._files[_abspath][0]), False, 
 
225
                              self._files[_abspath][1])
 
226
        elif _abspath in self._dirs:
 
227
            return MemoryStat(0, True, self._dirs[_abspath])
 
228
        else:
 
229
            raise NoSuchFile(_abspath)
 
230
 
 
231
    def lock_read(self, relpath):
 
232
        """See Transport.lock_read()."""
 
233
        return _MemoryLock(self._abspath(relpath), self)
 
234
 
 
235
    def lock_write(self, relpath):
 
236
        """See Transport.lock_write()."""
 
237
        return _MemoryLock(self._abspath(relpath), self)
 
238
 
 
239
    def _abspath(self, relpath):
 
240
        """Generate an internal absolute path."""
 
241
        relpath = urlutils.unescape(relpath)
 
242
        if relpath.find('..') != -1:
 
243
            raise AssertionError('relpath contains ..')
 
244
        if relpath == '':
 
245
            return '/'
 
246
        if relpath[0] == '/':
 
247
            return relpath
 
248
        if relpath == '.':
 
249
            if (self._cwd == '/'):
 
250
                return self._cwd
 
251
            return self._cwd[:-1]
 
252
        if relpath.endswith('/'):
 
253
            relpath = relpath[:-1]
 
254
        if relpath.startswith('./'):
 
255
            relpath = relpath[2:]
 
256
        return self._cwd + relpath
 
257
 
 
258
 
 
259
class _MemoryLock(object):
 
260
    """This makes a lock."""
 
261
 
 
262
    def __init__(self, path, transport):
 
263
        assert isinstance(transport, MemoryTransport)
 
264
        self.path = path
 
265
        self.transport = transport
 
266
        if self.path in self.transport._locks:
 
267
            raise LockError('File %r already locked' % (self.path,))
 
268
        self.transport._locks[self.path] = self
 
269
 
 
270
    def __del__(self):
 
271
        # Should this warn, or actually try to cleanup?
 
272
        if self.transport:
 
273
            warnings.warn("MemoryLock %r not explicitly unlocked" % (self.path,))
 
274
            self.unlock()
 
275
 
 
276
    def unlock(self):
 
277
        del self.transport._locks[self.path]
 
278
        self.transport = None
 
279
 
 
280
 
 
281
class MemoryServer(Server):
 
282
    """Server for the MemoryTransport for testing with."""
 
283
 
 
284
    def setUp(self):
 
285
        """See bzrlib.transport.Server.setUp."""
 
286
        self._dirs = {'/':None}
 
287
        self._files = {}
 
288
        self._locks = {}
 
289
        self._scheme = "memory+%s:///" % id(self)
 
290
        def memory_factory(url):
 
291
            result = MemoryTransport(url)
 
292
            result._dirs = self._dirs
 
293
            result._files = self._files
 
294
            result._locks = self._locks
 
295
            return result
 
296
        register_transport(self._scheme, memory_factory)
 
297
 
 
298
    def tearDown(self):
 
299
        """See bzrlib.transport.Server.tearDown."""
 
300
        # unregister this server
 
301
 
 
302
    def get_url(self):
 
303
        """See bzrlib.transport.Server.get_url."""
 
304
        return self._scheme
 
305
 
 
306
 
 
307
def get_test_permutations():
 
308
    """Return the permutations to be used in testing."""
 
309
    return [(MemoryTransport, MemoryServer),
 
310
            ]