/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/store/__init__.py

merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
# TODO: Could remember a bias towards whether a particular store is typically
18
18
# compressed or not.
25
25
"""
26
26
 
27
27
import os
28
 
from cStringIO import StringIO
29
 
import urllib
30
 
from zlib import adler32
31
28
 
32
 
import bzrlib
33
29
from bzrlib import (
34
30
    errors,
35
 
    osutils,
36
 
    symbol_versioning,
37
 
    urlutils,
38
 
    )
39
 
from bzrlib.errors import BzrError, UnlistableStore, TransportNotPossible
40
 
from bzrlib.symbol_versioning import (
41
 
    deprecated_function,
42
 
    zero_eight,
43
 
    zero_eleven,
44
 
    )
 
31
    versionedfile,
 
32
    )
 
33
from bzrlib.errors import BzrError, UnlistableStore
45
34
from bzrlib.trace import mutter
46
 
from bzrlib.transport import Transport
47
 
from bzrlib.transport.local import LocalTransport
48
35
 
49
36
######################################################################
50
37
# stores
55
42
 
56
43
class Store(object):
57
44
    """This class represents the abstract storage layout for saving information.
58
 
    
 
45
 
59
46
    Files can be added, but not modified once they are in.  Typically
60
47
    the hash is used as the name, or something else known to be unique,
61
48
    such as a UUID.
66
53
 
67
54
    def get(self, fileid, suffix=None):
68
55
        """Returns a file reading from a particular entry.
69
 
        
 
56
 
70
57
        If suffix is present, retrieve the named suffix for fileid.
71
58
        """
72
59
        raise NotImplementedError
75
62
        """DEPRECATED. Please use .get(fileid) instead."""
76
63
        raise NotImplementedError
77
64
 
78
 
    #def __contains__(self, fileid):
79
 
    #    """Deprecated, please use has_id"""
80
 
    #    raise NotImplementedError
81
 
 
82
65
    def __iter__(self):
83
66
        raise NotImplementedError
84
67
 
88
71
 
89
72
    def has_id(self, fileid, suffix=None):
90
73
        """Return True or false for the presence of fileid in the store.
91
 
        
92
 
        suffix, if present, is a per file suffix, i.e. for digital signature 
 
74
 
 
75
        suffix, if present, is a per file suffix, i.e. for digital signature
93
76
        data."""
94
77
        raise NotImplementedError
95
78
 
129
112
            pb.update('preparing to copy')
130
113
        failed = set()
131
114
        count = 0
132
 
        ids = [osutils.safe_file_id(i) for i in ids] # get the list for showing a length.
133
115
        for fileid in ids:
134
116
            count += 1
135
117
            if self.has_id(fileid):
148
130
                    failed.add(fileid)
149
131
                else:
150
132
                    raise
151
 
        assert count == len(ids)
152
133
        if pb:
153
134
            pb.clear()
154
135
        return count, failed
155
136
 
156
137
    def _copy_one(self, fileid, suffix, other, pb):
157
138
        """Most generic copy-one object routine.
158
 
        
 
139
 
159
140
        Subclasses can override this to provide an optimised
160
141
        copy between their own instances. Such overriden routines
161
 
        should call this if they have no optimised facility for a 
 
142
        should call this if they have no optimised facility for a
162
143
        specific 'other'.
163
144
        """
164
145
        mutter('Store._copy_one: %r', fileid)
174
155
 
175
156
        f -- A file-like object
176
157
        """
177
 
        fileid = osutils.safe_file_id(fileid)
178
158
        mutter("add store entry %r", fileid)
179
 
        if isinstance(f, str):
180
 
            symbol_versioning.warn(zero_eleven % 'Passing a string to Store.add',
181
 
                DeprecationWarning, stacklevel=2)
182
 
            f = StringIO(f)
183
 
        
184
159
        names = self._id_to_names(fileid, suffix)
185
160
        if self._transport.has_any(names):
186
 
            raise BzrError("store %r already contains id %r" 
 
161
            raise BzrError("store %r already contains id %r"
187
162
                           % (self._transport.base, fileid))
188
163
 
189
164
        # Most of the time, just adding the file will work
198
173
        raise NotImplementedError('children need to implement this function.')
199
174
 
200
175
    def _check_fileid(self, fileid):
201
 
        if not isinstance(fileid, basestring):
202
 
            raise TypeError('Fileids should be a string type: %s %r' % (type(fileid), fileid))
 
176
        if type(fileid) != str:
 
177
            raise TypeError('Fileids should be bytestrings: %s %r' % (
 
178
                type(fileid), fileid))
203
179
        if '\\' in fileid or '/' in fileid:
204
180
            raise ValueError("invalid store id %r" % fileid)
205
181
 
219
195
 
220
196
    def has_id(self, fileid, suffix=None):
221
197
        """See Store.has_id."""
222
 
        fileid = osutils.safe_file_id(fileid)
223
198
        return self._transport.has_any(self._id_to_names(fileid, suffix))
224
199
 
225
200
    def _get_name(self, fileid, suffix=None):
226
201
        """A special check, which returns the name of an existing file.
227
 
        
 
202
 
228
203
        This is similar in spirit to 'has_id', but it is designed
229
204
        to return information about which file the store has.
230
205
        """
236
211
    def _get(self, filename):
237
212
        """Return an vanilla file stream for clients to read from.
238
213
 
239
 
        This is the body of a template method on 'get', and should be 
 
214
        This is the body of a template method on 'get', and should be
240
215
        implemented by subclasses.
241
216
        """
242
217
        raise NotImplementedError
243
218
 
244
219
    def get(self, fileid, suffix=None):
245
220
        """See Store.get()."""
246
 
        fileid = osutils.safe_file_id(fileid)
247
221
        names = self._id_to_names(fileid, suffix)
248
222
        for name in names:
249
223
            try:
255
229
    def __init__(self, a_transport, prefixed=False, compressed=False,
256
230
                 dir_mode=None, file_mode=None,
257
231
                 escaped=False):
258
 
        assert isinstance(a_transport, Transport)
259
232
        super(TransportStore, self).__init__()
260
233
        self._transport = a_transport
261
234
        self._prefixed = prefixed
268
241
        # will just use the filesystem defaults
269
242
        self._dir_mode = dir_mode
270
243
        self._file_mode = file_mode
271
 
 
272
 
    def _unescape(self, file_id):
273
 
        """If filename escaping is enabled for this store, unescape and return the filename."""
274
 
        if self._escaped:
275
 
            return urllib.unquote(file_id)
 
244
        # Create a key mapper to use
 
245
        if escaped and prefixed:
 
246
            self._mapper = versionedfile.HashEscapedPrefixMapper()
 
247
        elif not escaped and prefixed:
 
248
            self._mapper = versionedfile.HashPrefixMapper()
 
249
        elif self._escaped:
 
250
            raise ValueError(
 
251
                "%r: escaped unprefixed stores are not permitted."
 
252
                % (self,))
276
253
        else:
277
 
            return file_id
 
254
            self._mapper = versionedfile.PrefixMapper()
278
255
 
279
256
    def _iter_files_recursive(self):
280
257
        """Iterate through the files in the transport."""
281
258
        for quoted_relpath in self._transport.iter_files_recursive():
282
 
            # transport iterator always returns quoted paths, regardless of
283
 
            # escaping
284
 
            yield urllib.unquote(quoted_relpath)
 
259
            yield quoted_relpath
285
260
 
286
261
    def __iter__(self):
287
262
        for relpath in self._iter_files_recursive():
295
270
                    if name.endswith('.' + suffix):
296
271
                        skip = True
297
272
            if not skip:
298
 
                yield self._unescape(name)
 
273
                yield self._mapper.unmap(name)[0]
299
274
 
300
275
    def __len__(self):
301
276
        return len(list(self.__iter__()))
309
284
                self._check_fileid(suffix)
310
285
        else:
311
286
            suffixes = []
312
 
        fileid = self._escape_file_id(fileid)
313
 
        if self._prefixed:
314
 
            # hash_prefix adds the '/' separator
315
 
            prefix = self.hash_prefix(fileid, escaped=True)
316
 
        else:
317
 
            prefix = ''
318
 
        path = prefix + fileid
319
 
        full_path = u'.'.join([path] + suffixes)
320
 
        return urlutils.escape(full_path)
321
 
 
322
 
    def _escape_file_id(self, file_id):
323
 
        """Turn a file id into a filesystem safe string.
324
 
 
325
 
        This is similar to a plain urllib.quote, except
326
 
        it uses specific safe characters, so that it doesn't
327
 
        have to translate a lot of valid file ids.
328
 
        """
329
 
        if not self._escaped:
330
 
            return file_id
331
 
        if isinstance(file_id, unicode):
332
 
            file_id = file_id.encode('utf-8')
333
 
        # @ does not get escaped. This is because it is a valid
334
 
        # filesystem character we use all the time, and it looks
335
 
        # a lot better than seeing %40 all the time.
336
 
        safe = "abcdefghijklmnopqrstuvwxyz0123456789-_@,."
337
 
        r = [((c in safe) and c or ('%%%02x' % ord(c)))
338
 
             for c in file_id]
339
 
        return ''.join(r)
340
 
 
341
 
    def hash_prefix(self, fileid, escaped=False):
342
 
        # fileid should be unescaped
343
 
        if not escaped and self._escaped:
344
 
            fileid = self._escape_file_id(fileid)
345
 
        return "%02x/" % (adler32(fileid) & 0xff)
 
287
        path = self._mapper.map((fileid,))
 
288
        full_path = '.'.join([path] + suffixes)
 
289
        return full_path
346
290
 
347
291
    def __repr__(self):
348
292
        if self._transport is None:
373
317
        for relpath in self._transport.iter_files_recursive():
374
318
            count += 1
375
319
            total += self._transport.stat(relpath).st_size
376
 
                
 
320
 
377
321
        return count, total
378
 
 
379
 
 
380
 
@deprecated_function(zero_eight)
381
 
def copy_all(store_from, store_to, pb=None):
382
 
    """Copy all ids from one store to another."""
383
 
    store_to.copy_all_ids(store_from, pb)