19
19
This is a fairly thin wrapper on regular file IO."""
24
24
from stat import ST_MODE, S_ISDIR, ST_SIZE
28
28
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):
29
from bzrlib.transport import Transport, Server
30
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename,
31
check_legal_path, rmtree)
37
34
class LocalTransport(Transport):
40
37
def __init__(self, base):
41
38
"""Set the base path where files will be stored."""
42
39
if base.startswith('file://'):
40
base = base[len('file://'):]
44
41
# realpath is incompatible with symlinks. When we traverse
45
42
# up we might be able to normpath stuff. RBC 20051003
46
super(LocalTransport, self).__init__(
47
os.path.normpath(abspath(base)))
43
base = normpath(abspath(base))
46
super(LocalTransport, self).__init__(base)
49
48
def should_cache(self):
60
59
return LocalTransport(self.abspath(offset))
62
61
def abspath(self, relpath):
63
"""Return the full url to the given relative URL.
64
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))
62
"""Return the full url to the given relative URL."""
63
assert isinstance(relpath, basestring), (type(relpath), relpath)
64
result = normpath(pathjoin(self.base, urllib.unquote(relpath)))
65
#if result[-1] != '/':
69
69
def relpath(self, abspath):
70
70
"""Return the local path portion from a given absolute path.
72
from bzrlib.osutils import relpath
72
from bzrlib.osutils import relpath, strip_trailing_slash
73
73
if abspath is None:
75
return relpath(self.base, abspath)
76
return relpath(strip_trailing_slash(self.base),
77
strip_trailing_slash(abspath))
77
79
def has(self, relpath):
78
80
return os.access(self.abspath(relpath), os.F_OK)
86
88
path = self.abspath(relpath)
87
89
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)
90
except (IOError, OSError),e:
91
self._translate_error(e, path)
93
def put(self, relpath, f):
93
def put(self, relpath, f, mode=None):
94
94
"""Copy the file-like or string object into the location.
96
96
:param relpath: Location to put the contents, relative to base.
99
99
from bzrlib.atomicfile import AtomicFile
102
103
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)
104
check_legal_path(path)
105
fp = AtomicFile(path, 'wb', new_mode=mode)
106
except (IOError, OSError),e:
107
self._translate_error(e, path)
109
109
self._pump(f, fp)
126
def mkdir(self, relpath):
126
def mkdir(self, relpath, mode=None):
127
127
"""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)
130
path = self.abspath(relpath)
134
except (IOError, OSError),e:
135
self._translate_error(e, path)
137
def append(self, relpath, f):
137
def append(self, relpath, f, mode=None):
138
138
"""Append the text in the file-like object into the final
141
fp = open(self.abspath(relpath), 'ab')
142
fp = open(self.abspath(relpath), 'ab')
144
os.chmod(self.abspath(relpath), mode)
145
except (IOError, OSError),e:
146
self._translate_error(e, relpath)
147
# win32 workaround (tell on an unwritten file returns 0)
142
150
self._pump(f, fp)
144
153
def copy(self, rel_from, rel_to):
145
154
"""Copy the item at rel_from to the location at rel_to"""
147
155
path_from = self.abspath(rel_from)
148
156
path_to = self.abspath(rel_to)
150
158
shutil.copy(path_from, path_to)
152
raise LocalTransportError(orig_error=e)
159
except (IOError, OSError),e:
160
# TODO: What about path_to?
161
self._translate_error(e, path_from)
163
def rename(self, rel_from, rel_to):
164
path_from = self.abspath(rel_from)
166
# *don't* call bzrlib.osutils.rename, because we want to
167
# detect errors on rename
168
os.rename(path_from, self.abspath(rel_to))
169
except (IOError, OSError),e:
170
# TODO: What about path_to?
171
self._translate_error(e, path_from)
154
173
def move(self, rel_from, rel_to):
155
174
"""Move the item at rel_from to the location at rel_to"""
157
176
path_to = self.abspath(rel_to)
160
os.rename(path_from, path_to)
162
raise LocalTransportError(orig_error=e)
179
# this version will delete the destination if necessary
180
rename(path_from, path_to)
181
except (IOError, OSError),e:
182
# TODO: What about path_to?
183
self._translate_error(e, path_from)
164
185
def delete(self, relpath):
165
186
"""Delete the item at relpath"""
167
os.remove(self.abspath(relpath))
169
raise LocalTransportError(orig_error=e)
189
path = self.abspath(relpath)
191
except (IOError, OSError),e:
192
# TODO: What about path_to?
193
self._translate_error(e, path)
171
def copy_to(self, relpaths, other, pb=None):
195
def copy_to(self, relpaths, other, mode=None, pb=None):
172
196
"""Copy a set of entries from self into another Transport.
174
198
:param relpaths: A list/generator of entries to be copied.
177
201
# Both from & to are on the local filesystem
178
202
# Unfortunately, I can't think of anything faster than just
179
203
# copying them across, one by one :(
182
204
total = self._get_total(relpaths)
184
206
for path in relpaths:
185
207
self._update_pb(pb, 'copy-to', count, total)
186
shutil.copy(self.abspath(path), other.abspath(path))
209
mypath = self.abspath(path)
210
otherpath = other.abspath(path)
211
shutil.copy(mypath, otherpath)
213
os.chmod(otherpath, mode)
214
except (IOError, OSError),e:
215
self._translate_error(e, path)
190
return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
219
return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
192
221
def listable(self):
193
222
"""See Transport.listable."""
198
227
WARNING: many transports do not support this, so trying avoid using
199
228
it if at all possible.
230
path = self.abspath(relpath)
202
return os.listdir(self.abspath(relpath))
204
raise LocalTransportError(orig_error=e)
232
return [urllib.quote(entry) for entry in os.listdir(path)]
233
except (IOError, OSError), e:
234
self._translate_error(e, path)
206
236
def stat(self, relpath):
207
237
"""Return the stat information for a file.
210
return os.stat(self.abspath(relpath))
212
raise LocalTransportError(orig_error=e)
241
path = self.abspath(relpath)
243
except (IOError, OSError),e:
244
self._translate_error(e, path)
214
246
def lock_read(self, relpath):
215
247
"""Lock the given file for shared (read) access.
216
248
:return: A lock object, which should be passed to Transport.unlock()
218
250
from bzrlib.lock import ReadLock
219
return ReadLock(self.abspath(relpath))
253
path = self.abspath(relpath)
254
return ReadLock(path)
255
except (IOError, OSError), e:
256
self._translate_error(e, path)
221
258
def lock_write(self, relpath):
222
259
"""Lock the given file for exclusive (write) access.
227
264
from bzrlib.lock import WriteLock
228
265
return WriteLock(self.abspath(relpath))
267
def rmdir(self, relpath):
268
"""See Transport.rmdir."""
271
path = self.abspath(relpath)
273
except (IOError, OSError),e:
274
self._translate_error(e, path)
276
def _can_roundtrip_unix_modebits(self):
277
if sys.platform == 'win32':
231
284
class ScratchTransport(LocalTransport):
232
285
"""A transport that works in a temporary dir and cleans up after itself.
241
294
super(ScratchTransport, self).__init__(base)
243
296
def __del__(self):
244
shutil.rmtree(self.base, ignore_errors=True)
297
rmtree(self.base, ignore_errors=True)
245
298
mutter("%r destroyed" % self)
301
class LocalRelpathServer(Server):
302
"""A pretend server for local transports, using relpaths."""
305
"""See Transport.Server.get_url."""
309
class LocalAbspathServer(Server):
310
"""A pretend server for local transports, using absolute paths."""
313
"""See Transport.Server.get_url."""
314
return os.path.abspath("")
317
class LocalURLServer(Server):
318
"""A pretend server for local transports, using file:// urls."""
321
"""See Transport.Server.get_url."""
322
# FIXME: \ to / on windows
323
return "file://%s" % os.path.abspath("")
326
def get_test_permutations():
327
"""Return the permutations to be used in testing."""
328
return [(LocalTransport, LocalRelpathServer),
329
(LocalTransport, LocalAbspathServer),
330
(LocalTransport, LocalURLServer),