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
34
if _protocol_handlers.has_key(prefix):
36
mutter('overriding transport: %s => %s' % (prefix, klass.__name__))
37
_protocol_handlers[prefix] = klass
39
mutter('registering transport: %s => %s' % (prefix, klass.__name__))
40
_protocol_handlers[prefix] = klass
42
class Transport(object):
43
"""This class encapsulates methods for retrieving or putting a file
44
from/to a storage location.
46
Most functions have a _multi variant, which allows you to queue up
47
multiple requests. They generally have a dumb base implementation
48
which just iterates over the arguments, but smart Transport
49
implementations can do pipelining.
50
In general implementations should support having a generator or a list
51
as an argument (ie always iterate, never index)
54
def __init__(self, base):
55
super(Transport, self).__init__()
58
def clone(self, offset=None):
59
"""Return a new Transport object, cloned from the current location,
60
using a subdirectory or parent directory. This allows connections
61
to be pooled, rather than a new one needed for each subdir.
63
raise NotImplementedError
65
def should_cache(self):
66
"""Return True if the data pulled across should be cached locally.
70
def _pump(self, from_file, to_file):
71
"""Most children will need to copy from one file-like
72
object or string to another one.
73
This just gives them something easy to call.
75
if isinstance(from_file, basestring):
76
to_file.write(from_file)
78
from bzrlib.osutils import pumpfile
79
pumpfile(from_file, to_file)
81
def _get_total(self, multi):
82
"""Try to figure out how many entries are in multi,
83
but if not possible, return None.
87
except TypeError: # We can't tell how many, because relpaths is a generator
90
def _update_pb(self, pb, msg, count, total):
91
"""Update the progress bar based on the current count
92
and total available, total may be None if it was
93
not possible to determine.
98
pb.update(msg, count, count+1)
100
pb.update(msg, count, total)
102
def _iterate_over(self, multi, func, pb, msg, expand=True):
103
"""Iterate over all entries in multi, passing them to func,
104
and update the progress bar as you go along.
106
:param expand: If True, the entries will be passed to the function
107
by expanding the tuple. If False, it will be passed
108
as a single parameter.
110
total = self._get_total(multi)
113
self._update_pb(pb, msg, count, total)
121
def abspath(self, relpath):
122
"""Return the full url to the given relative path.
123
This can be supplied with a string or a list
125
raise NotImplementedError
127
def relpath(self, abspath):
128
"""Return the local path portion from a given absolute path.
130
raise NotImplementedError
132
def has(self, relpath):
133
"""Does the target location exist?"""
134
raise NotImplementedError
136
def has_multi(self, relpaths, pb=None):
137
"""Return True/False for each entry in relpaths"""
138
total = self._get_total(relpaths)
140
for relpath in relpaths:
141
self._update_pb(pb, 'has', count, total)
142
yield self.has(relpath)
145
def get(self, relpath):
146
"""Get the file at the given relative path.
148
:param relpath: The relative path to the file
150
raise NotImplementedError
152
def get_multi(self, relpaths, pb=None):
153
"""Get a list of file-like objects, one for each entry in relpaths.
155
:param relpaths: A list of relative paths.
156
:param pb: An optional ProgressBar for indicating percent done.
157
:return: A list or generator of file-like objects
159
# TODO: Consider having this actually buffer the requests,
160
# in the default mode, it probably won't give worse performance,
161
# and all children wouldn't have to implement buffering
162
total = self._get_total(relpaths)
164
for relpath in relpaths:
165
self._update_pb(pb, 'get', count, total)
166
yield self.get(relpath)
169
def get_partial(self, relpath, start, length=None):
170
"""Get just part of a file.
172
:param relpath: Path to the file, relative to base
173
:param start: The starting position to read from
174
:param length: The length to read. A length of None indicates
175
read to the end of the file.
176
:return: A file-like object containing at least the specified bytes.
177
Some implementations may return objects which can be read
178
past this length, but this is not guaranteed.
180
raise NotImplementedError
182
def get_partial_multi(self, offsets, pb=None):
183
"""Put a set of files or strings into the location.
185
Requesting multiple portions of the same file can be dangerous.
187
:param offsets: A list of tuples of relpath, start, length
188
[(path1, start1, length1),
189
(path2, start2, length2),
190
(path3, start3), ...]
191
length is optional, defaulting to None
192
:param pb: An optional ProgressBar for indicating percent done.
193
:return: A generator of file-like objects.
195
total = self._get_total(offsets)
197
for offset in offsets:
198
self._update_pb(pb, 'get_partial', count, total)
199
yield self.get_partial(*offset)
202
def put(self, relpath, f):
203
"""Copy the file-like or string object into the location.
205
:param relpath: Location to put the contents, relative to base.
206
:param f: File-like or string object.
208
raise NotImplementedError
210
def put_multi(self, files, pb=None):
211
"""Put a set of files or strings into the location.
213
:param files: A list of tuples of relpath, file object [(path1, file1), (path2, file2),...]
214
:param pb: An optional ProgressBar for indicating percent done.
215
:return: The number of files copied.
217
return self._iterate_over(files, self.put, pb, 'put', expand=True)
219
def mkdir(self, relpath):
220
"""Create a directory at the given path."""
221
raise NotImplementedError
223
def mkdir_multi(self, relpaths, pb=None):
224
"""Create a group of directories"""
225
return self._iterate_over(relpaths, self.mkdir, pb, 'mkdir', expand=False)
227
def append(self, relpath, f):
228
"""Append the text in the file-like or string object to
229
the supplied location.
231
raise NotImplementedError
233
def append_multi(self, files, pb=None):
234
"""Append the text in each file-like or string object to
235
the supplied location.
237
:param files: A set of (path, f) entries
238
:param pb: An optional ProgressBar for indicating percent done.
240
return self._iterate_over(files, self.append, pb, 'append', expand=True)
242
def copy(self, rel_from, rel_to):
243
"""Copy the item at rel_from to the location at rel_to"""
244
raise NotImplementedError
246
def copy_multi(self, relpaths, pb=None):
247
"""Copy a bunch of entries.
249
:param relpaths: A list of tuples of the form [(from, to), (from, to),...]
251
# This is the non-pipelined implementation, so that
252
# implementors don't have to implement everything.
253
return self._iterate_over(relpaths, self.copy, pb, 'copy', expand=True)
255
def copy_to(self, relpaths, other, pb=None):
256
"""Copy a set of entries from self into another Transport.
258
:param relpaths: A list/generator of entries to be copied.
260
# The dummy implementation just does a simple get + put
261
def copy_entry(path):
262
other.put(path, self.get(path))
264
return self._iterate_over(relpaths, copy_entry, pb, 'copy_to', expand=False)
267
def move(self, rel_from, rel_to):
268
"""Move the item at rel_from to the location at rel_to"""
269
raise NotImplementedError
271
def move_multi(self, relpaths, pb=None):
272
"""Move a bunch of entries.
274
:param relpaths: A list of tuples of the form [(from1, to1), (from2, to2),...]
276
return self._iterate_over(relpaths, self.move, pb, 'move', expand=True)
278
def move_multi_to(self, relpaths, rel_to):
279
"""Move a bunch of entries to a single location.
280
This differs from move_multi in that you give a list of from, and
281
a single destination, rather than multiple destinations.
283
:param relpaths: A list of relative paths [from1, from2, from3, ...]
284
:param rel_to: A directory where each entry should be placed.
286
# This is not implemented, because you need to do special tricks to
287
# extract the basename, and add it to rel_to
288
raise NotImplementedError
290
def delete(self, relpath):
291
"""Delete the item at relpath"""
292
raise NotImplementedError
294
def delete_multi(self, relpaths, pb=None):
295
"""Queue up a bunch of deletes to be done.
297
return self._iterate_over(relpaths, self.delete, pb, 'delete', expand=False)
299
def stat(self, relpath):
300
"""Return the stat information for a file.
301
WARNING: This may not be implementable for all protocols, so use
304
raise NotImplementedError
306
def stat_multi(self, relpaths, pb=None):
307
"""Stat multiple files and return the information.
309
#TODO: Is it worth making this a generator instead of a
313
stats.append(self.stat(path))
315
count = self._iterate_over(relpaths, gather, pb, 'stat', expand=False)
319
def list_dir(self, relpath):
320
"""Return a list of all files at the given location.
321
WARNING: many transports do not support this, so trying avoid using
322
it if at all possible.
324
raise NotImplementedError
326
def lock_read(self, relpath):
327
"""Lock the given file for shared (read) access.
328
WARNING: many transports do not support this, so trying avoid using it
330
:return: A lock object, which should contain an unlock() function.
332
raise NotImplementedError
334
def lock_write(self, relpath):
335
"""Lock the given file for exclusive (write) access.
336
WARNING: many transports do not support this, so trying avoid using it
338
:return: A lock object, which should contain an unlock() function.
340
raise NotImplementedError
343
def get_transport(base):
344
global _protocol_handlers
347
for proto, klass in _protocol_handlers.iteritems():
348
if proto is not None and base.startswith(proto):
350
# The default handler is the filesystem handler
351
# which has a lookup of None
352
return _protocol_handlers[None](base)
354
# Local transport should always be initialized
355
import bzrlib.transport.local