1
# Copyright (C) 2005, 2006 Canonical Ltd
1
# Copyright (C) 2005 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
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."""
16
"""Implementation of Transport for the local filesystem.
24
22
from stat import ST_MODE, S_ISDIR, ST_SIZE
28
25
from bzrlib.trace import mutter
29
from bzrlib.transport import Transport, Server
30
from bzrlib.osutils import (abspath, realpath, normpath, pathjoin, rename,
26
from bzrlib.transport import Transport, register_transport, \
27
TransportError, NoSuchFile, FileExists
30
class LocalTransportError(TransportError):
34
34
class LocalTransport(Transport):
37
37
def __init__(self, base):
38
38
"""Set the base path where files will be stored."""
39
39
if base.startswith('file://'):
40
base = base[len('file://'):]
41
41
# realpath is incompatible with symlinks. When we traverse
42
42
# up we might be able to normpath stuff. RBC 20051003
43
base = normpath(abspath(base))
46
super(LocalTransport, self).__init__(base)
43
super(LocalTransport, self).__init__(
44
os.path.normpath(os.path.abspath(base)))
48
46
def should_cache(self):
59
57
return LocalTransport(self.abspath(offset))
61
59
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] != '/':
60
"""Return the full url to the given relative path.
61
This can be supplied with a string or a list
63
if isinstance(relpath, basestring):
65
return os.path.join(self.base, *relpath)
69
67
def relpath(self, abspath):
70
68
"""Return the local path portion from a given absolute path.
72
from bzrlib.osutils import relpath, strip_trailing_slash
70
from bzrlib.osutils import relpath
73
71
if abspath is None:
76
return relpath(strip_trailing_slash(self.base),
77
strip_trailing_slash(abspath))
73
return relpath(self.base, abspath)
79
75
def has(self, relpath):
80
76
return os.access(self.abspath(relpath), os.F_OK)
88
84
path = self.abspath(relpath)
89
85
return open(path, 'rb')
90
except (IOError, OSError),e:
91
self._translate_error(e, path)
87
if e.errno in (errno.ENOENT, errno.ENOTDIR):
88
raise NoSuchFile('File or directory %r does not exist' % path, orig_error=e)
89
raise LocalTransportError(orig_error=e)
93
def put(self, relpath, f, mode=None):
91
def put(self, relpath, f):
94
92
"""Copy the file-like or string object into the location.
96
94
:param relpath: Location to put the contents, relative to base.
99
97
from bzrlib.atomicfile import AtomicFile
103
100
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)
101
fp = AtomicFile(path, 'wb')
103
if e.errno == errno.ENOENT:
104
raise NoSuchFile('File %r does not exist' % path, orig_error=e)
105
raise LocalTransportError(orig_error=e)
109
107
self._pump(f, fp)
126
def mkdir(self, relpath, mode=None):
124
def mkdir(self, relpath):
127
125
"""Create a directory at the given path."""
130
path = self.abspath(relpath)
134
except (IOError, OSError),e:
135
self._translate_error(e, path)
127
os.mkdir(self.abspath(relpath))
129
if e.errno == errno.EEXIST:
130
raise FileExists(orig_error=e)
131
elif e.errno == errno.ENOENT:
132
raise NoSuchFile(orig_error=e)
133
raise LocalTransportError(orig_error=e)
137
def append(self, relpath, f, mode=None):
135
def append(self, relpath, f):
138
136
"""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)
139
fp = open(self.abspath(relpath), 'ab')
150
140
self._pump(f, fp)
153
142
def copy(self, rel_from, rel_to):
154
143
"""Copy the item at rel_from to the location at rel_to"""
157
146
path_to = self.abspath(rel_to)
159
148
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)
150
raise LocalTransportError(orig_error=e)
174
152
def move(self, rel_from, rel_to):
175
153
"""Move the item at rel_from to the location at rel_to"""
177
155
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)
158
os.rename(path_from, path_to)
160
raise LocalTransportError(orig_error=e)
186
162
def delete(self, relpath):
187
163
"""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)
165
os.remove(self.abspath(relpath))
167
raise LocalTransportError(orig_error=e)
196
def copy_to(self, relpaths, other, mode=None, pb=None):
169
def copy_to(self, relpaths, other, pb=None):
197
170
"""Copy a set of entries from self into another Transport.
199
172
:param relpaths: A list/generator of entries to be copied.
209
182
for path in relpaths:
210
183
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)
184
shutil.copy(self.abspath(path), other.abspath(path))
222
return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
188
return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
224
190
def listable(self):
225
191
"""See Transport.listable."""
230
196
WARNING: many transports do not support this, so trying avoid using
231
197
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)
200
return os.listdir(self.abspath(relpath))
202
raise LocalTransportError(orig_error=e)
239
204
def stat(self, relpath):
240
205
"""Return the stat information for a file.
244
path = self.abspath(relpath)
246
except (IOError, OSError),e:
247
self._translate_error(e, path)
208
return os.stat(self.abspath(relpath))
210
raise LocalTransportError(orig_error=e)
249
212
def lock_read(self, relpath):
250
213
"""Lock the given file for shared (read) access.
251
214
:return: A lock object, which should be passed to Transport.unlock()
253
216
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)
217
return ReadLock(self.abspath(relpath))
261
219
def lock_write(self, relpath):
262
220
"""Lock the given file for exclusive (write) access.
267
225
from bzrlib.lock import WriteLock
268
226
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
229
class ScratchTransport(LocalTransport):
288
230
"""A transport that works in a temporary dir and cleans up after itself.
300
242
shutil.rmtree(self.base, ignore_errors=True)
301
243
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),
245
# If nothing else matches, try the LocalTransport
246
register_transport(None, LocalTransport)
247
register_transport('file://', LocalTransport)