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
"""Transport for the local filesystem.
19
This is a fairly thin wrapper on regular file IO."""
24
from stat import ST_MODE, S_ISDIR, ST_SIZE
28
from bzrlib.trace import mutter
29
from bzrlib.transport import Transport, Server
30
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename,
16
"""Implementation of Transport for the local filesystem.
19
from bzrlib.transport import Transport, register_transport, \
20
TransportError, NoSuchFile, FileExists
23
class LocalTransportError(TransportError):
34
26
class LocalTransport(Transport):
35
27
"""This is the transport agent for local filesystem access."""
37
29
def __init__(self, base):
38
30
"""Set the base path where files will be stored."""
39
31
if base.startswith('file://'):
40
base = base[len('file://'):]
41
33
# realpath is incompatible with symlinks. When we traverse
42
34
# up we might be able to normpath stuff. RBC 20051003
43
base = normpath(abspath(base))
46
super(LocalTransport, self).__init__(base)
35
super(LocalTransport, self).__init__(
36
os.path.normpath(os.path.abspath(base)))
48
38
def should_cache(self):
59
49
return LocalTransport(self.abspath(offset))
61
51
def abspath(self, 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] != '/':
52
"""Return the full url to the given relative path.
53
This can be supplied with a string or a list
55
if isinstance(relpath, basestring):
57
return os.path.join(self.base, *relpath)
69
59
def relpath(self, abspath):
70
60
"""Return the local path portion from a given absolute path.
72
from bzrlib.osutils import relpath, strip_trailing_slash
76
return relpath(strip_trailing_slash(self.base),
77
strip_trailing_slash(abspath))
62
from bzrlib.branch import _relpath
63
return _relpath(self.base, abspath)
79
65
def has(self, relpath):
80
66
return os.access(self.abspath(relpath), os.F_OK)
88
74
path = self.abspath(relpath)
89
75
return open(path, 'rb')
90
except (IOError, OSError),e:
91
self._translate_error(e, path)
93
def put(self, relpath, f, mode=None):
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):
94
106
"""Copy the file-like or string object into the location.
96
108
:param relpath: Location to put the contents, relative to base.
99
111
from bzrlib.atomicfile import AtomicFile
103
114
path = self.abspath(relpath)
104
check_legal_path(path)
105
fp = AtomicFile(path, 'wb', new_mode=mode)
106
except (IOError, OSError),e:
107
self._translate_error(e, path)
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)
109
121
self._pump(f, fp)
114
def iter_files_recursive(self):
115
"""Iter the relative paths of files in the transports sub-tree."""
116
queue = list(self.list_dir(u'.'))
118
relpath = queue.pop(0)
119
st = self.stat(relpath)
120
if S_ISDIR(st[ST_MODE]):
121
for i, basename in enumerate(self.list_dir(relpath)):
122
queue.insert(i, relpath+'/'+basename)
126
def mkdir(self, relpath, mode=None):
126
def mkdir(self, relpath):
127
127
"""Create a directory at the given path."""
130
path = self.abspath(relpath)
134
except (IOError, OSError),e:
135
self._translate_error(e, 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)
137
def append(self, relpath, f, mode=None):
137
def append(self, relpath, f):
138
138
"""Append the text in the file-like object into the final
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)
141
fp = open(self.abspath(relpath), 'ab')
150
142
self._pump(f, fp)
153
144
def copy(self, rel_from, rel_to):
154
145
"""Copy the item at rel_from to the location at rel_to"""
157
148
path_to = self.abspath(rel_to)
159
150
shutil.copy(path_from, path_to)
160
except (IOError, OSError),e:
161
# TODO: What about path_to?
162
self._translate_error(e, path_from)
164
def rename(self, rel_from, rel_to):
165
path_from = self.abspath(rel_from)
167
# *don't* call bzrlib.osutils.rename, because we want to
168
# detect errors on rename
169
os.rename(path_from, self.abspath(rel_to))
170
except (IOError, OSError),e:
171
# TODO: What about path_to?
172
self._translate_error(e, path_from)
152
raise LocalTransportError(orig_error=e)
174
154
def move(self, rel_from, rel_to):
175
155
"""Move the item at rel_from to the location at rel_to"""
177
157
path_to = self.abspath(rel_to)
180
# this version will delete the destination if necessary
181
rename(path_from, path_to)
182
except (IOError, OSError),e:
183
# TODO: What about path_to?
184
self._translate_error(e, path_from)
160
os.rename(path_from, path_to)
162
raise LocalTransportError(orig_error=e)
186
164
def delete(self, relpath):
187
165
"""Delete the item at relpath"""
190
path = self.abspath(relpath)
192
except (IOError, OSError),e:
193
# TODO: What about path_to?
194
self._translate_error(e, path)
167
os.remove(self.abspath(relpath))
169
raise LocalTransportError(orig_error=e)
196
def copy_to(self, relpaths, other, mode=None, pb=None):
171
def copy_to(self, relpaths, other, pb=None):
197
172
"""Copy a set of entries from self into another Transport.
199
174
:param relpaths: A list/generator of entries to be copied.
209
184
for path in relpaths:
210
185
self._update_pb(pb, 'copy-to', count, total)
212
mypath = self.abspath(path)
213
otherpath = other.abspath(path)
214
shutil.copy(mypath, otherpath)
216
os.chmod(otherpath, mode)
217
except (IOError, OSError),e:
218
self._translate_error(e, path)
186
shutil.copy(self.abspath(path), other.abspath(path))
222
return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
190
return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
224
192
def listable(self):
225
193
"""See Transport.listable."""
230
198
WARNING: many transports do not support this, so trying avoid using
231
199
it if at all possible.
233
path = self.abspath(relpath)
235
return [urllib.quote(entry) for entry in os.listdir(path)]
236
except (IOError, OSError), e:
237
self._translate_error(e, path)
202
return os.listdir(self.abspath(relpath))
204
raise LocalTransportError(orig_error=e)
239
206
def stat(self, relpath):
240
207
"""Return the stat information for a file.
244
path = self.abspath(relpath)
246
except (IOError, OSError),e:
247
self._translate_error(e, path)
210
return os.stat(self.abspath(relpath))
212
raise LocalTransportError(orig_error=e)
249
214
def lock_read(self, relpath):
250
215
"""Lock the given file for shared (read) access.
251
216
:return: A lock object, which should be passed to Transport.unlock()
253
218
from bzrlib.lock import ReadLock
256
path = self.abspath(relpath)
257
return ReadLock(path)
258
except (IOError, OSError), e:
259
self._translate_error(e, path)
219
return ReadLock(self.abspath(relpath))
261
221
def lock_write(self, relpath):
262
222
"""Lock the given file for exclusive (write) access.
267
227
from bzrlib.lock import WriteLock
268
228
return WriteLock(self.abspath(relpath))
270
def rmdir(self, relpath):
271
"""See Transport.rmdir."""
274
path = self.abspath(relpath)
276
except (IOError, OSError),e:
277
self._translate_error(e, path)
279
def _can_roundtrip_unix_modebits(self):
280
if sys.platform == 'win32':
287
class ScratchTransport(LocalTransport):
288
"""A transport that works in a temporary dir and cleans up after itself.
290
The dir only exists for the lifetime of the Python object.
291
Obviously you should not put anything precious in it.
294
def __init__(self, base=None):
296
base = tempfile.mkdtemp()
297
super(ScratchTransport, self).__init__(base)
300
shutil.rmtree(self.base, ignore_errors=True)
301
mutter("%r destroyed" % self)
304
class LocalRelpathServer(Server):
305
"""A pretend server for local transports, using relpaths."""
308
"""See Transport.Server.get_url."""
312
class LocalAbspathServer(Server):
313
"""A pretend server for local transports, using absolute paths."""
316
"""See Transport.Server.get_url."""
317
return os.path.abspath("")
320
class LocalURLServer(Server):
321
"""A pretend server for local transports, using file:// urls."""
324
"""See Transport.Server.get_url."""
325
# FIXME: \ to / on windows
326
return "file://%s" % os.path.abspath("")
329
def get_test_permutations():
330
"""Return the permutations to be used in testing."""
331
return [(LocalTransport, LocalRelpathServer),
332
(LocalTransport, LocalAbspathServer),
333
(LocalTransport, LocalURLServer),
230
# If nothing else matches, try the LocalTransport
231
register_transport(None, LocalTransport)
232
register_transport('file://', LocalTransport)