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
16
"""Implementation of Transport for the local filesystem.
19
from bzrlib.transport import Transport, register_transport, \
20
TransportError, NoSuchFile, FileExists
23
class LocalTransportError(TransportError):
17
"""Transport for the local filesystem.
19
This is a fairly thin wrapper on regular file IO."""
23
from stat import ST_MODE, S_ISDIR, ST_SIZE
27
from bzrlib.trace import mutter
28
from bzrlib.transport import Transport, Server
29
from bzrlib.osutils import abspath, realpath, normpath, pathjoin, rename
26
32
class LocalTransport(Transport):
27
33
"""This is the transport agent for local filesystem access."""
33
39
# realpath is incompatible with symlinks. When we traverse
34
40
# up we might be able to normpath stuff. RBC 20051003
35
super(LocalTransport, self).__init__(
36
os.path.normpath(os.path.abspath(base)))
41
base = normpath(abspath(base))
44
super(LocalTransport, self).__init__(base)
38
46
def should_cache(self):
49
57
return LocalTransport(self.abspath(offset))
51
59
def abspath(self, relpath):
52
"""Return the full url to the given relative path.
60
"""Return the full url to the given relative URL.
53
61
This can be supplied with a string or a list
55
if isinstance(relpath, basestring):
57
return os.path.join(self.base, *relpath)
63
assert isinstance(relpath, basestring), (type(relpath), relpath)
64
return pathjoin(self.base, urllib.unquote(relpath))
59
66
def relpath(self, abspath):
60
67
"""Return the local path portion from a given absolute path.
62
from bzrlib.branch import _relpath
63
return _relpath(self.base, abspath)
69
from bzrlib.osutils import relpath
72
if abspath.endswith('/'):
73
abspath = abspath[:-1]
74
return relpath(self.base[:-1], abspath)
65
76
def has(self, relpath):
66
77
return os.access(self.abspath(relpath), os.F_OK)
74
85
path = self.abspath(relpath)
75
86
return open(path, 'rb')
77
if e.errno in (errno.ENOENT, errno.ENOTDIR):
78
raise NoSuchFile('File or directory %r does not exist' % path, orig_error=e)
79
raise LocalTransportError(orig_error=e)
81
def get_partial(self, relpath, start, length=None):
82
"""Get just part of a file.
84
:param relpath: Path to the file, relative to base
85
:param start: The starting position to read from
86
:param length: The length to read. A length of None indicates
87
read to the end of the file.
88
:return: A file-like object containing at least the specified bytes.
89
Some implementations may return objects which can be read
90
past this length, but this is not guaranteed.
92
# LocalTransport.get_partial() doesn't care about the length
93
# argument, because it is using a local file, and thus just
94
# returns the file seek'ed to the appropriate location.
96
path = self.abspath(relpath)
101
if e.errno == errno.ENOENT:
102
raise NoSuchFile('File %r does not exist' % path, orig_error=e)
103
raise LocalTransportError(orig_error=e)
105
def put(self, relpath, f):
87
except (IOError, OSError),e:
88
self._translate_error(e, path)
90
def put(self, relpath, f, mode=None):
106
91
"""Copy the file-like or string object into the location.
108
93
:param relpath: Location to put the contents, relative to base.
111
96
from bzrlib.atomicfile import AtomicFile
114
100
path = self.abspath(relpath)
115
fp = AtomicFile(path, 'wb')
117
if e.errno == errno.ENOENT:
118
raise NoSuchFile('File %r does not exist' % path, orig_error=e)
119
raise LocalTransportError(orig_error=e)
101
fp = AtomicFile(path, 'wb', new_mode=mode)
102
except (IOError, OSError),e:
103
self._translate_error(e, path)
121
105
self._pump(f, fp)
126
def mkdir(self, relpath):
110
def iter_files_recursive(self):
111
"""Iter the relative paths of files in the transports sub-tree."""
112
queue = list(self.list_dir(u'.'))
114
relpath = urllib.quote(queue.pop(0))
115
st = self.stat(relpath)
116
if S_ISDIR(st[ST_MODE]):
117
for i, basename in enumerate(self.list_dir(relpath)):
118
queue.insert(i, relpath+'/'+basename)
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
141
self._pump(f, fp)
144
143
def copy(self, rel_from, rel_to):
148
147
path_to = self.abspath(rel_to)
150
149
shutil.copy(path_from, path_to)
152
raise LocalTransportError(orig_error=e)
150
except (IOError, OSError),e:
151
# TODO: What about path_to?
152
self._translate_error(e, path_from)
154
154
def move(self, rel_from, rel_to):
155
155
"""Move the item at rel_from to the location at rel_to"""
157
157
path_to = self.abspath(rel_to)
160
os.rename(path_from, path_to)
162
raise LocalTransportError(orig_error=e)
160
rename(path_from, path_to)
161
except (IOError, OSError),e:
162
# TODO: What about path_to?
163
self._translate_error(e, path_from)
164
165
def delete(self, relpath):
165
166
"""Delete the item at relpath"""
167
os.remove(self.abspath(relpath))
169
raise LocalTransportError(orig_error=e)
169
path = self.abspath(relpath)
171
except (IOError, OSError),e:
172
# TODO: What about path_to?
173
self._translate_error(e, path)
171
def copy_to(self, relpaths, other, pb=None):
175
def copy_to(self, relpaths, other, mode=None, pb=None):
172
176
"""Copy a set of entries from self into another Transport.
174
178
:param relpaths: A list/generator of entries to be copied.
184
188
for path in relpaths:
185
189
self._update_pb(pb, 'copy-to', count, total)
186
shutil.copy(self.abspath(path), other.abspath(path))
191
mypath = self.abspath(path)
192
otherpath = other.abspath(path)
193
shutil.copy(mypath, otherpath)
195
os.chmod(otherpath, mode)
196
except (IOError, OSError),e:
197
self._translate_error(e, path)
190
return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
201
return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
192
203
def listable(self):
193
204
"""See Transport.listable."""
198
209
WARNING: many transports do not support this, so trying avoid using
199
210
it if at all possible.
202
return os.listdir(self.abspath(relpath))
204
raise LocalTransportError(orig_error=e)
214
path = self.abspath(relpath)
215
return os.listdir(path)
216
except (IOError, OSError),e:
217
self._translate_error(e, path)
206
219
def stat(self, relpath):
207
220
"""Return the stat information for a file.
210
return os.stat(self.abspath(relpath))
212
raise LocalTransportError(orig_error=e)
224
path = self.abspath(relpath)
226
except (IOError, OSError),e:
227
self._translate_error(e, path)
214
229
def lock_read(self, relpath):
215
230
"""Lock the given file for shared (read) access.
216
231
:return: A lock object, which should be passed to Transport.unlock()
218
233
from bzrlib.lock import ReadLock
219
return ReadLock(self.abspath(relpath))
236
path = self.abspath(relpath)
237
return ReadLock(path)
238
except (IOError, OSError), e:
239
self._translate_error(e, path)
221
241
def lock_write(self, relpath):
222
242
"""Lock the given file for exclusive (write) access.
227
247
from bzrlib.lock import WriteLock
228
248
return WriteLock(self.abspath(relpath))
230
# If nothing else matches, try the LocalTransport
231
register_transport(None, LocalTransport)
232
register_transport('file://', LocalTransport)
251
class ScratchTransport(LocalTransport):
252
"""A transport that works in a temporary dir and cleans up after itself.
254
The dir only exists for the lifetime of the Python object.
255
Obviously you should not put anything precious in it.
258
def __init__(self, base=None):
260
base = tempfile.mkdtemp()
261
super(ScratchTransport, self).__init__(base)
264
shutil.rmtree(self.base, ignore_errors=True)
265
mutter("%r destroyed" % self)
268
class LocalRelpathServer(Server):
269
"""A pretend server for local transports, using relpaths."""
272
"""See Transport.Server.get_url."""
276
class LocalAbspathServer(Server):
277
"""A pretend server for local transports, using absolute paths."""
280
"""See Transport.Server.get_url."""
281
return os.path.abspath("")
284
class LocalURLServer(Server):
285
"""A pretend server for local transports, using file:// urls."""
288
"""See Transport.Server.get_url."""
289
# FIXME: \ to / on windows
290
return "file://%s" % os.path.abspath("")
293
def get_test_permutations():
294
"""Return the permutations to be used in testing."""
295
return [(LocalTransport, LocalRelpathServer),
296
(LocalTransport, LocalAbspathServer),
297
(LocalTransport, LocalURLServer),