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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Implementation of Transport that uses memory for its storage.
19
The contents of the transport will be lost when the object is discarded,
20
so this is primarily useful for testing.
16
"""Implementation of Transport that uses memory for its storage."""
27
from stat import S_IFREG, S_IFDIR
28
20
from cStringIO import StringIO
31
from bzrlib.errors import TransportError, NoSuchFile, FileExists, LockError
32
22
from bzrlib.trace import mutter
33
from bzrlib.transport import (Transport, register_transport, Server)
34
import bzrlib.urlutils as urlutils
23
from bzrlib.transport import Transport, \
24
TransportError, NoSuchFile, FileExists
38
27
class MemoryStat(object):
40
def __init__(self, size, is_dir, perms):
29
def __init__(self, size):
41
30
self.st_size = size
45
self.st_mode = S_IFREG | perms
49
self.st_mode = S_IFDIR | perms
52
33
class MemoryTransport(Transport):
53
"""This is an in memory file system for transient data storage."""
34
"""This is the transport agent for local filesystem access."""
55
def __init__(self, url=""):
56
37
"""Set the 'base' path where files will be stored."""
61
super(MemoryTransport, self).__init__(url)
62
self._cwd = url[url.find(':') + 3:]
63
# dictionaries from absolute path to file mode
64
self._dirs = {'/':None}
38
super(MemoryTransport, self).__init__('in-memory:')
68
42
def clone(self, offset=None):
69
43
"""See Transport.clone()."""
70
if offset is None or offset == '':
72
segments = offset.split('/')
73
cwdsegments = self._cwd.split('/')[:-1]
75
segment = segments.pop(0)
79
if len(cwdsegments) > 1:
82
cwdsegments.append(segment)
83
url = self.base[:self.base.find(':') + 3] + '/'.join(cwdsegments) + '/'
84
result = MemoryTransport(url)
85
result._dirs = self._dirs
86
result._files = self._files
87
result._locks = self._locks
90
46
def abspath(self, relpath):
91
47
"""See Transport.abspath()."""
92
# while a little slow, this is sufficiently fast to not matter in our
93
# current environment - XXX RBC 20060404 move the clone '..' handling
94
# into here and call abspath from clone
95
temp_t = self.clone(relpath)
96
if temp_t.base.count('/') == 3:
99
return temp_t.base[:-1]
48
return self.base + relpath
101
def append(self, relpath, f, mode=None):
50
def append(self, relpath, f):
102
51
"""See Transport.append()."""
103
_abspath = self._abspath(relpath)
104
self._check_parent(_abspath)
105
orig_content, orig_mode = self._files.get(_abspath, ("", None))
108
self._files[_abspath] = (orig_content + f.read(), mode)
109
return len(orig_content)
52
self._check_parent(relpath)
53
self._files[relpath] = self._files.get(relpath, "") + f.read()
111
def _check_parent(self, _abspath):
112
dir = os.path.dirname(_abspath)
55
def _check_parent(self, relpath):
56
dir = os.path.dirname(relpath)
114
58
if not dir in self._dirs:
115
raise NoSuchFile(_abspath)
59
raise NoSuchFile(relpath)
117
61
def has(self, relpath):
118
62
"""See Transport.has()."""
119
_abspath = self._abspath(relpath)
120
return _abspath in self._files or _abspath in self._dirs
122
def delete(self, relpath):
123
"""See Transport.delete()."""
124
_abspath = self._abspath(relpath)
125
if not _abspath in self._files:
126
raise NoSuchFile(relpath)
127
del self._files[_abspath]
63
return relpath in self._files
129
65
def get(self, relpath):
130
66
"""See Transport.get()."""
131
_abspath = self._abspath(relpath)
132
if not _abspath in self._files:
67
if not relpath in self._files:
133
68
raise NoSuchFile(relpath)
134
return StringIO(self._files[_abspath][0])
69
return StringIO(self._files[relpath])
136
def put(self, relpath, f, mode=None):
71
def put(self, relpath, f):
137
72
"""See Transport.put()."""
138
_abspath = self._abspath(relpath)
139
self._check_parent(_abspath)
140
self._files[_abspath] = (f.read(), mode)
73
self._check_parent(relpath)
74
self._files[relpath] = f.read()
142
def mkdir(self, relpath, mode=None):
76
def mkdir(self, relpath):
143
77
"""See Transport.mkdir()."""
144
_abspath = self._abspath(relpath)
145
self._check_parent(_abspath)
146
if _abspath in self._dirs:
78
self._check_parent(relpath)
79
if relpath in self._dirs:
147
80
raise FileExists(relpath)
148
self._dirs[_abspath]=mode
81
self._dirs.add(relpath)
150
83
def listable(self):
151
84
"""See Transport.listable."""
154
87
def iter_files_recursive(self):
155
for file in self._files:
156
if file.startswith(self._cwd):
157
yield file[len(self._cwd):]
159
def list_dir(self, relpath):
160
"""See Transport.list_dir()."""
161
_abspath = self._abspath(relpath)
162
if _abspath != '/' and _abspath not in self._dirs:
163
raise NoSuchFile(relpath)
165
for path in self._files:
166
if (path.startswith(_abspath) and
167
path[len(_abspath) + 1:].find('/') == -1 and
168
len(path) > len(_abspath)):
169
result.append(path[len(_abspath) + 1:])
170
for path in self._dirs:
171
if (path.startswith(_abspath) and
172
path[len(_abspath) + 1:].find('/') == -1 and
173
len(path) > len(_abspath) and
174
path[len(_abspath)] == '/'):
175
result.append(path[len(_abspath) + 1:])
178
def rename(self, rel_from, rel_to):
179
"""Rename a file or directory; fail if the destination exists"""
180
abs_from = self._abspath(rel_from)
181
abs_to = self._abspath(rel_to)
185
elif x.startswith(abs_from + '/'):
186
x = abs_to + x[len(abs_from):]
188
def do_renames(container):
189
for path in container:
190
new_path = replace(path)
192
if new_path in container:
193
raise FileExists(new_path)
194
container[new_path] = container[path]
196
do_renames(self._files)
197
do_renames(self._dirs)
199
def rmdir(self, relpath):
200
"""See Transport.rmdir."""
201
_abspath = self._abspath(relpath)
202
if _abspath in self._files:
203
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
204
for path in self._files:
205
if path.startswith(_abspath):
206
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
208
for path in self._dirs:
209
if path.startswith(_abspath) and path != _abspath:
210
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
211
if not _abspath in self._dirs:
212
raise NoSuchFile(relpath)
213
del self._dirs[_abspath]
88
return iter(self._files)
90
# def list_dir(self, relpath):
215
93
def stat(self, relpath):
216
94
"""See Transport.stat()."""
217
_abspath = self._abspath(relpath)
218
if _abspath in self._files:
219
return MemoryStat(len(self._files[_abspath][0]), False,
220
self._files[_abspath][1])
221
elif _abspath in self._dirs:
222
return MemoryStat(0, True, self._dirs[_abspath])
224
raise NoSuchFile(_abspath)
226
def lock_read(self, relpath):
227
"""See Transport.lock_read()."""
228
return _MemoryLock(self._abspath(relpath), self)
230
def lock_write(self, relpath):
231
"""See Transport.lock_write()."""
232
return _MemoryLock(self._abspath(relpath), self)
234
def _abspath(self, relpath):
235
"""Generate an internal absolute path."""
236
relpath = urlutils.unescape(relpath)
237
if relpath.find('..') != -1:
238
raise AssertionError('relpath contains ..')
240
if (self._cwd == '/'):
242
return self._cwd[:-1]
243
if relpath.endswith('/'):
244
relpath = relpath[:-1]
245
if relpath.startswith('./'):
246
relpath = relpath[2:]
247
return self._cwd + relpath
250
class _MemoryLock(object):
251
"""This makes a lock."""
253
def __init__(self, path, transport):
254
assert isinstance(transport, MemoryTransport)
256
self.transport = transport
257
if self.path in self.transport._locks:
258
raise LockError('File %r already locked' % (self.path,))
259
self.transport._locks[self.path] = self
262
# Should this warn, or actually try to cleanup?
264
warnings.warn("MemoryLock %r not explicitly unlocked" % (self.path,))
268
del self.transport._locks[self.path]
269
self.transport = None
272
class MemoryServer(Server):
273
"""Server for the MemoryTransport for testing with."""
276
"""See bzrlib.transport.Server.setUp."""
277
self._dirs = {'/':None}
280
self._scheme = "memory+%s:///" % id(self)
281
def memory_factory(url):
282
result = MemoryTransport(url)
283
result._dirs = self._dirs
284
result._files = self._files
285
result._locks = self._locks
287
register_transport(self._scheme, memory_factory)
290
"""See bzrlib.transport.Server.tearDown."""
291
# unregister this server
294
"""See bzrlib.transport.Server.get_url."""
298
def get_test_permutations():
299
"""Return the permutations to be used in testing."""
300
return [(MemoryTransport, MemoryServer),
95
return MemoryStat(len(self._files[relpath]))
97
# def lock_read(self, relpath):
100
# def lock_write(self, relpath):