~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
1185.16.17 by Martin Pool
- avoid warning about log not being registered during startup
33
    # trace messages commented out because they're typically 
34
    # run during import before trace is set up
907.1.45 by John Arbash Meinel
Switch to registering protocol handlers, rather than just updating a dictionary.
35
    if _protocol_handlers.has_key(prefix):
36
        if override:
1185.16.17 by Martin Pool
- avoid warning about log not being registered during startup
37
            ## mutter('overriding transport: %s => %s' % (prefix, klass.__name__))
907.1.45 by John Arbash Meinel
Switch to registering protocol handlers, rather than just updating a dictionary.
38
            _protocol_handlers[prefix] = klass
39
    else:
1185.16.17 by Martin Pool
- avoid warning about log not being registered during startup
40
        ## mutter('registering transport: %s => %s' % (prefix, klass.__name__))
907.1.45 by John Arbash Meinel
Switch to registering protocol handlers, rather than just updating a dictionary.
41
        _protocol_handlers[prefix] = klass
42
1442.1.24 by Robert Collins
Pull up _check_id and _relpath from Text and CompressedText stores into TransportStore
43
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
44
class Transport(object):
45
    """This class encapsulates methods for retrieving or putting a file
46
    from/to a storage location.
47
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.
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
52
    In general implementations should support having a generator or a list
53
    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.
54
    """
55
56
    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.
57
        super(Transport, self).__init__()
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
58
        self.base = base
59
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
60
    def clone(self, offset=None):
61
        """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.
62
        using a subdirectory or parent directory. This allows connections 
63
        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.
64
        """
65
        raise NotImplementedError
66
907.1.32 by John Arbash Meinel
Renaming is_remote to should_cache as it is more appropriate.
67
    def should_cache(self):
68
        """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.
69
        """
907.1.32 by John Arbash Meinel
Renaming is_remote to should_cache as it is more appropriate.
70
        return False
907.1.22 by John Arbash Meinel
Fixed some encoding issues, added is_remote function for Transport objects.
71
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
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.
76
        """
77
        if isinstance(from_file, basestring):
907.1.8 by John Arbash Meinel
Changed the format for abspath. Updated branch to use a hidden _transport
78
            to_file.write(from_file)
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
79
        else:
80
            from bzrlib.osutils import pumpfile
81
            pumpfile(from_file, to_file)
82
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
83
    def _get_total(self, multi):
84
        """Try to figure out how many entries are in multi,
85
        but if not possible, return None.
86
        """
87
        try:
88
            return len(multi)
89
        except TypeError: # We can't tell how many, because relpaths is a generator
90
            return None
91
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.
96
        """
907.1.14 by John Arbash Meinel
Handling some transport functions which take only a single argument.
97
        if pb is None:
98
            return
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
99
        if total is None:
100
            pb.update(msg, count, count+1)
101
        else:
102
            pb.update(msg, count, total)
103
907.1.14 by John Arbash Meinel
Handling some transport functions which take only a single argument.
104
    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.
105
        """Iterate over all entries in multi, passing them to func,
106
        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.
107
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.
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
111
        """
112
        total = self._get_total(multi)
113
        count = 0
114
        for entry in multi:
115
            self._update_pb(pb, msg, count, total)
907.1.14 by John Arbash Meinel
Handling some transport functions which take only a single argument.
116
            if expand:
117
                func(*entry)
118
            else:
119
                func(entry)
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
120
            count += 1
121
        return count
122
907.1.34 by John Arbash Meinel
Fixing append(), cleaning up function locations.
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
1442.1.44 by Robert Collins
Many transport related tweaks:
126
127
        XXX: Robert Collins 20051016 - is this really needed in the public
128
             interface ?
907.1.34 by John Arbash Meinel
Fixing append(), cleaning up function locations.
129
        """
130
        raise NotImplementedError
131
132
    def relpath(self, abspath):
133
        """Return the local path portion from a given absolute path.
1442.1.44 by Robert Collins
Many transport related tweaks:
134
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 
138
        resolved.
907.1.34 by John Arbash Meinel
Fixing append(), cleaning up function locations.
139
        """
1442.1.44 by Robert Collins
Many transport related tweaks:
140
        if not abspath.startswith(self.base):
141
            raise NonRelativePath('path %r is not under base URL %r'
142
                           % (abspath, self.base))
143
        pl = len(self.base)
144
        return abspath[pl:].lstrip('/')
145
907.1.34 by John Arbash Meinel
Fixing append(), cleaning up function locations.
146
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
147
    def has(self, relpath):
1442.1.44 by Robert Collins
Many transport related tweaks:
148
        """Does the file relpath exist?
149
        
150
        Note that some transports MAY allow querying on directories, but this
151
        is not part of the protocol.
152
        """
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
153
        raise NotImplementedError
154
907.1.36 by John Arbash Meinel
Moving the multi-get functionality higher up into the Branch class.
155
    def has_multi(self, relpaths, pb=None):
907.1.34 by John Arbash Meinel
Fixing append(), cleaning up function locations.
156
        """Return True/False for each entry in relpaths"""
157
        total = self._get_total(relpaths)
158
        count = 0
159
        for relpath in relpaths:
907.1.36 by John Arbash Meinel
Moving the multi-get functionality higher up into the Branch class.
160
            self._update_pb(pb, 'has', count, total)
907.1.34 by John Arbash Meinel
Fixing append(), cleaning up function locations.
161
            yield self.has(relpath)
162
            count += 1
163
1442.1.44 by Robert Collins
Many transport related tweaks:
164
    def iter_files_recursive(self):
165
        """Iter the relative paths of files in the transports sub-tree.
166
        
167
        As with other listing functions, only some transports implement this,.
168
        you may check via is_listable to determine if it will.
169
        """
170
        raise NotImplementedError
171
907.1.50 by John Arbash Meinel
Removed encode/decode from Transport.put/get, added more exceptions that can be thrown.
172
    def get(self, relpath):
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
173
        """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
174
175
        :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.
176
        """
177
        raise NotImplementedError
178
907.1.50 by John Arbash Meinel
Removed encode/decode from Transport.put/get, added more exceptions that can be thrown.
179
    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.
180
        """Get a list of file-like objects, one for each entry in relpaths.
181
182
        :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.
183
        :param pb:  An optional ProgressBar for indicating percent done.
184
        :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.
185
        """
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
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
907.1.16 by John Arbash Meinel
Fixing a few cut&paste typos.
189
        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.
190
        count = 0
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
191
        for relpath in relpaths:
907.1.16 by John Arbash Meinel
Fixing a few cut&paste typos.
192
            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.
193
            yield self.get(relpath)
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
194
            count += 1
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
195
907.1.50 by John Arbash Meinel
Removed encode/decode from Transport.put/get, added more exceptions that can be thrown.
196
    def put(self, relpath, f):
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
197
        """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
198
199
        :param relpath: Location to put the contents, relative to base.
200
        :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.
201
        """
202
        raise NotImplementedError
203
907.1.50 by John Arbash Meinel
Removed encode/decode from Transport.put/get, added more exceptions that can be thrown.
204
    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.
205
        """Put a set of files or strings into the location.
206
207
        :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.
208
        :param pb:  An optional ProgressBar for indicating percent done.
209
        :return: The number of files copied.
210
        """
1185.11.20 by John Arbash Meinel
Added some more test cases to verify generators work.
211
        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.
212
213
    def mkdir(self, relpath):
214
        """Create a directory at the given path."""
215
        raise NotImplementedError
216
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
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)
220
907.1.50 by John Arbash Meinel
Removed encode/decode from Transport.put/get, added more exceptions that can be thrown.
221
    def append(self, relpath, f):
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
222
        """Append the text in the file-like or string object to 
223
        the supplied location.
224
        """
225
        raise NotImplementedError
226
1185.11.19 by John Arbash Meinel
Testing put and append, also testing agaist file-like objects as well as strings.
227
    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.
228
        """Append the text in each file-like or string object to
229
        the supplied location.
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
230
231
        :param files: A set of (path, f) entries
232
        :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.
233
        """
907.1.14 by John Arbash Meinel
Handling some transport functions which take only a single argument.
234
        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.
235
236
    def copy(self, rel_from, rel_to):
237
        """Copy the item at rel_from to the location at rel_to"""
238
        raise NotImplementedError
239
907.1.28 by John Arbash Meinel
Added pb to function that were missing, implemented a basic double-dispatch copy_to function.
240
    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.
241
        """Copy a bunch of entries.
242
        
243
        :param relpaths: A list of tuples of the form [(from, to), (from, to),...]
244
        """
245
        # This is the non-pipelined implementation, so that
246
        # implementors don't have to implement everything.
907.1.14 by John Arbash Meinel
Handling some transport functions which take only a single argument.
247
        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.
248
907.1.28 by John Arbash Meinel
Added pb to function that were missing, implemented a basic double-dispatch copy_to function.
249
    def copy_to(self, relpaths, other, pb=None):
250
        """Copy a set of entries from self into another Transport.
251
252
        :param relpaths: A list/generator of entries to be copied.
253
        """
254
        # The dummy implementation just does a simple get + put
255
        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.
256
            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.
257
258
        return self._iterate_over(relpaths, copy_entry, pb, 'copy_to', expand=False)
259
907.1.57 by John Arbash Meinel
Trying to get pipelined http library working + tests.
260
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
261
    def move(self, rel_from, rel_to):
262
        """Move the item at rel_from to the location at rel_to"""
263
        raise NotImplementedError
264
907.1.28 by John Arbash Meinel
Added pb to function that were missing, implemented a basic double-dispatch copy_to function.
265
    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.
266
        """Move a bunch of entries.
267
        
268
        :param relpaths: A list of tuples of the form [(from1, to1), (from2, to2),...]
269
        """
907.1.14 by John Arbash Meinel
Handling some transport functions which take only a single argument.
270
        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.
271
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.
276
277
        :param relpaths: A list of relative paths [from1, from2, from3, ...]
278
        :param rel_to: A directory where each entry should be placed.
279
        """
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
283
284
    def delete(self, relpath):
285
        """Delete the item at relpath"""
286
        raise NotImplementedError
287
907.1.28 by John Arbash Meinel
Added pb to function that were missing, implemented a basic double-dispatch copy_to function.
288
    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.
289
        """Queue up a bunch of deletes to be done.
290
        """
907.1.14 by John Arbash Meinel
Handling some transport functions which take only a single argument.
291
        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.
292
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
296
        sparingly.
1442.1.44 by Robert Collins
Many transport related tweaks:
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 
301
        transports.
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
302
        """
303
        raise NotImplementedError
304
305
    def stat_multi(self, relpaths, pb=None):
306
        """Stat multiple files and return the information.
307
        """
308
        #TODO:  Is it worth making this a generator instead of a
309
        #       returning a list?
310
        stats = []
311
        def gather(path):
312
            stats.append(self.stat(path))
313
907.1.14 by John Arbash Meinel
Handling some transport functions which take only a single argument.
314
        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.
315
        return stats
316
1400.1.1 by Robert Collins
implement a basic test for the ui branch command from http servers
317
    def listable(self):
318
        """Return True if this store supports listing."""
319
        raise NotImplementedError
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
320
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.
325
        """
1400.1.1 by Robert Collins
implement a basic test for the ui branch command from http servers
326
        raise TransportNotPossible("This transport has not "
327
                                   "implemented list_dir.")
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
328
907.1.24 by John Arbash Meinel
Remote functionality work.
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
332
333
        :return: A lock object, which should contain an unlock() function.
334
        """
335
        raise NotImplementedError
336
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
340
341
        :return: A lock object, which should contain an unlock() function.
342
        """
343
        raise NotImplementedError
344
345
1393.2.4 by John Arbash Meinel
All tests pass.
346
def get_transport(base):
907.1.45 by John Arbash Meinel
Switch to registering protocol handlers, rather than just updating a dictionary.
347
    global _protocol_handlers
907.1.13 by John Arbash Meinel
Fixed bzr root.
348
    if base is None:
349
        base = '.'
907.1.45 by John Arbash Meinel
Switch to registering protocol handlers, rather than just updating a dictionary.
350
    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.
351
        if proto is not None and base.startswith(proto):
352
            return klass(base)
353
    # The default handler is the filesystem handler
354
    # which has a lookup of None
907.1.45 by John Arbash Meinel
Switch to registering protocol handlers, rather than just updating a dictionary.
355
    return _protocol_handlers[None](base)
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
356
357
# Local transport should always be initialized
1185.11.1 by John Arbash Meinel
(broken) Transport work is merged in. Tests do not pass yet.
358
import bzrlib.transport.local