/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

  • Committer: Aaron Bentley
  • Date: 2008-10-16 20:29:36 UTC
  • mfrom: (3779 +trunk)
  • mto: (0.14.24 prepare-shelf)
  • mto: This revision was merged to the branch mainline in revision 3820.
  • Revision ID: aaron@aaronbentley.com-20081016202936-q8budtnystke56fn
Merge with bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 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
 
 
17
# TODO: Could remember a bias towards whether a particular store is typically
 
18
# compressed or not.
 
19
 
 
20
"""
 
21
Stores are the main data-storage mechanism for Bazaar.
 
22
 
 
23
A store is a simple write-once container indexed by a universally
 
24
unique ID.
 
25
"""
 
26
 
 
27
import os
 
28
 
 
29
from bzrlib import (
 
30
    errors,
 
31
    versionedfile,
 
32
    )
 
33
from bzrlib.errors import BzrError, UnlistableStore
 
34
from bzrlib.trace import mutter
 
35
 
 
36
######################################################################
 
37
# stores
 
38
 
 
39
class StoreError(Exception):
 
40
    pass
 
41
 
 
42
 
 
43
class Store(object):
 
44
    """This class represents the abstract storage layout for saving information.
 
45
    
 
46
    Files can be added, but not modified once they are in.  Typically
 
47
    the hash is used as the name, or something else known to be unique,
 
48
    such as a UUID.
 
49
    """
 
50
 
 
51
    def __len__(self):
 
52
        raise NotImplementedError('Children should define their length')
 
53
 
 
54
    def get(self, fileid, suffix=None):
 
55
        """Returns a file reading from a particular entry.
 
56
        
 
57
        If suffix is present, retrieve the named suffix for fileid.
 
58
        """
 
59
        raise NotImplementedError
 
60
 
 
61
    def __getitem__(self, fileid):
 
62
        """DEPRECATED. Please use .get(fileid) instead."""
 
63
        raise NotImplementedError
 
64
 
 
65
    def __iter__(self):
 
66
        raise NotImplementedError
 
67
 
 
68
    def add(self, f, fileid):
 
69
        """Add a file object f to the store accessible from the given fileid"""
 
70
        raise NotImplementedError('Children of Store must define their method of adding entries.')
 
71
 
 
72
    def has_id(self, fileid, suffix=None):
 
73
        """Return True or false for the presence of fileid in the store.
 
74
        
 
75
        suffix, if present, is a per file suffix, i.e. for digital signature 
 
76
        data."""
 
77
        raise NotImplementedError
 
78
 
 
79
    def listable(self):
 
80
        """Return True if this store is able to be listed."""
 
81
        return (getattr(self, "__iter__", None) is not None)
 
82
 
 
83
    def copy_all_ids(self, store_from, pb=None):
 
84
        """Copy all the file ids from store_from into self."""
 
85
        if not store_from.listable():
 
86
            raise UnlistableStore(store_from)
 
87
        ids = []
 
88
        for count, file_id in enumerate(store_from):
 
89
            if pb:
 
90
                pb.update('listing files', count, count)
 
91
            ids.append(file_id)
 
92
        if pb:
 
93
            pb.clear()
 
94
        mutter('copy_all ids: %r', ids)
 
95
        self.copy_multi(store_from, ids, pb=pb)
 
96
 
 
97
    def copy_multi(self, other, ids, pb=None, permit_failure=False):
 
98
        """Copy texts for ids from other into self.
 
99
 
 
100
        If an id is present in self, it is skipped.  A count of copied
 
101
        ids is returned, which may be less than len(ids).
 
102
 
 
103
        :param other: Another Store object
 
104
        :param ids: A list of entry ids to be copied
 
105
        :param pb: A ProgressBar object, if none is given, the default will be created.
 
106
        :param permit_failure: Allow missing entries to be ignored
 
107
        :return: (n_copied, [failed]) The number of entries copied successfully,
 
108
            followed by a list of entries which could not be copied (because they
 
109
            were missing)
 
110
        """
 
111
        if pb:
 
112
            pb.update('preparing to copy')
 
113
        failed = set()
 
114
        count = 0
 
115
        for fileid in ids:
 
116
            count += 1
 
117
            if self.has_id(fileid):
 
118
                continue
 
119
            try:
 
120
                self._copy_one(fileid, None, other, pb)
 
121
                for suffix in self._suffixes:
 
122
                    try:
 
123
                        self._copy_one(fileid, suffix, other, pb)
 
124
                    except KeyError:
 
125
                        pass
 
126
                if pb:
 
127
                    pb.update('copy', count, len(ids))
 
128
            except KeyError:
 
129
                if permit_failure:
 
130
                    failed.add(fileid)
 
131
                else:
 
132
                    raise
 
133
        if pb:
 
134
            pb.clear()
 
135
        return count, failed
 
136
 
 
137
    def _copy_one(self, fileid, suffix, other, pb):
 
138
        """Most generic copy-one object routine.
 
139
        
 
140
        Subclasses can override this to provide an optimised
 
141
        copy between their own instances. Such overriden routines
 
142
        should call this if they have no optimised facility for a 
 
143
        specific 'other'.
 
144
        """
 
145
        mutter('Store._copy_one: %r', fileid)
 
146
        f = other.get(fileid, suffix)
 
147
        self.add(f, fileid, suffix)
 
148
 
 
149
 
 
150
class TransportStore(Store):
 
151
    """A TransportStore is a Store superclass for Stores that use Transports."""
 
152
 
 
153
    def add(self, f, fileid, suffix=None):
 
154
        """Add contents of a file into the store.
 
155
 
 
156
        f -- A file-like object
 
157
        """
 
158
        mutter("add store entry %r", fileid)
 
159
        names = self._id_to_names(fileid, suffix)
 
160
        if self._transport.has_any(names):
 
161
            raise BzrError("store %r already contains id %r" 
 
162
                           % (self._transport.base, fileid))
 
163
 
 
164
        # Most of the time, just adding the file will work
 
165
        # if we find a time where it fails, (because the dir
 
166
        # doesn't exist), then create the dir, and try again
 
167
        self._add(names[0], f)
 
168
 
 
169
    def _add(self, relpath, f):
 
170
        """Actually add the file to the given location.
 
171
        This should be overridden by children.
 
172
        """
 
173
        raise NotImplementedError('children need to implement this function.')
 
174
 
 
175
    def _check_fileid(self, fileid):
 
176
        if type(fileid) != str:
 
177
            raise TypeError('Fileids should be bytestrings: %s %r' % (
 
178
                type(fileid), fileid))
 
179
        if '\\' in fileid or '/' in fileid:
 
180
            raise ValueError("invalid store id %r" % fileid)
 
181
 
 
182
    def _id_to_names(self, fileid, suffix):
 
183
        """Return the names in the expected order"""
 
184
        if suffix is not None:
 
185
            fn = self._relpath(fileid, [suffix])
 
186
        else:
 
187
            fn = self._relpath(fileid)
 
188
 
 
189
        # FIXME RBC 20051128 this belongs in TextStore.
 
190
        fn_gz = fn + '.gz'
 
191
        if self._compressed:
 
192
            return fn_gz, fn
 
193
        else:
 
194
            return fn, fn_gz
 
195
 
 
196
    def has_id(self, fileid, suffix=None):
 
197
        """See Store.has_id."""
 
198
        return self._transport.has_any(self._id_to_names(fileid, suffix))
 
199
 
 
200
    def _get_name(self, fileid, suffix=None):
 
201
        """A special check, which returns the name of an existing file.
 
202
        
 
203
        This is similar in spirit to 'has_id', but it is designed
 
204
        to return information about which file the store has.
 
205
        """
 
206
        for name in self._id_to_names(fileid, suffix=suffix):
 
207
            if self._transport.has(name):
 
208
                return name
 
209
        return None
 
210
 
 
211
    def _get(self, filename):
 
212
        """Return an vanilla file stream for clients to read from.
 
213
 
 
214
        This is the body of a template method on 'get', and should be 
 
215
        implemented by subclasses.
 
216
        """
 
217
        raise NotImplementedError
 
218
 
 
219
    def get(self, fileid, suffix=None):
 
220
        """See Store.get()."""
 
221
        names = self._id_to_names(fileid, suffix)
 
222
        for name in names:
 
223
            try:
 
224
                return self._get(name)
 
225
            except errors.NoSuchFile:
 
226
                pass
 
227
        raise KeyError(fileid)
 
228
 
 
229
    def __init__(self, a_transport, prefixed=False, compressed=False,
 
230
                 dir_mode=None, file_mode=None,
 
231
                 escaped=False):
 
232
        super(TransportStore, self).__init__()
 
233
        self._transport = a_transport
 
234
        self._prefixed = prefixed
 
235
        # FIXME RBC 20051128 this belongs in TextStore.
 
236
        self._compressed = compressed
 
237
        self._suffixes = set()
 
238
        self._escaped = escaped
 
239
 
 
240
        # It is okay for these to be None, it just means they
 
241
        # will just use the filesystem defaults
 
242
        self._dir_mode = dir_mode
 
243
        self._file_mode = file_mode
 
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,))
 
253
        else:
 
254
            self._mapper = versionedfile.PrefixMapper()
 
255
 
 
256
    def _iter_files_recursive(self):
 
257
        """Iterate through the files in the transport."""
 
258
        for quoted_relpath in self._transport.iter_files_recursive():
 
259
            yield quoted_relpath
 
260
 
 
261
    def __iter__(self):
 
262
        for relpath in self._iter_files_recursive():
 
263
            # worst case is one of each suffix.
 
264
            name = os.path.basename(relpath)
 
265
            if name.endswith('.gz'):
 
266
                name = name[:-3]
 
267
            skip = False
 
268
            for count in range(len(self._suffixes)):
 
269
                for suffix in self._suffixes:
 
270
                    if name.endswith('.' + suffix):
 
271
                        skip = True
 
272
            if not skip:
 
273
                yield self._mapper.unmap(name)[0]
 
274
 
 
275
    def __len__(self):
 
276
        return len(list(self.__iter__()))
 
277
 
 
278
    def _relpath(self, fileid, suffixes=None):
 
279
        self._check_fileid(fileid)
 
280
        if suffixes:
 
281
            for suffix in suffixes:
 
282
                if not suffix in self._suffixes:
 
283
                    raise ValueError("Unregistered suffix %r" % suffix)
 
284
                self._check_fileid(suffix)
 
285
        else:
 
286
            suffixes = []
 
287
        path = self._mapper.map((fileid,))
 
288
        full_path = '.'.join([path] + suffixes)
 
289
        return full_path
 
290
 
 
291
    def __repr__(self):
 
292
        if self._transport is None:
 
293
            return "%s(None)" % (self.__class__.__name__)
 
294
        else:
 
295
            return "%s(%r)" % (self.__class__.__name__, self._transport.base)
 
296
 
 
297
    __str__ = __repr__
 
298
 
 
299
    def listable(self):
 
300
        """Return True if this store is able to be listed."""
 
301
        return self._transport.listable()
 
302
 
 
303
    def register_suffix(self, suffix):
 
304
        """Register a suffix as being expected in this store."""
 
305
        self._check_fileid(suffix)
 
306
        if suffix == 'gz':
 
307
            raise ValueError('You cannot register the "gz" suffix.')
 
308
        self._suffixes.add(suffix)
 
309
 
 
310
    def total_size(self):
 
311
        """Return (count, bytes)
 
312
 
 
313
        This is the (compressed) size stored on disk, not the size of
 
314
        the content."""
 
315
        total = 0
 
316
        count = 0
 
317
        for relpath in self._transport.iter_files_recursive():
 
318
            count += 1
 
319
            total += self._transport.stat(relpath).st_size
 
320
                
 
321
        return count, total