~bzr-pqm/bzr/bzr.dev

1185.11.19 by John Arbash Meinel
Testing put and append, also testing agaist file-like objects as well as strings.
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).
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
20
"""
21
907.1.45 by John Arbash Meinel
Switch to registering protocol handlers, rather than just updating a dictionary.
22
from bzrlib.trace import mutter
1185.11.9 by John Arbash Meinel
Most tests pass, some problems with unavailable socket recv
23
from bzrlib.errors import (BzrError, 
24
    TransportError, TransportNotPossible, NonRelativePath,
25
    NoSuchFile, FileExists, PermissionDenied,
26
    ConnectionReset)
907.1.45 by John Arbash Meinel
Switch to registering protocol handlers, rather than just updating a dictionary.
27
28
_protocol_handlers = {
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
29
}
30
907.1.45 by John Arbash Meinel
Switch to registering protocol handlers, rather than just updating a dictionary.
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
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
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.
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
50
    In general implementations should support having a generator or a list
51
    as an argument (ie always iterate, never index)
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
52
    """
53
54
    def __init__(self, base):
907.1.50 by John Arbash Meinel
Removed encode/decode from Transport.put/get, added more exceptions that can be thrown.
55
        super(Transport, self).__init__()
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
56
        self.base = base
57
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
58
    def clone(self, offset=None):
59
        """Return a new Transport object, cloned from the current location,
1185.11.6 by John Arbash Meinel
Made HttpTransport handle a request for a parent directory differently.
60
        using a subdirectory or parent directory. This allows connections 
61
        to be pooled, rather than a new one needed for each subdir.
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
62
        """
63
        raise NotImplementedError
64
907.1.32 by John Arbash Meinel
Renaming is_remote to should_cache as it is more appropriate.
65
    def should_cache(self):
66
        """Return True if the data pulled across should be cached locally.
907.1.22 by John Arbash Meinel
Fixed some encoding issues, added is_remote function for Transport objects.
67
        """
907.1.32 by John Arbash Meinel
Renaming is_remote to should_cache as it is more appropriate.
68
        return False
907.1.22 by John Arbash Meinel
Fixed some encoding issues, added is_remote function for Transport objects.
69
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
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):
907.1.8 by John Arbash Meinel
Changed the format for abspath. Updated branch to use a hidden _transport
76
            to_file.write(from_file)
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
77
        else:
78
            from bzrlib.osutils import pumpfile
79
            pumpfile(from_file, to_file)
80
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
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
        """
907.1.14 by John Arbash Meinel
Handling some transport functions which take only a single argument.
95
        if pb is None:
96
            return
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
97
        if total is None:
98
            pb.update(msg, count, count+1)
99
        else:
100
            pb.update(msg, count, total)
101
907.1.14 by John Arbash Meinel
Handling some transport functions which take only a single argument.
102
    def _iterate_over(self, multi, func, pb, msg, expand=True):
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
103
        """Iterate over all entries in multi, passing them to func,
104
        and update the progress bar as you go along.
907.1.14 by John Arbash Meinel
Handling some transport functions which take only a single argument.
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.
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
109
        """
110
        total = self._get_total(multi)
111
        count = 0
112
        for entry in multi:
113
            self._update_pb(pb, msg, count, total)
907.1.14 by John Arbash Meinel
Handling some transport functions which take only a single argument.
114
            if expand:
115
                func(*entry)
116
            else:
117
                func(entry)
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
118
            count += 1
119
        return count
120
907.1.34 by John Arbash Meinel
Fixing append(), cleaning up function locations.
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
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
132
    def has(self, relpath):
133
        """Does the target location exist?"""
134
        raise NotImplementedError
135
907.1.36 by John Arbash Meinel
Moving the multi-get functionality higher up into the Branch class.
136
    def has_multi(self, relpaths, pb=None):
907.1.34 by John Arbash Meinel
Fixing append(), cleaning up function locations.
137
        """Return True/False for each entry in relpaths"""
138
        total = self._get_total(relpaths)
139
        count = 0
140
        for relpath in relpaths:
907.1.36 by John Arbash Meinel
Moving the multi-get functionality higher up into the Branch class.
141
            self._update_pb(pb, 'has', count, total)
907.1.34 by John Arbash Meinel
Fixing append(), cleaning up function locations.
142
            yield self.has(relpath)
143
            count += 1
144
907.1.50 by John Arbash Meinel
Removed encode/decode from Transport.put/get, added more exceptions that can be thrown.
145
    def get(self, relpath):
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
146
        """Get the file at the given relative path.
907.1.20 by John Arbash Meinel
Removed Transport.open(), making get + put encode/decode to utf-8
147
148
        :param relpath: The relative path to the file
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
149
        """
150
        raise NotImplementedError
151
907.1.50 by John Arbash Meinel
Removed encode/decode from Transport.put/get, added more exceptions that can be thrown.
152
    def get_multi(self, relpaths, pb=None):
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
153
        """Get a list of file-like objects, one for each entry in relpaths.
154
155
        :param relpaths: A list of relative paths.
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
156
        :param pb:  An optional ProgressBar for indicating percent done.
157
        :return: A list or generator of file-like objects
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
158
        """
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
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
907.1.16 by John Arbash Meinel
Fixing a few cut&paste typos.
162
        total = self._get_total(relpaths)
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
163
        count = 0
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
164
        for relpath in relpaths:
907.1.16 by John Arbash Meinel
Fixing a few cut&paste typos.
165
            self._update_pb(pb, 'get', count, total)
907.1.50 by John Arbash Meinel
Removed encode/decode from Transport.put/get, added more exceptions that can be thrown.
166
            yield self.get(relpath)
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
167
            count += 1
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
168
1185.11.21 by John Arbash Meinel
Added implementations and tests for get_partial
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
907.1.50 by John Arbash Meinel
Removed encode/decode from Transport.put/get, added more exceptions that can be thrown.
202
    def put(self, relpath, f):
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
203
        """Copy the file-like or string object into the location.
907.1.20 by John Arbash Meinel
Removed Transport.open(), making get + put encode/decode to utf-8
204
205
        :param relpath: Location to put the contents, relative to base.
206
        :param f:       File-like or string object.
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
207
        """
208
        raise NotImplementedError
209
907.1.50 by John Arbash Meinel
Removed encode/decode from Transport.put/get, added more exceptions that can be thrown.
210
    def put_multi(self, files, pb=None):
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
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),...]
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
214
        :param pb:  An optional ProgressBar for indicating percent done.
215
        :return: The number of files copied.
216
        """
1185.11.20 by John Arbash Meinel
Added some more test cases to verify generators work.
217
        return self._iterate_over(files, self.put, pb, 'put', expand=True)
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
218
219
    def mkdir(self, relpath):
220
        """Create a directory at the given path."""
221
        raise NotImplementedError
222
907.1.47 by John Arbash Meinel
Created mkdir_multi, modified branch to use _multi forms when initializing a branch, creating a full test routine for transports
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
907.1.50 by John Arbash Meinel
Removed encode/decode from Transport.put/get, added more exceptions that can be thrown.
227
    def append(self, relpath, f):
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
228
        """Append the text in the file-like or string object to 
229
        the supplied location.
230
        """
231
        raise NotImplementedError
232
1185.11.19 by John Arbash Meinel
Testing put and append, also testing agaist file-like objects as well as strings.
233
    def append_multi(self, files, pb=None):
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
234
        """Append the text in each file-like or string object to
235
        the supplied location.
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
236
237
        :param files: A set of (path, f) entries
238
        :param pb:  An optional ProgressBar for indicating percent done.
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
239
        """
907.1.14 by John Arbash Meinel
Handling some transport functions which take only a single argument.
240
        return self._iterate_over(files, self.append, pb, 'append', expand=True)
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
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
907.1.28 by John Arbash Meinel
Added pb to function that were missing, implemented a basic double-dispatch copy_to function.
246
    def copy_multi(self, relpaths, pb=None):
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
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.
907.1.14 by John Arbash Meinel
Handling some transport functions which take only a single argument.
253
        return self._iterate_over(relpaths, self.copy, pb, 'copy', expand=True)
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
254
907.1.28 by John Arbash Meinel
Added pb to function that were missing, implemented a basic double-dispatch copy_to function.
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):
907.1.50 by John Arbash Meinel
Removed encode/decode from Transport.put/get, added more exceptions that can be thrown.
262
            other.put(path, self.get(path))
907.1.28 by John Arbash Meinel
Added pb to function that were missing, implemented a basic double-dispatch copy_to function.
263
264
        return self._iterate_over(relpaths, copy_entry, pb, 'copy_to', expand=False)
265
907.1.57 by John Arbash Meinel
Trying to get pipelined http library working + tests.
266
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
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
907.1.28 by John Arbash Meinel
Added pb to function that were missing, implemented a basic double-dispatch copy_to function.
271
    def move_multi(self, relpaths, pb=None):
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
272
        """Move a bunch of entries.
273
        
274
        :param relpaths: A list of tuples of the form [(from1, to1), (from2, to2),...]
275
        """
907.1.14 by John Arbash Meinel
Handling some transport functions which take only a single argument.
276
        return self._iterate_over(relpaths, self.move, pb, 'move', expand=True)
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
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
907.1.28 by John Arbash Meinel
Added pb to function that were missing, implemented a basic double-dispatch copy_to function.
294
    def delete_multi(self, relpaths, pb=None):
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
295
        """Queue up a bunch of deletes to be done.
296
        """
907.1.14 by John Arbash Meinel
Handling some transport functions which take only a single argument.
297
        return self._iterate_over(relpaths, self.delete, pb, 'delete', expand=False)
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
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
907.1.14 by John Arbash Meinel
Handling some transport functions which take only a single argument.
315
        count = self._iterate_over(relpaths, gather, pb, 'stat', expand=False)
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
316
        return stats
317
1400.1.1 by Robert Collins
implement a basic test for the ui branch command from http servers
318
    def listable(self):
319
        """Return True if this store supports listing."""
320
        raise NotImplementedError
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
321
322
    def list_dir(self, relpath):
323
        """Return a list of all files at the given location.
324
        WARNING: many transports do not support this, so trying avoid using
325
        it if at all possible.
326
        """
1400.1.1 by Robert Collins
implement a basic test for the ui branch command from http servers
327
        raise TransportNotPossible("This transport has not "
328
                                   "implemented list_dir.")
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
329
907.1.24 by John Arbash Meinel
Remote functionality work.
330
    def lock_read(self, relpath):
331
        """Lock the given file for shared (read) access.
332
        WARNING: many transports do not support this, so trying avoid using it
333
334
        :return: A lock object, which should contain an unlock() function.
335
        """
336
        raise NotImplementedError
337
338
    def lock_write(self, relpath):
339
        """Lock the given file for exclusive (write) access.
340
        WARNING: many transports do not support this, so trying avoid using it
341
342
        :return: A lock object, which should contain an unlock() function.
343
        """
344
        raise NotImplementedError
345
346
1393.2.4 by John Arbash Meinel
All tests pass.
347
def get_transport(base):
907.1.45 by John Arbash Meinel
Switch to registering protocol handlers, rather than just updating a dictionary.
348
    global _protocol_handlers
907.1.13 by John Arbash Meinel
Fixed bzr root.
349
    if base is None:
350
        base = '.'
907.1.45 by John Arbash Meinel
Switch to registering protocol handlers, rather than just updating a dictionary.
351
    for proto, klass in _protocol_handlers.iteritems():
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
352
        if proto is not None and base.startswith(proto):
353
            return klass(base)
354
    # The default handler is the filesystem handler
355
    # which has a lookup of None
907.1.45 by John Arbash Meinel
Switch to registering protocol handlers, rather than just updating a dictionary.
356
    return _protocol_handlers[None](base)
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
357
358
# Local transport should always be initialized
1185.11.1 by John Arbash Meinel
(broken) Transport work is merged in. Tests do not pass yet.
359
import bzrlib.transport.local