~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/__init__.py

  • Committer: Robert Collins
  • Date: 2005-10-02 21:51:29 UTC
  • mfrom: (1396)
  • mto: This revision was merged to the branch mainline in revision 1397.
  • Revision ID: robertc@robertcollins.net-20051002215128-5686c7d24bf9bdb9
merge from martins newformat branch - brings in transport abstraction

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 Canonical Ltd
 
2
 
 
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.
 
7
 
 
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.
 
12
 
 
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.
 
17
 
 
18
The abstraction is to allow access from the local filesystem, as well
 
19
as remote (such as http or sftp).
 
20
"""
 
21
 
 
22
from bzrlib.trace import mutter
 
23
from bzrlib.errors import (BzrError, 
 
24
    TransportError, TransportNotPossible, NonRelativePath,
 
25
    NoSuchFile, FileExists, PermissionDenied,
 
26
    ConnectionReset)
 
27
 
 
28
_protocol_handlers = {
 
29
}
 
30
 
 
31
def register_transport(prefix, klass, override=True):
 
32
    global _protocol_handlers
 
33
 
 
34
    if _protocol_handlers.has_key(prefix):
 
35
        if override:
 
36
            mutter('overriding transport: %s => %s' % (prefix, klass.__name__))
 
37
            _protocol_handlers[prefix] = klass
 
38
    else:
 
39
        mutter('registering transport: %s => %s' % (prefix, klass.__name__))
 
40
        _protocol_handlers[prefix] = klass
 
41
 
 
42
class Transport(object):
 
43
    """This class encapsulates methods for retrieving or putting a file
 
44
    from/to a storage location.
 
45
 
 
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)
 
52
    """
 
53
 
 
54
    def __init__(self, base):
 
55
        super(Transport, self).__init__()
 
56
        self.base = base
 
57
 
 
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.
 
62
        """
 
63
        raise NotImplementedError
 
64
 
 
65
    def should_cache(self):
 
66
        """Return True if the data pulled across should be cached locally.
 
67
        """
 
68
        return False
 
69
 
 
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.
 
74
        """
 
75
        if isinstance(from_file, basestring):
 
76
            to_file.write(from_file)
 
77
        else:
 
78
            from bzrlib.osutils import pumpfile
 
79
            pumpfile(from_file, to_file)
 
80
 
 
81
    def _get_total(self, multi):
 
82
        """Try to figure out how many entries are in multi,
 
83
        but if not possible, return None.
 
84
        """
 
85
        try:
 
86
            return len(multi)
 
87
        except TypeError: # We can't tell how many, because relpaths is a generator
 
88
            return None
 
89
 
 
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.
 
94
        """
 
95
        if pb is None:
 
96
            return
 
97
        if total is None:
 
98
            pb.update(msg, count, count+1)
 
99
        else:
 
100
            pb.update(msg, count, total)
 
101
 
 
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.
 
105
 
 
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.
 
109
        """
 
110
        total = self._get_total(multi)
 
111
        count = 0
 
112
        for entry in multi:
 
113
            self._update_pb(pb, msg, count, total)
 
114
            if expand:
 
115
                func(*entry)
 
116
            else:
 
117
                func(entry)
 
118
            count += 1
 
119
        return count
 
120
 
 
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
 
124
        """
 
125
        raise NotImplementedError
 
126
 
 
127
    def relpath(self, abspath):
 
128
        """Return the local path portion from a given absolute path.
 
129
        """
 
130
        raise NotImplementedError
 
131
 
 
132
    def has(self, relpath):
 
133
        """Does the target location exist?"""
 
134
        raise NotImplementedError
 
135
 
 
136
    def has_multi(self, relpaths, pb=None):
 
137
        """Return True/False for each entry in relpaths"""
 
138
        total = self._get_total(relpaths)
 
139
        count = 0
 
140
        for relpath in relpaths:
 
141
            self._update_pb(pb, 'has', count, total)
 
142
            yield self.has(relpath)
 
143
            count += 1
 
144
 
 
145
    def get(self, relpath):
 
146
        """Get the file at the given relative path.
 
147
 
 
148
        :param relpath: The relative path to the file
 
149
        """
 
150
        raise NotImplementedError
 
151
 
 
152
    def get_multi(self, relpaths, pb=None):
 
153
        """Get a list of file-like objects, one for each entry in relpaths.
 
154
 
 
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
 
158
        """
 
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)
 
163
        count = 0
 
164
        for relpath in relpaths:
 
165
            self._update_pb(pb, 'get', count, total)
 
166
            yield self.get(relpath)
 
167
            count += 1
 
168
 
 
169
    def get_partial(self, relpath, start, length=None):
 
170
        """Get just part of a file.
 
171
 
 
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.
 
179
        """
 
180
        raise NotImplementedError
 
181
 
 
182
    def get_partial_multi(self, offsets, pb=None):
 
183
        """Put a set of files or strings into the location.
 
184
 
 
185
        Requesting multiple portions of the same file can be dangerous.
 
186
 
 
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.
 
194
        """
 
195
        total = self._get_total(offsets)
 
196
        count = 0
 
197
        for offset in offsets:
 
198
            self._update_pb(pb, 'get_partial', count, total)
 
199
            yield self.get_partial(*offset)
 
200
            count += 1
 
201
 
 
202
    def put(self, relpath, f):
 
203
        """Copy the file-like or string object into the location.
 
204
 
 
205
        :param relpath: Location to put the contents, relative to base.
 
206
        :param f:       File-like or string object.
 
207
        """
 
208
        raise NotImplementedError
 
209
 
 
210
    def put_multi(self, files, pb=None):
 
211
        """Put a set of files or strings into the location.
 
212
 
 
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.
 
216
        """
 
217
        return self._iterate_over(files, self.put, pb, 'put', expand=True)
 
218
 
 
219
    def mkdir(self, relpath):
 
220
        """Create a directory at the given path."""
 
221
        raise NotImplementedError
 
222
 
 
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)
 
226
 
 
227
    def append(self, relpath, f):
 
228
        """Append the text in the file-like or string object to 
 
229
        the supplied location.
 
230
        """
 
231
        raise NotImplementedError
 
232
 
 
233
    def append_multi(self, files, pb=None):
 
234
        """Append the text in each file-like or string object to
 
235
        the supplied location.
 
236
 
 
237
        :param files: A set of (path, f) entries
 
238
        :param pb:  An optional ProgressBar for indicating percent done.
 
239
        """
 
240
        return self._iterate_over(files, self.append, pb, 'append', expand=True)
 
241
 
 
242
    def copy(self, rel_from, rel_to):
 
243
        """Copy the item at rel_from to the location at rel_to"""
 
244
        raise NotImplementedError
 
245
 
 
246
    def copy_multi(self, relpaths, pb=None):
 
247
        """Copy a bunch of entries.
 
248
        
 
249
        :param relpaths: A list of tuples of the form [(from, to), (from, to),...]
 
250
        """
 
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)
 
254
 
 
255
    def copy_to(self, relpaths, other, pb=None):
 
256
        """Copy a set of entries from self into another Transport.
 
257
 
 
258
        :param relpaths: A list/generator of entries to be copied.
 
259
        """
 
260
        # The dummy implementation just does a simple get + put
 
261
        def copy_entry(path):
 
262
            other.put(path, self.get(path))
 
263
 
 
264
        return self._iterate_over(relpaths, copy_entry, pb, 'copy_to', expand=False)
 
265
 
 
266
 
 
267
    def move(self, rel_from, rel_to):
 
268
        """Move the item at rel_from to the location at rel_to"""
 
269
        raise NotImplementedError
 
270
 
 
271
    def move_multi(self, relpaths, pb=None):
 
272
        """Move a bunch of entries.
 
273
        
 
274
        :param relpaths: A list of tuples of the form [(from1, to1), (from2, to2),...]
 
275
        """
 
276
        return self._iterate_over(relpaths, self.move, pb, 'move', expand=True)
 
277
 
 
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.
 
282
 
 
283
        :param relpaths: A list of relative paths [from1, from2, from3, ...]
 
284
        :param rel_to: A directory where each entry should be placed.
 
285
        """
 
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
 
289
 
 
290
    def delete(self, relpath):
 
291
        """Delete the item at relpath"""
 
292
        raise NotImplementedError
 
293
 
 
294
    def delete_multi(self, relpaths, pb=None):
 
295
        """Queue up a bunch of deletes to be done.
 
296
        """
 
297
        return self._iterate_over(relpaths, self.delete, pb, 'delete', expand=False)
 
298
 
 
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
 
302
        sparingly.
 
303
        """
 
304
        raise NotImplementedError
 
305
 
 
306
    def stat_multi(self, relpaths, pb=None):
 
307
        """Stat multiple files and return the information.
 
308
        """
 
309
        #TODO:  Is it worth making this a generator instead of a
 
310
        #       returning a list?
 
311
        stats = []
 
312
        def gather(path):
 
313
            stats.append(self.stat(path))
 
314
 
 
315
        count = self._iterate_over(relpaths, gather, pb, 'stat', expand=False)
 
316
        return stats
 
317
 
 
318
 
 
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.
 
323
        """
 
324
        raise NotImplementedError
 
325
 
 
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
 
329
 
 
330
        :return: A lock object, which should contain an unlock() function.
 
331
        """
 
332
        raise NotImplementedError
 
333
 
 
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
 
337
 
 
338
        :return: A lock object, which should contain an unlock() function.
 
339
        """
 
340
        raise NotImplementedError
 
341
 
 
342
 
 
343
def get_transport(base):
 
344
    global _protocol_handlers
 
345
    if base is None:
 
346
        base = '.'
 
347
    for proto, klass in _protocol_handlers.iteritems():
 
348
        if proto is not None and base.startswith(proto):
 
349
            return klass(base)
 
350
    # The default handler is the filesystem handler
 
351
    # which has a lookup of None
 
352
    return _protocol_handlers[None](base)
 
353
 
 
354
# Local transport should always be initialized
 
355
import bzrlib.transport.local