1
# Copyright (C) 2005, 2006 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
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,
31
check_legal_path, rmtree)
34
class LocalTransport(Transport):
35
"""This is the transport agent for local filesystem access."""
37
def __init__(self, base):
38
"""Set the base path where files will be stored."""
39
if base.startswith('file://'):
40
base = base[len('file://'):]
41
# realpath is incompatible with symlinks. When we traverse
42
# up we might be able to normpath stuff. RBC 20051003
43
base = normpath(abspath(base))
46
super(LocalTransport, self).__init__(base)
48
def should_cache(self):
51
def clone(self, offset=None):
52
"""Return a new LocalTransport with root at self.base + offset
53
Because the local filesystem does not require a connection,
54
we can just return a new object.
57
return LocalTransport(self.base)
59
return LocalTransport(self.abspath(offset))
61
def _abspath(self, relative_reference):
62
"""Return a path for use in os calls.
64
Several assumptions are made:
65
- relative_reference does not contain '..'
66
- relative_reference is url escaped.
68
return pathjoin(self.base, urllib.unquote(relative_reference))
70
def abspath(self, relpath):
71
"""Return the full url to the given relative URL."""
72
# TODO: url escape the result. RBC 20060523.
73
assert isinstance(relpath, basestring), (type(relpath), relpath)
74
result = normpath(pathjoin(self.base, urllib.unquote(relpath)))
75
#if result[-1] != '/':
79
def relpath(self, abspath):
80
"""Return the local path portion from a given absolute path.
82
from bzrlib.osutils import relpath, strip_trailing_slash
86
return relpath(strip_trailing_slash(self.base),
87
strip_trailing_slash(abspath))
89
def has(self, relpath):
90
return os.access(self._abspath(relpath), os.F_OK)
92
def get(self, relpath):
93
"""Get the file at the given relative path.
95
:param relpath: The relative path to the file
98
path = self._abspath(relpath)
99
return open(path, 'rb')
100
except (IOError, OSError),e:
101
self._translate_error(e, path)
103
def put(self, relpath, f, mode=None):
104
"""Copy the file-like or string object into the location.
106
:param relpath: Location to put the contents, relative to base.
107
:param f: File-like or string object.
109
from bzrlib.atomicfile import AtomicFile
113
path = self._abspath(relpath)
114
check_legal_path(path)
115
fp = AtomicFile(path, 'wb', new_mode=mode)
116
except (IOError, OSError),e:
117
self._translate_error(e, path)
124
def iter_files_recursive(self):
125
"""Iter the relative paths of files in the transports sub-tree."""
126
queue = list(self.list_dir(u'.'))
128
relpath = queue.pop(0)
129
st = self.stat(relpath)
130
if S_ISDIR(st[ST_MODE]):
131
for i, basename in enumerate(self.list_dir(relpath)):
132
queue.insert(i, relpath+'/'+basename)
136
def mkdir(self, relpath, mode=None):
137
"""Create a directory at the given path."""
140
path = self._abspath(relpath)
144
except (IOError, OSError),e:
145
self._translate_error(e, path)
147
def append(self, relpath, f, mode=None):
148
"""Append the text in the file-like object into the final location."""
149
abspath = self._abspath(relpath)
151
fp = open(abspath, 'ab')
152
# FIXME should we really be chmodding every time ? RBC 20060523
154
os.chmod(abspath, mode)
155
except (IOError, OSError),e:
156
self._translate_error(e, relpath)
157
# win32 workaround (tell on an unwritten file returns 0)
163
def copy(self, rel_from, rel_to):
164
"""Copy the item at rel_from to the location at rel_to"""
165
path_from = self._abspath(rel_from)
166
path_to = self._abspath(rel_to)
168
shutil.copy(path_from, path_to)
169
except (IOError, OSError),e:
170
# TODO: What about path_to?
171
self._translate_error(e, path_from)
173
def rename(self, rel_from, rel_to):
174
path_from = self._abspath(rel_from)
176
# *don't* call bzrlib.osutils.rename, because we want to
177
# detect errors on rename
178
os.rename(path_from, self._abspath(rel_to))
179
except (IOError, OSError),e:
180
# TODO: What about path_to?
181
self._translate_error(e, path_from)
183
def move(self, rel_from, rel_to):
184
"""Move the item at rel_from to the location at rel_to"""
185
path_from = self._abspath(rel_from)
186
path_to = self._abspath(rel_to)
189
# this version will delete the destination if necessary
190
rename(path_from, path_to)
191
except (IOError, OSError),e:
192
# TODO: What about path_to?
193
self._translate_error(e, path_from)
195
def delete(self, relpath):
196
"""Delete the item at relpath"""
199
path = self._abspath(relpath)
201
except (IOError, OSError),e:
202
self._translate_error(e, path)
204
def copy_to(self, relpaths, other, mode=None, pb=None):
205
"""Copy a set of entries from self into another Transport.
207
:param relpaths: A list/generator of entries to be copied.
209
if isinstance(other, LocalTransport):
210
# Both from & to are on the local filesystem
211
# Unfortunately, I can't think of anything faster than just
212
# copying them across, one by one :(
213
total = self._get_total(relpaths)
215
for path in relpaths:
216
self._update_pb(pb, 'copy-to', count, total)
218
mypath = self._abspath(path)
219
otherpath = other._abspath(path)
220
shutil.copy(mypath, otherpath)
222
os.chmod(otherpath, mode)
223
except (IOError, OSError),e:
224
self._translate_error(e, path)
228
return super(LocalTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
231
"""See Transport.listable."""
234
def list_dir(self, relpath):
235
"""Return a list of all files at the given location.
236
WARNING: many transports do not support this, so trying avoid using
237
it if at all possible.
239
path = self._abspath(relpath)
241
return [urllib.quote(entry) for entry in os.listdir(path)]
242
except (IOError, OSError), e:
243
self._translate_error(e, path)
245
def stat(self, relpath):
246
"""Return the stat information for a file.
250
path = self._abspath(relpath)
252
except (IOError, OSError),e:
253
self._translate_error(e, path)
255
def lock_read(self, relpath):
256
"""Lock the given file for shared (read) access.
257
:return: A lock object, which should be passed to Transport.unlock()
259
from bzrlib.lock import ReadLock
262
path = self._abspath(relpath)
263
return ReadLock(path)
264
except (IOError, OSError), e:
265
self._translate_error(e, path)
267
def lock_write(self, relpath):
268
"""Lock the given file for exclusive (write) access.
269
WARNING: many transports do not support this, so trying avoid using it
271
:return: A lock object, which should be passed to Transport.unlock()
273
from bzrlib.lock import WriteLock
274
return WriteLock(self._abspath(relpath))
276
def rmdir(self, relpath):
277
"""See Transport.rmdir."""
280
path = self._abspath(relpath)
282
except (IOError, OSError),e:
283
self._translate_error(e, path)
285
def _can_roundtrip_unix_modebits(self):
286
if sys.platform == 'win32':
293
class ScratchTransport(LocalTransport):
294
"""A transport that works in a temporary dir and cleans up after itself.
296
The dir only exists for the lifetime of the Python object.
297
Obviously you should not put anything precious in it.
300
def __init__(self, base=None):
302
base = tempfile.mkdtemp()
303
super(ScratchTransport, self).__init__(base)
306
rmtree(self.base, ignore_errors=True)
307
mutter("%r destroyed" % self)
310
class LocalRelpathServer(Server):
311
"""A pretend server for local transports, using relpaths."""
314
"""See Transport.Server.get_url."""
318
class LocalAbspathServer(Server):
319
"""A pretend server for local transports, using absolute paths."""
322
"""See Transport.Server.get_url."""
323
return os.path.abspath("")
326
class LocalURLServer(Server):
327
"""A pretend server for local transports, using file:// urls."""
330
"""See Transport.Server.get_url."""
331
# FIXME: \ to / on windows
332
return "file://%s" % os.path.abspath("")
335
def get_test_permutations():
336
"""Return the permutations to be used in testing."""
337
return [(LocalTransport, LocalRelpathServer),
338
(LocalTransport, LocalAbspathServer),
339
(LocalTransport, LocalURLServer),