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
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."""
39
33
# realpath is incompatible with symlinks. When we traverse
40
34
# up we might be able to normpath stuff. RBC 20051003
41
35
super(LocalTransport, self).__init__(
42
os.path.normpath(abspath(base)))
36
os.path.normpath(os.path.abspath(base)))
44
38
def should_cache(self):
55
49
return LocalTransport(self.abspath(offset))
57
51
def abspath(self, relpath):
58
"""Return the full url to the given relative URL.
52
"""Return the full url to the given relative path.
59
53
This can be supplied with a string or a list
61
assert isinstance(relpath, basestring), (type(relpath), relpath)
62
return os.path.join(self.base, urllib.unquote(relpath))
55
if isinstance(relpath, basestring):
57
return os.path.join(self.base, *relpath)
64
59
def relpath(self, abspath):
65
60
"""Return the local path portion from a given absolute path.
67
from bzrlib.osutils import relpath
70
return relpath(self.base, abspath)
62
from bzrlib.branch import _relpath
63
return _relpath(self.base, abspath)
72
65
def has(self, relpath):
73
66
return os.access(self.abspath(relpath), os.F_OK)
81
74
path = self.abspath(relpath)
82
75
return open(path, 'rb')
83
except (IOError, OSError),e:
84
self._translate_error(e, path)
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)
86
105
def put(self, relpath, f):
87
106
"""Copy the file-like or string object into the location.
92
111
from bzrlib.atomicfile import AtomicFile
96
114
path = self.abspath(relpath)
97
115
fp = AtomicFile(path, 'wb')
98
except (IOError, OSError),e:
99
self._translate_error(e, path)
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
121
self._pump(f, fp)
106
def iter_files_recursive(self):
107
"""Iter the relative paths of files in the transports sub-tree."""
108
queue = list(self.list_dir(u'.'))
110
relpath = urllib.quote(queue.pop(0))
111
st = self.stat(relpath)
112
if S_ISDIR(st[ST_MODE]):
113
for i, basename in enumerate(self.list_dir(relpath)):
114
queue.insert(i, relpath+'/'+basename)
118
126
def mkdir(self, relpath):
119
127
"""Create a directory at the given path."""
122
path = self.abspath(relpath)
124
except (IOError, OSError),e:
125
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)
127
137
def append(self, relpath, f):
128
138
"""Append the text in the file-like object into the final
138
148
path_to = self.abspath(rel_to)
140
150
shutil.copy(path_from, path_to)
141
except (IOError, OSError),e:
142
# TODO: What about path_to?
143
self._translate_error(e, path_from)
152
raise LocalTransportError(orig_error=e)
145
154
def move(self, rel_from, rel_to):
146
155
"""Move the item at rel_from to the location at rel_to"""
151
160
os.rename(path_from, path_to)
152
except (IOError, OSError),e:
153
# TODO: What about path_to?
154
self._translate_error(e, path_from)
162
raise LocalTransportError(orig_error=e)
156
164
def delete(self, relpath):
157
165
"""Delete the item at relpath"""
160
path = self.abspath(relpath)
162
except (IOError, OSError),e:
163
# TODO: What about path_to?
164
self._translate_error(e, path)
167
os.remove(self.abspath(relpath))
169
raise LocalTransportError(orig_error=e)
166
171
def copy_to(self, relpaths, other, pb=None):
167
172
"""Copy a set of entries from self into another Transport.
179
184
for path in relpaths:
180
185
self._update_pb(pb, 'copy-to', count, total)
182
shutil.copy(self.abspath(path), other.abspath(path))
183
except (IOError, OSError),e:
184
self._translate_error(e, path)
186
shutil.copy(self.abspath(path), other.abspath(path))
196
198
WARNING: many transports do not support this, so trying avoid using
197
199
it if at all possible.
201
path = self.abspath(relpath)
202
return os.listdir(path)
203
except (IOError, OSError),e:
204
self._translate_error(e, path)
202
return os.listdir(self.abspath(relpath))
204
raise LocalTransportError(orig_error=e)
206
206
def stat(self, relpath):
207
207
"""Return the stat information for a file.
211
path = self.abspath(relpath)
213
except (IOError, OSError),e:
214
self._translate_error(e, path)
210
return os.stat(self.abspath(relpath))
212
raise LocalTransportError(orig_error=e)
216
214
def lock_read(self, relpath):
217
215
"""Lock the given file for shared (read) access.
229
227
from bzrlib.lock import WriteLock
230
228
return WriteLock(self.abspath(relpath))
233
class ScratchTransport(LocalTransport):
234
"""A transport that works in a temporary dir and cleans up after itself.
236
The dir only exists for the lifetime of the Python object.
237
Obviously you should not put anything precious in it.
240
def __init__(self, base=None):
242
base = tempfile.mkdtemp()
243
super(ScratchTransport, self).__init__(base)
246
shutil.rmtree(self.base, ignore_errors=True)
247
mutter("%r destroyed" % self)
230
# If nothing else matches, try the LocalTransport
231
register_transport(None, LocalTransport)
232
register_transport('file://', LocalTransport)