3
This module contains the basic class handling transport of
7
from bzrlib.trace import mutter
8
from bzrlib.errors import BzrError
10
_protocol_handlers = {
13
def register_transport(prefix, klass, override=True):
14
global _protocol_handlers
16
if _protocol_handlers.has_key(prefix):
18
mutter('overriding transport: %s => %s' % (prefix, klass.__name__))
19
_protocol_handlers[prefix] = klass
21
mutter('registering transport: %s => %s' % (prefix, klass.__name__))
22
_protocol_handlers[prefix] = klass
24
class TransportError(BzrError):
25
"""All errors thrown by Transport implementations should derive
28
def __init__(self, msg=None, orig_error=None):
29
if msg is None and orig_error is not None:
31
BzrError.__init__(self, msg)
33
self.orig_error = orig_error
35
class AsyncError(TransportError):
38
# A set of semi-meaningful errors which can be thrown
39
class TransportNotPossible(TransportError):
40
"""This is for transports where a specific function is explicitly not
41
possible. Such as pushing files to an HTTP server.
45
class NonRelativePath(TransportError):
46
"""An absolute path was supplied, that could not be decoded into
51
class NoSuchFile(TransportError, IOError):
52
"""A get() was issued for a file that doesn't exist."""
53
def __init__(self, msg=None, orig_error=None):
55
TransportError.__init__(self, msg=msg, orig_error=orig_error)
56
IOError.__init__(self, errno.ENOENT, self.msg)
58
class FileExists(TransportError, OSError):
59
"""An operation was attempted, which would overwrite an entry,
60
but overwritting is not supported.
62
mkdir() can throw this, but put() just overwites existing files.
64
def __init__(self, msg=None, orig_error=None):
66
TransportError.__init__(self, msg=msg, orig_error=orig_error)
67
OSError.__init__(self, errno.EEXIST, self.msg)
69
class PermissionDenied(TransportError):
70
"""An operation cannot succeed because of a lack of permissions."""
73
class ConnectionReset(TransportError):
74
"""The connection has been closed."""
77
class AsyncFile(object):
78
"""This will be returned from a Transport object,
79
whenever an asyncronous get is requested.
81
_max_write_buffer = 8192
85
self.write_buffer = []
86
self._finalized = False
88
def _total_len(self, buffer):
95
"""This will block until all data has been buffered for
96
reading, or all data has been flushed for writing.
97
Once this is called, you can no longer write more data.
99
raise NotImplementedError
102
"""Can we read some data without blocking"""
103
return len(self.read_buffer) > 0
106
"""Can we write some more data out without blocking?"""
107
return (not self._finalized \
108
and self._total_len(self.write_buffer) < self._max_write_buffer)
110
def write(self, chunk):
112
raise AsyncError('write attempted on finalized file.')
113
self.write_buffer.append(chunk)
115
def read(self, size=None):
117
return ''.join(self.read_buffer)
121
buf = self.read_buffer.pop(0)
122
if len(buf) + len(out) < size:
125
left = size - len(out)
128
self.read_buffer.insert(0, buf)
132
class Transport(object):
133
"""This class encapsulates methods for retrieving or putting a file
134
from/to a storage location.
136
Most functions have a _multi variant, which allows you to queue up
137
multiple requests. They generally have a dumb base implementation
138
which just iterates over the arguments, but smart Transport
139
implementations can do pipelining.
140
In general implementations should support having a generator or a list
141
as an argument (ie always iterate, never index)
144
def __init__(self, base):
145
super(Transport, self).__init__()
148
def clone(self, offset=None):
149
"""Return a new Transport object, cloned from the current location,
150
using a subdirectory. This allows connections to be pooled,
151
rather than a new one needed for each subdir.
153
raise NotImplementedError
155
def should_cache(self):
156
"""Return True if the data pulled across should be cached locally.
160
def _pump(self, from_file, to_file):
161
"""Most children will need to copy from one file-like
162
object or string to another one.
163
This just gives them something easy to call.
165
if isinstance(from_file, basestring):
166
to_file.write(from_file)
168
from bzrlib.osutils import pumpfile
169
pumpfile(from_file, to_file)
171
def _get_total(self, multi):
172
"""Try to figure out how many entries are in multi,
173
but if not possible, return None.
177
except TypeError: # We can't tell how many, because relpaths is a generator
180
def _update_pb(self, pb, msg, count, total):
181
"""Update the progress bar based on the current count
182
and total available, total may be None if it was
183
not possible to determine.
188
pb.update(msg, count, count+1)
190
pb.update(msg, count, total)
192
def _iterate_over(self, multi, func, pb, msg, expand=True):
193
"""Iterate over all entries in multi, passing them to func,
194
and update the progress bar as you go along.
196
:param expand: If True, the entries will be passed to the function
197
by expanding the tuple. If False, it will be passed
198
as a single parameter.
200
total = self._get_total(multi)
203
self._update_pb(pb, msg, count, total)
211
def abspath(self, relpath):
212
"""Return the full url to the given relative path.
213
This can be supplied with a string or a list
215
raise NotImplementedError
217
def relpath(self, abspath):
218
"""Return the local path portion from a given absolute path.
220
raise NotImplementedError
222
def has(self, relpath):
223
"""Does the target location exist?"""
224
raise NotImplementedError
226
def has_multi(self, relpaths, pb=None):
227
"""Return True/False for each entry in relpaths"""
228
total = self._get_total(relpaths)
230
for relpath in relpaths:
231
self._update_pb(pb, 'has', count, total)
232
yield self.has(relpath)
235
def get(self, relpath):
236
"""Get the file at the given relative path.
238
:param relpath: The relative path to the file
240
raise NotImplementedError
242
def get_partial(self, relpath, portion):
243
"""Get just part of a file.
245
:param relpath: Path to the file, relative to base
246
:param portion: A tuple of [start,length).
247
Length can be -1 indicating copy until the end
248
of the file. If the file ends before length bytes,
249
just the set of bytes will be returned (think read())
250
:return: A file-like object containing at least the specified bytes.
251
Some implementations may return objects which can be read
252
past this length, but this is not guaranteed.
254
raise NotImplementedError
256
def get_partial_multi(self, files, pb=None):
257
"""Put a set of files or strings into the location.
259
Requesting multiple portions of the same file can be dangerous.
261
:param files: A list of tuples of relpath, portion
262
[(path1, portion1), (path2, portion2),...]
263
:param pb: An optional ProgressBar for indicating percent done.
264
:return: A generator of file-like objects.
266
def get_partial(relpath, portion):
267
yield self.get_partial(relpath, portion)
268
self._iterate_over(files, get_partial, pb, 'get_partial', expand=True)
270
def get_multi(self, relpaths, pb=None):
271
"""Get a list of file-like objects, one for each entry in relpaths.
273
:param relpaths: A list of relative paths.
274
:param pb: An optional ProgressBar for indicating percent done.
275
:return: A list or generator of file-like objects
277
# TODO: Consider having this actually buffer the requests,
278
# in the default mode, it probably won't give worse performance,
279
# and all children wouldn't have to implement buffering
280
total = self._get_total(relpaths)
282
for relpath in relpaths:
283
self._update_pb(pb, 'get', count, total)
284
yield self.get(relpath)
287
def put(self, relpath, f):
288
"""Copy the file-like or string object into the location.
290
:param relpath: Location to put the contents, relative to base.
291
:param f: File-like or string object.
293
raise NotImplementedError
295
def put_multi(self, files, pb=None):
296
"""Put a set of files or strings into the location.
298
:param files: A list of tuples of relpath, file object [(path1, file1), (path2, file2),...]
299
:param pb: An optional ProgressBar for indicating percent done.
300
:return: The number of files copied.
304
return self._iterate_over(files, put, pb, 'put', expand=True)
306
def mkdir(self, relpath):
307
"""Create a directory at the given path."""
308
raise NotImplementedError
310
def mkdir_multi(self, relpaths, pb=None):
311
"""Create a group of directories"""
312
return self._iterate_over(relpaths, self.mkdir, pb, 'mkdir', expand=False)
314
def append(self, relpath, f):
315
"""Append the text in the file-like or string object to
316
the supplied location.
318
raise NotImplementedError
320
def append_multi(self, files):
321
"""Append the text in each file-like or string object to
322
the supplied location.
324
:param files: A set of (path, f) entries
325
:param pb: An optional ProgressBar for indicating percent done.
327
return self._iterate_over(files, self.append, pb, 'append', expand=True)
329
def copy(self, rel_from, rel_to):
330
"""Copy the item at rel_from to the location at rel_to"""
331
raise NotImplementedError
333
def copy_multi(self, relpaths, pb=None):
334
"""Copy a bunch of entries.
336
:param relpaths: A list of tuples of the form [(from, to), (from, to),...]
338
# This is the non-pipelined implementation, so that
339
# implementors don't have to implement everything.
340
return self._iterate_over(relpaths, self.copy, pb, 'copy', expand=True)
342
def copy_to(self, relpaths, other, pb=None):
343
"""Copy a set of entries from self into another Transport.
345
:param relpaths: A list/generator of entries to be copied.
347
# The dummy implementation just does a simple get + put
348
def copy_entry(path):
349
other.put(path, self.get(path))
351
return self._iterate_over(relpaths, copy_entry, pb, 'copy_to', expand=False)
354
def move(self, rel_from, rel_to):
355
"""Move the item at rel_from to the location at rel_to"""
356
raise NotImplementedError
358
def move_multi(self, relpaths, pb=None):
359
"""Move a bunch of entries.
361
:param relpaths: A list of tuples of the form [(from1, to1), (from2, to2),...]
363
return self._iterate_over(relpaths, self.move, pb, 'move', expand=True)
365
def move_multi_to(self, relpaths, rel_to):
366
"""Move a bunch of entries to a single location.
367
This differs from move_multi in that you give a list of from, and
368
a single destination, rather than multiple destinations.
370
:param relpaths: A list of relative paths [from1, from2, from3, ...]
371
:param rel_to: A directory where each entry should be placed.
373
# This is not implemented, because you need to do special tricks to
374
# extract the basename, and add it to rel_to
375
raise NotImplementedError
377
def delete(self, relpath):
378
"""Delete the item at relpath"""
379
raise NotImplementedError
381
def delete_multi(self, relpaths, pb=None):
382
"""Queue up a bunch of deletes to be done.
384
return self._iterate_over(relpaths, self.delete, pb, 'delete', expand=False)
386
def stat(self, relpath):
387
"""Return the stat information for a file.
388
WARNING: This may not be implementable for all protocols, so use
391
raise NotImplementedError
393
def stat_multi(self, relpaths, pb=None):
394
"""Stat multiple files and return the information.
396
#TODO: Is it worth making this a generator instead of a
400
stats.append(self.stat(path))
402
count = self._iterate_over(relpaths, gather, pb, 'stat', expand=False)
406
def list_dir(self, relpath):
407
"""Return a list of all files at the given location.
408
WARNING: many transports do not support this, so trying avoid using
409
it if at all possible.
411
raise NotImplementedError
413
def lock_read(self, relpath):
414
"""Lock the given file for shared (read) access.
415
WARNING: many transports do not support this, so trying avoid using it
417
:return: A lock object, which should contain an unlock() function.
419
raise NotImplementedError
421
def lock_write(self, relpath):
422
"""Lock the given file for exclusive (write) access.
423
WARNING: many transports do not support this, so trying avoid using it
425
:return: A lock object, which should contain an unlock() function.
427
raise NotImplementedError
431
global _protocol_handlers
434
for proto, klass in _protocol_handlers.iteritems():
435
if proto is not None and base.startswith(proto):
437
# The default handler is the filesystem handler
438
# which has a lookup of None
439
return _protocol_handlers[None](base)
441
# Local transport should always be initialized
442
import bzrlib.transport.local