1
# Copyright (C) 2005 Canonical Ltd
1
# Copyright (C) 2005, 2006 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
19
19
This is a fairly thin wrapper on regular file IO."""
24
23
from stat import ST_MODE, S_ISDIR, ST_SIZE
28
27
from bzrlib.trace import mutter
29
from bzrlib.transport import Transport, register_transport, \
30
TransportError, NoSuchFile, FileExists
31
from bzrlib.osutils import abspath
33
class LocalTransportError(TransportError):
28
from bzrlib.transport import Transport, Server
29
from bzrlib.osutils import abspath, realpath, normpath, pathjoin, rename
37
32
class LocalTransport(Transport):
40
35
def __init__(self, base):
41
36
"""Set the base path where files will be stored."""
42
37
if base.startswith('file://'):
38
base = base[len('file://'):]
44
39
# realpath is incompatible with symlinks. When we traverse
45
40
# up we might be able to normpath stuff. RBC 20051003
46
super(LocalTransport, self).__init__(
47
os.path.normpath(abspath(base)))
41
base = normpath(abspath(base))
44
super(LocalTransport, self).__init__(base)
49
46
def should_cache(self):
63
60
"""Return the full url to the given relative URL.
64
61
This can be supplied with a string or a list
66
assert isinstance(relpath, basestring)
67
return os.path.join(self.base, urllib.unquote(relpath))
63
assert isinstance(relpath, basestring), (type(relpath), relpath)
64
return pathjoin(self.base, urllib.unquote(relpath))
69
66
def relpath(self, abspath):
70
67
"""Return the local path portion from a given absolute path.
72
69
from bzrlib.osutils import relpath
73
70
if abspath is None:
75
return relpath(self.base, abspath)
72
if abspath.endswith('/'):
73
abspath = abspath[:-1]
74
return relpath(self.base[:-1], abspath)
77
76
def has(self, relpath):
78
77
return os.access(self.abspath(relpath), os.F_OK)
86
85
path = self.abspath(relpath)
87
86
return open(path, 'rb')
89
if e.errno in (errno.ENOENT, errno.ENOTDIR):
90
raise NoSuchFile('File or directory %r does not exist' % path, orig_error=e)
91
raise LocalTransportError(orig_error=e)
87
except (IOError, OSError),e:
88
self._translate_error(e, path)
93
def put(self, relpath, f):
90
def put(self, relpath, f, mode=None):
94
91
"""Copy the file-like or string object into the location.
96
93
:param relpath: Location to put the contents, relative to base.
99
96
from bzrlib.atomicfile import AtomicFile
102
100
path = self.abspath(relpath)
103
fp = AtomicFile(path, 'wb')
105
if e.errno == errno.ENOENT:
106
raise NoSuchFile('File %r does not exist' % path, orig_error=e)
107
raise LocalTransportError(orig_error=e)
101
fp = AtomicFile(path, 'wb', new_mode=mode)
102
except (IOError, OSError),e:
103
self._translate_error(e, path)
109
105
self._pump(f, fp)
126
def mkdir(self, relpath):
122
def mkdir(self, relpath, mode=None):
127
123
"""Create a directory at the given path."""
129
os.mkdir(self.abspath(relpath))
131
if e.errno == errno.EEXIST:
132
raise FileExists(orig_error=e)
133
elif e.errno == errno.ENOENT:
134
raise NoSuchFile(orig_error=e)
135
raise LocalTransportError(orig_error=e)
126
path = self.abspath(relpath)
130
except (IOError, OSError),e:
131
self._translate_error(e, path)
137
133
def append(self, relpath, f):
138
134
"""Append the text in the file-like object into the final
141
fp = open(self.abspath(relpath), 'ab')
138
fp = open(self.abspath(relpath), 'ab')
139
except (IOError, OSError),e:
140
self._translate_error(e, relpath)
142
142
self._pump(f, fp)
144
145
def copy(self, rel_from, rel_to):
145
146
"""Copy the item at rel_from to the location at rel_to"""
148
149
path_to = self.abspath(rel_to)
150
151
shutil.copy(path_from, path_to)
152
raise LocalTransportError(orig_error=e)
152
except (IOError, OSError),e:
153
# TODO: What about path_to?
154
self._translate_error(e, path_from)
156
def rename(self, rel_from, rel_to):
157
path_from = self.abspath(rel_from)
159
# *don't* call bzrlib.osutils.rename, because we want to
160
# detect errors on rename
161
os.rename(path_from, self.abspath(rel_to))
162
except (IOError, OSError),e:
163
# TODO: What about path_to?
164
self._translate_error(e, path_from)
154
166
def move(self, rel_from, rel_to):
155
167
"""Move the item at rel_from to the location at rel_to"""
157
169
path_to = self.abspath(rel_to)
160
os.rename(path_from, path_to)
162
raise LocalTransportError(orig_error=e)
172
# this version will delete the destination if necessary
173
rename(path_from, path_to)
174
except (IOError, OSError),e:
175
# TODO: What about path_to?
176
self._translate_error(e, path_from)
164
178
def delete(self, relpath):
165
179
"""Delete the item at relpath"""
167
os.remove(self.abspath(relpath))
169
raise LocalTransportError(orig_error=e)
182
path = self.abspath(relpath)
184
except (IOError, OSError),e:
185
# TODO: What about path_to?
186
self._translate_error(e, path)
171
def copy_to(self, relpaths, other, pb=None):
188
def copy_to(self, relpaths, other, mode=None, pb=None):
172
189
"""Copy a set of entries from self into another Transport.
174
191
:param relpaths: A list/generator of entries to be copied.
184
201
for path in relpaths:
185
202
self._update_pb(pb, 'copy-to', count, total)
186
shutil.copy(self.abspath(path), other.abspath(path))
204
mypath = self.abspath(path)
205
otherpath = other.abspath(path)
206
shutil.copy(mypath, otherpath)
208
os.chmod(otherpath, mode)
209
except (IOError, OSError),e:
210
self._translate_error(e, path)
190
return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
214
return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
192
216
def listable(self):
193
217
"""See Transport.listable."""
198
222
WARNING: many transports do not support this, so trying avoid using
199
223
it if at all possible.
225
path = self.abspath(relpath)
202
return os.listdir(self.abspath(relpath))
204
raise LocalTransportError(orig_error=e)
227
return [urllib.quote(entry) for entry in os.listdir(path)]
228
except (IOError, OSError),e:
229
self._translate_error(e, path)
206
231
def stat(self, relpath):
207
232
"""Return the stat information for a file.
210
return os.stat(self.abspath(relpath))
212
raise LocalTransportError(orig_error=e)
236
path = self.abspath(relpath)
238
except (IOError, OSError),e:
239
self._translate_error(e, path)
214
241
def lock_read(self, relpath):
215
242
"""Lock the given file for shared (read) access.
216
243
:return: A lock object, which should be passed to Transport.unlock()
218
245
from bzrlib.lock import ReadLock
219
return ReadLock(self.abspath(relpath))
248
path = self.abspath(relpath)
249
return ReadLock(path)
250
except (IOError, OSError), e:
251
self._translate_error(e, path)
221
253
def lock_write(self, relpath):
222
254
"""Lock the given file for exclusive (write) access.
227
259
from bzrlib.lock import WriteLock
228
260
return WriteLock(self.abspath(relpath))
262
def rmdir(self, relpath):
263
"""See Transport.rmdir."""
266
path = self.abspath(relpath)
268
except (IOError, OSError),e:
269
self._translate_error(e, path)
231
272
class ScratchTransport(LocalTransport):
232
273
"""A transport that works in a temporary dir and cleans up after itself.
243
284
def __del__(self):
244
285
shutil.rmtree(self.base, ignore_errors=True)
245
286
mutter("%r destroyed" % self)
289
class LocalRelpathServer(Server):
290
"""A pretend server for local transports, using relpaths."""
293
"""See Transport.Server.get_url."""
297
class LocalAbspathServer(Server):
298
"""A pretend server for local transports, using absolute paths."""
301
"""See Transport.Server.get_url."""
302
return os.path.abspath("")
305
class LocalURLServer(Server):
306
"""A pretend server for local transports, using file:// urls."""
309
"""See Transport.Server.get_url."""
310
# FIXME: \ to / on windows
311
return "file://%s" % os.path.abspath("")
314
def get_test_permutations():
315
"""Return the permutations to be used in testing."""
316
return [(LocalTransport, LocalRelpathServer),
317
(LocalTransport, LocalAbspathServer),
318
(LocalTransport, LocalURLServer),