1
# Copyright (C) 2005 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
16
"""Transport is an abstraction layer to handle file access.
18
The abstraction is to allow access from the local filesystem, as well
19
as remote (such as http or sftp).
22
from bzrlib.trace import mutter
23
from bzrlib.errors import (BzrError,
24
TransportError, TransportNotPossible, NonRelativePath,
25
NoSuchFile, FileExists, PermissionDenied,
28
_protocol_handlers = {
31
def register_transport(prefix, klass, override=True):
32
global _protocol_handlers
33
# trace messages commented out because they're typically
34
# run during import before trace is set up
35
if _protocol_handlers.has_key(prefix):
37
## mutter('overriding transport: %s => %s' % (prefix, klass.__name__))
38
_protocol_handlers[prefix] = klass
40
## mutter('registering transport: %s => %s' % (prefix, klass.__name__))
41
_protocol_handlers[prefix] = klass
44
class Transport(object):
45
"""This class encapsulates methods for retrieving or putting a file
46
from/to a storage location.
48
Most functions have a _multi variant, which allows you to queue up
49
multiple requests. They generally have a dumb base implementation
50
which just iterates over the arguments, but smart Transport
51
implementations can do pipelining.
52
In general implementations should support having a generator or a list
53
as an argument (ie always iterate, never index)
56
def __init__(self, base):
57
super(Transport, self).__init__()
60
def clone(self, offset=None):
61
"""Return a new Transport object, cloned from the current location,
62
using a subdirectory or parent directory. This allows connections
63
to be pooled, rather than a new one needed for each subdir.
65
raise NotImplementedError
67
def should_cache(self):
68
"""Return True if the data pulled across should be cached locally.
72
def _pump(self, from_file, to_file):
73
"""Most children will need to copy from one file-like
74
object or string to another one.
75
This just gives them something easy to call.
77
if isinstance(from_file, basestring):
78
to_file.write(from_file)
80
from bzrlib.osutils import pumpfile
81
pumpfile(from_file, to_file)
83
def _get_total(self, multi):
84
"""Try to figure out how many entries are in multi,
85
but if not possible, return None.
89
except TypeError: # We can't tell how many, because relpaths is a generator
92
def _update_pb(self, pb, msg, count, total):
93
"""Update the progress bar based on the current count
94
and total available, total may be None if it was
95
not possible to determine.
100
pb.update(msg, count, count+1)
102
pb.update(msg, count, total)
104
def _iterate_over(self, multi, func, pb, msg, expand=True):
105
"""Iterate over all entries in multi, passing them to func,
106
and update the progress bar as you go along.
108
:param expand: If True, the entries will be passed to the function
109
by expanding the tuple. If False, it will be passed
110
as a single parameter.
112
total = self._get_total(multi)
115
self._update_pb(pb, msg, count, total)
123
def abspath(self, relpath):
124
"""Return the full url to the given relative path.
125
This can be supplied with a string or a list
127
XXX: Robert Collins 20051016 - is this really needed in the public
130
raise NotImplementedError
132
def relpath(self, abspath):
133
"""Return the local path portion from a given absolute path.
135
This default implementation is not suitable for filesystems with
136
aliasing, such as that given by symlinks, where a path may not
137
start with our base, but still be a relpath once aliasing is
140
if not abspath.startswith(self.base):
141
raise NonRelativePath('path %r is not under base URL %r'
142
% (abspath, self.base))
144
return abspath[pl:].lstrip('/')
147
def has(self, relpath):
148
"""Does the file relpath exist?
150
Note that some transports MAY allow querying on directories, but this
151
is not part of the protocol.
153
raise NotImplementedError
155
def has_multi(self, relpaths, pb=None):
156
"""Return True/False for each entry in relpaths"""
157
total = self._get_total(relpaths)
159
for relpath in relpaths:
160
self._update_pb(pb, 'has', count, total)
161
yield self.has(relpath)
164
def iter_files_recursive(self):
165
"""Iter the relative paths of files in the transports sub-tree.
167
As with other listing functions, only some transports implement this,.
168
you may check via is_listable to determine if it will.
170
raise NotImplementedError
172
def get(self, relpath):
173
"""Get the file at the given relative path.
175
:param relpath: The relative path to the file
177
raise NotImplementedError
179
def get_multi(self, relpaths, pb=None):
180
"""Get a list of file-like objects, one for each entry in relpaths.
182
:param relpaths: A list of relative paths.
183
:param pb: An optional ProgressBar for indicating percent done.
184
:return: A list or generator of file-like objects
186
# TODO: Consider having this actually buffer the requests,
187
# in the default mode, it probably won't give worse performance,
188
# and all children wouldn't have to implement buffering
189
total = self._get_total(relpaths)
191
for relpath in relpaths:
192
self._update_pb(pb, 'get', count, total)
193
yield self.get(relpath)
196
def put(self, relpath, f):
197
"""Copy the file-like or string object into the location.
199
:param relpath: Location to put the contents, relative to base.
200
:param f: File-like or string object.
202
raise NotImplementedError
204
def put_multi(self, files, pb=None):
205
"""Put a set of files or strings into the location.
207
:param files: A list of tuples of relpath, file object [(path1, file1), (path2, file2),...]
208
:param pb: An optional ProgressBar for indicating percent done.
209
:return: The number of files copied.
211
return self._iterate_over(files, self.put, pb, 'put', expand=True)
213
def mkdir(self, relpath):
214
"""Create a directory at the given path."""
215
raise NotImplementedError
217
def mkdir_multi(self, relpaths, pb=None):
218
"""Create a group of directories"""
219
return self._iterate_over(relpaths, self.mkdir, pb, 'mkdir', expand=False)
221
def append(self, relpath, f):
222
"""Append the text in the file-like or string object to
223
the supplied location.
225
raise NotImplementedError
227
def append_multi(self, files, pb=None):
228
"""Append the text in each file-like or string object to
229
the supplied location.
231
:param files: A set of (path, f) entries
232
:param pb: An optional ProgressBar for indicating percent done.
234
return self._iterate_over(files, self.append, pb, 'append', expand=True)
236
def copy(self, rel_from, rel_to):
237
"""Copy the item at rel_from to the location at rel_to"""
238
raise NotImplementedError
240
def copy_multi(self, relpaths, pb=None):
241
"""Copy a bunch of entries.
243
:param relpaths: A list of tuples of the form [(from, to), (from, to),...]
245
# This is the non-pipelined implementation, so that
246
# implementors don't have to implement everything.
247
return self._iterate_over(relpaths, self.copy, pb, 'copy', expand=True)
249
def copy_to(self, relpaths, other, pb=None):
250
"""Copy a set of entries from self into another Transport.
252
:param relpaths: A list/generator of entries to be copied.
254
# The dummy implementation just does a simple get + put
255
def copy_entry(path):
256
other.put(path, self.get(path))
258
return self._iterate_over(relpaths, copy_entry, pb, 'copy_to', expand=False)
261
def move(self, rel_from, rel_to):
262
"""Move the item at rel_from to the location at rel_to"""
263
raise NotImplementedError
265
def move_multi(self, relpaths, pb=None):
266
"""Move a bunch of entries.
268
:param relpaths: A list of tuples of the form [(from1, to1), (from2, to2),...]
270
return self._iterate_over(relpaths, self.move, pb, 'move', expand=True)
272
def move_multi_to(self, relpaths, rel_to):
273
"""Move a bunch of entries to a single location.
274
This differs from move_multi in that you give a list of from, and
275
a single destination, rather than multiple destinations.
277
:param relpaths: A list of relative paths [from1, from2, from3, ...]
278
:param rel_to: A directory where each entry should be placed.
280
# This is not implemented, because you need to do special tricks to
281
# extract the basename, and add it to rel_to
282
raise NotImplementedError
284
def delete(self, relpath):
285
"""Delete the item at relpath"""
286
raise NotImplementedError
288
def delete_multi(self, relpaths, pb=None):
289
"""Queue up a bunch of deletes to be done.
291
return self._iterate_over(relpaths, self.delete, pb, 'delete', expand=False)
293
def stat(self, relpath):
294
"""Return the stat information for a file.
295
WARNING: This may not be implementable for all protocols, so use
297
NOTE: This returns an object with fields such as 'st_size'. It MAY
298
or MAY NOT return the literal result of an os.stat() call, so all
299
access should be via named fields.
300
ALSO NOTE: Stats of directories may not be supported on some
303
raise NotImplementedError
305
def stat_multi(self, relpaths, pb=None):
306
"""Stat multiple files and return the information.
308
#TODO: Is it worth making this a generator instead of a
312
stats.append(self.stat(path))
314
count = self._iterate_over(relpaths, gather, pb, 'stat', expand=False)
318
"""Return True if this store supports listing."""
319
raise NotImplementedError
321
def list_dir(self, relpath):
322
"""Return a list of all files at the given location.
323
WARNING: many transports do not support this, so trying avoid using
324
it if at all possible.
326
raise TransportNotPossible("This transport has not "
327
"implemented list_dir.")
329
def lock_read(self, relpath):
330
"""Lock the given file for shared (read) access.
331
WARNING: many transports do not support this, so trying avoid using it
333
:return: A lock object, which should contain an unlock() function.
335
raise NotImplementedError
337
def lock_write(self, relpath):
338
"""Lock the given file for exclusive (write) access.
339
WARNING: many transports do not support this, so trying avoid using it
341
:return: A lock object, which should contain an unlock() function.
343
raise NotImplementedError
346
def get_transport(base):
347
global _protocol_handlers
350
for proto, klass in _protocol_handlers.iteritems():
351
if proto is not None and base.startswith(proto):
353
# The default handler is the filesystem handler
354
# which has a lookup of None
355
return _protocol_handlers[None](base)
358
def register_lazy_transport(scheme, module, classname):
359
"""Register lazy-loaded transport class.
361
When opening a URL with the given scheme, load the module and then
362
instantiate the particular class.
365
mod = __import__(module, globals(), locals(), [classname])
366
klass = getattr(mod, classname)
368
register_transport(scheme, _loader)
371
def urlescape(relpath):
372
"""Escape relpath to be a valid url."""
373
# TODO utf8 it first. utf8relpath = relpath.encode('utf8')
375
return urllib.quote(relpath)
378
# None is the default transport, for things with no url scheme
379
register_lazy_transport(None, 'bzrlib.transport.local', 'LocalTransport')
380
register_lazy_transport('file://', 'bzrlib.transport.local', 'LocalTransport')
381
register_lazy_transport('sftp://', 'bzrlib.transport.sftp', 'SFTPTransport')
382
register_lazy_transport('http://', 'bzrlib.transport.http', 'HttpTransport')
383
register_lazy_transport('https://', 'bzrlib.transport.http', 'HttpTransport')