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."""
23
from stat import ST_MODE, S_ISDIR, ST_SIZE
27
from bzrlib.trace import mutter
28
from bzrlib.transport import Transport
29
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):
32
26
class LocalTransport(Transport):
33
27
"""This is the transport agent for local filesystem access."""
36
30
"""Set the base path where files will be stored."""
37
31
if base.startswith('file://'):
39
# realpath is incompatible with symlinks. When we traverse
40
# up we might be able to normpath stuff. RBC 20051003
41
super(LocalTransport, self).__init__(normpath(abspath(base)))
33
super(LocalTransport, self).__init__(os.path.realpath(base))
43
35
def should_cache(self):
54
46
return LocalTransport(self.abspath(offset))
56
48
def abspath(self, relpath):
57
"""Return the full url to the given relative URL.
49
"""Return the full url to the given relative path.
58
50
This can be supplied with a string or a list
60
assert isinstance(relpath, basestring), (type(relpath), relpath)
61
return pathjoin(self.base, urllib.unquote(relpath))
52
if isinstance(relpath, basestring):
54
return os.path.join(self.base, *relpath)
63
56
def relpath(self, abspath):
64
57
"""Return the local path portion from a given absolute path.
66
from bzrlib.osutils import relpath
69
return relpath(self.base, abspath)
59
from bzrlib.branch import _relpath
60
return _relpath(self.base, abspath)
71
62
def has(self, relpath):
72
63
return os.access(self.abspath(relpath), os.F_OK)
80
71
path = self.abspath(relpath)
81
72
return open(path, 'rb')
82
except (IOError, OSError),e:
83
self._translate_error(e, path)
85
def put(self, relpath, f, mode=None):
74
if e.errno == errno.ENOENT:
75
raise NoSuchFile('File %r does not exist' % path, orig_error=e)
76
raise LocalTransportError(orig_error=e)
78
def get_partial(self, relpath, start, length=None):
79
"""Get just part of a file.
81
:param relpath: Path to the file, relative to base
82
:param start: The starting position to read from
83
:param length: The length to read. A length of None indicates
84
read to the end of the file.
85
:return: A file-like object containing at least the specified bytes.
86
Some implementations may return objects which can be read
87
past this length, but this is not guaranteed.
89
# LocalTransport.get_partial() doesn't care about the length
90
# argument, because it is using a local file, and thus just
91
# returns the file seek'ed to the appropriate location.
93
path = self.abspath(relpath)
98
if e.errno == errno.ENOENT:
99
raise NoSuchFile('File %r does not exist' % path, orig_error=e)
100
raise LocalTransportError(orig_error=e)
102
def put(self, relpath, f):
86
103
"""Copy the file-like or string object into the location.
88
105
:param relpath: Location to put the contents, relative to base.
91
108
from bzrlib.atomicfile import AtomicFile
95
111
path = self.abspath(relpath)
96
fp = AtomicFile(path, 'wb', new_mode=mode)
97
except (IOError, OSError),e:
98
self._translate_error(e, path)
112
fp = AtomicFile(path, 'wb')
114
if e.errno == errno.ENOENT:
115
raise NoSuchFile('File %r does not exist' % path, orig_error=e)
116
raise LocalTransportError(orig_error=e)
100
118
self._pump(f, fp)
105
def iter_files_recursive(self):
106
"""Iter the relative paths of files in the transports sub-tree."""
107
queue = list(self.list_dir(u'.'))
109
relpath = urllib.quote(queue.pop(0))
110
st = self.stat(relpath)
111
if S_ISDIR(st[ST_MODE]):
112
for i, basename in enumerate(self.list_dir(relpath)):
113
queue.insert(i, relpath+'/'+basename)
117
def mkdir(self, relpath, mode=None):
123
def mkdir(self, relpath):
118
124
"""Create a directory at the given path."""
121
path = self.abspath(relpath)
125
except (IOError, OSError),e:
126
self._translate_error(e, path)
126
os.mkdir(self.abspath(relpath))
128
if e.errno == errno.EEXIST:
129
raise FileExists(orig_error=e)
130
elif e.errno == errno.ENOENT:
131
raise NoSuchFile(orig_error=e)
132
raise LocalTransportError(orig_error=e)
128
134
def append(self, relpath, f):
129
135
"""Append the text in the file-like object into the final
139
145
path_to = self.abspath(rel_to)
141
147
shutil.copy(path_from, path_to)
142
except (IOError, OSError),e:
143
# TODO: What about path_to?
144
self._translate_error(e, path_from)
149
raise LocalTransportError(orig_error=e)
146
151
def move(self, rel_from, rel_to):
147
152
"""Move the item at rel_from to the location at rel_to"""
149
154
path_to = self.abspath(rel_to)
152
rename(path_from, path_to)
153
except (IOError, OSError),e:
154
# TODO: What about path_to?
155
self._translate_error(e, path_from)
157
os.rename(path_from, path_to)
159
raise LocalTransportError(orig_error=e)
157
161
def delete(self, relpath):
158
162
"""Delete the item at relpath"""
161
path = self.abspath(relpath)
163
except (IOError, OSError),e:
164
# TODO: What about path_to?
165
self._translate_error(e, path)
164
os.remove(self.abspath(relpath))
166
raise LocalTransportError(orig_error=e)
167
def copy_to(self, relpaths, other, mode=None, pb=None):
168
def copy_to(self, relpaths, other, pb=None):
168
169
"""Copy a set of entries from self into another Transport.
170
171
:param relpaths: A list/generator of entries to be copied.
180
181
for path in relpaths:
181
182
self._update_pb(pb, 'copy-to', count, total)
183
mypath = self.abspath(path)
184
otherpath = other.abspath(path)
185
shutil.copy(mypath, otherpath)
187
os.chmod(otherpath, mode)
188
except (IOError, OSError),e:
189
self._translate_error(e, path)
183
shutil.copy(self.abspath(path), other.abspath(path))
193
return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
187
return super(LocalTransport, self).copy_to(relpaths, other, pb=pb)
196
"""See Transport.listable."""
199
190
def list_dir(self, relpath):
200
191
"""Return a list of all files at the given location.
201
192
WARNING: many transports do not support this, so trying avoid using
202
193
it if at all possible.
206
path = self.abspath(relpath)
207
return os.listdir(path)
208
except (IOError, OSError),e:
209
self._translate_error(e, path)
196
return os.listdir(self.abspath(relpath))
198
raise LocalTransportError(orig_error=e)
211
200
def stat(self, relpath):
212
201
"""Return the stat information for a file.
216
path = self.abspath(relpath)
218
except (IOError, OSError),e:
219
self._translate_error(e, path)
204
return os.stat(self.abspath(relpath))
206
raise LocalTransportError(orig_error=e)
221
208
def lock_read(self, relpath):
222
209
"""Lock the given file for shared (read) access.
234
221
from bzrlib.lock import WriteLock
235
222
return WriteLock(self.abspath(relpath))
238
class ScratchTransport(LocalTransport):
239
"""A transport that works in a temporary dir and cleans up after itself.
241
The dir only exists for the lifetime of the Python object.
242
Obviously you should not put anything precious in it.
245
def __init__(self, base=None):
247
base = tempfile.mkdtemp()
248
super(ScratchTransport, self).__init__(base)
251
shutil.rmtree(self.base, ignore_errors=True)
252
mutter("%r destroyed" % self)
224
# If nothing else matches, try the LocalTransport
225
register_transport(None, LocalTransport)
226
register_transport('file://', LocalTransport)