/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.py

  • Committer: Robert Collins
  • Date: 2005-09-28 09:35:50 UTC
  • mfrom: (1185.1.47)
  • Revision ID: robertc@robertcollins.net-20050928093550-3ca194dfaffc79f1
merge from integration

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 by Canonical Development 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-NG.
 
22
 
 
23
A store is a simple write-once container indexed by a universally
 
24
unique ID.
 
25
"""
 
26
 
 
27
import errno
 
28
import gzip
 
29
import os
 
30
import tempfile
 
31
import types
 
32
from stat import ST_SIZE
 
33
from StringIO import StringIO
 
34
 
 
35
from bzrlib.errors import BzrError, UnlistableStore
 
36
from bzrlib.trace import mutter
 
37
import bzrlib.ui
 
38
import bzrlib.osutils as osutils
 
39
 
 
40
 
 
41
######################################################################
 
42
# stores
 
43
 
 
44
class StoreError(Exception):
 
45
    pass
 
46
 
 
47
 
 
48
class ImmutableStore(object):
 
49
    """Store that holds files indexed by unique names.
 
50
 
 
51
    Files can be added, but not modified once they are in.  Typically
 
52
    the hash is used as the name, or something else known to be unique,
 
53
    such as a UUID.
 
54
 
 
55
    >>> st = ImmutableScratchStore()
 
56
 
 
57
    >>> st.add(StringIO('hello'), 'aa')
 
58
    >>> 'aa' in st
 
59
    True
 
60
    >>> 'foo' in st
 
61
    False
 
62
 
 
63
    You are not allowed to add an id that is already present.
 
64
 
 
65
    Entries can be retrieved as files, which may then be read.
 
66
 
 
67
    >>> st.add(StringIO('goodbye'), '123123')
 
68
    >>> st['123123'].read()
 
69
    'goodbye'
 
70
 
 
71
    TODO: Atomic add by writing to a temporary file and renaming.
 
72
 
 
73
    In bzr 0.0.5 and earlier, files within the store were marked
 
74
    readonly on disk.  This is no longer done but existing stores need
 
75
    to be accomodated.
 
76
    """
 
77
 
 
78
    def __init__(self, basedir):
 
79
        self._basedir = basedir
 
80
 
 
81
    def _path(self, entry_id):
 
82
        if not isinstance(entry_id, basestring):
 
83
            raise TypeError(type(entry_id))
 
84
        if '\\' in entry_id or '/' in entry_id:
 
85
            raise ValueError("invalid store id %r" % entry_id)
 
86
        return os.path.join(self._basedir, entry_id)
 
87
 
 
88
    def __repr__(self):
 
89
        return "%s(%r)" % (self.__class__.__name__, self._basedir)
 
90
 
 
91
    def add(self, f, fileid, compressed=True):
 
92
        """Add contents of a file into the store.
 
93
 
 
94
        f -- An open file, or file-like object."""
 
95
        # FIXME: Only works on files that will fit in memory
 
96
        
 
97
        from bzrlib.atomicfile import AtomicFile
 
98
        
 
99
        mutter("add store entry %r" % (fileid))
 
100
        if isinstance(f, types.StringTypes):
 
101
            content = f
 
102
        else:
 
103
            content = f.read()
 
104
            
 
105
        p = self._path(fileid)
 
106
        if os.access(p, os.F_OK) or os.access(p + '.gz', os.F_OK):
 
107
            raise BzrError("store %r already contains id %r" % (self._basedir, fileid))
 
108
 
 
109
        fn = p
 
110
        if compressed:
 
111
            fn = fn + '.gz'
 
112
            
 
113
        af = AtomicFile(fn, 'wb')
 
114
        try:
 
115
            if compressed:
 
116
                gf = gzip.GzipFile(mode='wb', fileobj=af)
 
117
                gf.write(content)
 
118
                gf.close()
 
119
            else:
 
120
                af.write(content)
 
121
            af.commit()
 
122
        finally:
 
123
            af.close()
 
124
 
 
125
 
 
126
    def copy_multi(self, other, ids, permit_failure=False):
 
127
        """Copy texts for ids from other into self.
 
128
 
 
129
        If an id is present in self, it is skipped.
 
130
 
 
131
        Returns (count_copied, failed), where failed is a collection of ids
 
132
        that could not be copied.
 
133
        """
 
134
        pb = bzrlib.ui.ui_factory.progress_bar()
 
135
        
 
136
        pb.update('preparing to copy')
 
137
        to_copy = [id for id in ids if id not in self]
 
138
        if isinstance(other, ImmutableStore):
 
139
            return self.copy_multi_immutable(other, to_copy, pb, 
 
140
                                             permit_failure=permit_failure)
 
141
        count = 0
 
142
        failed = set()
 
143
        for id in to_copy:
 
144
            count += 1
 
145
            pb.update('copy', count, len(to_copy))
 
146
            if not permit_failure:
 
147
                self.add(other[id], id)
 
148
            else:
 
149
                try:
 
150
                    entry = other[id]
 
151
                except KeyError:
 
152
                    failed.add(id)
 
153
                    continue
 
154
                self.add(entry, id)
 
155
                
 
156
        if not permit_failure:
 
157
            assert count == len(to_copy)
 
158
        pb.clear()
 
159
        return count, failed
 
160
 
 
161
    def copy_multi_immutable(self, other, to_copy, pb, permit_failure=False):
 
162
        count = 0
 
163
        failed = set()
 
164
        for id in to_copy:
 
165
            p = self._path(id)
 
166
            other_p = other._path(id)
 
167
            try:
 
168
                osutils.link_or_copy(other_p, p)
 
169
            except (IOError, OSError), e:
 
170
                if e.errno == errno.ENOENT:
 
171
                    if not permit_failure:
 
172
                        osutils.link_or_copy(other_p+".gz", p+".gz")
 
173
                    else:
 
174
                        try:
 
175
                            osutils.link_or_copy(other_p+".gz", p+".gz")
 
176
                        except IOError, e:
 
177
                            if e.errno == errno.ENOENT:
 
178
                                failed.add(id)
 
179
                            else:
 
180
                                raise
 
181
                else:
 
182
                    raise
 
183
            
 
184
            count += 1
 
185
            pb.update('copy', count, len(to_copy))
 
186
        assert count == len(to_copy)
 
187
        pb.clear()
 
188
        return count, failed
 
189
    
 
190
 
 
191
    def __contains__(self, fileid):
 
192
        """"""
 
193
        p = self._path(fileid)
 
194
        return (os.access(p, os.R_OK)
 
195
                or os.access(p + '.gz', os.R_OK))
 
196
 
 
197
    # TODO: Guard against the same thing being stored twice,
 
198
    # compressed and uncompressed
 
199
 
 
200
    def __iter__(self):
 
201
        for f in os.listdir(self._basedir):
 
202
            if f[-3:] == '.gz':
 
203
                # TODO: case-insensitive?
 
204
                yield f[:-3]
 
205
            else:
 
206
                yield f
 
207
 
 
208
    def __len__(self):
 
209
        return len(os.listdir(self._basedir))
 
210
 
 
211
 
 
212
    def __getitem__(self, fileid):
 
213
        """Returns a file reading from a particular entry."""
 
214
        p = self._path(fileid)
 
215
        try:
 
216
            return gzip.GzipFile(p + '.gz', 'rb')
 
217
        except IOError, e:
 
218
            if e.errno != errno.ENOENT:
 
219
                raise
 
220
 
 
221
        try:
 
222
            return file(p, 'rb')
 
223
        except IOError, e:
 
224
            if e.errno != errno.ENOENT:
 
225
                raise
 
226
 
 
227
        raise KeyError(fileid)
 
228
 
 
229
 
 
230
    def total_size(self):
 
231
        """Return (count, bytes)
 
232
 
 
233
        This is the (compressed) size stored on disk, not the size of
 
234
        the content."""
 
235
        total = 0
 
236
        count = 0
 
237
        for fid in self:
 
238
            count += 1
 
239
            p = self._path(fid)
 
240
            try:
 
241
                total += os.stat(p)[ST_SIZE]
 
242
            except OSError:
 
243
                total += os.stat(p + '.gz')[ST_SIZE]
 
244
                
 
245
        return count, total
 
246
 
 
247
 
 
248
 
 
249
 
 
250
class ImmutableScratchStore(ImmutableStore):
 
251
    """Self-destructing test subclass of ImmutableStore.
 
252
 
 
253
    The Store only exists for the lifetime of the Python object.
 
254
 Obviously you should not put anything precious in it.
 
255
    """
 
256
    def __init__(self):
 
257
        ImmutableStore.__init__(self, tempfile.mkdtemp())
 
258
 
 
259
    def __del__(self):
 
260
        for f in os.listdir(self._basedir):
 
261
            fpath = os.path.join(self._basedir, f)
 
262
            # needed on windows, and maybe some other filesystems
 
263
            os.chmod(fpath, 0600)
 
264
            os.remove(fpath)
 
265
        os.rmdir(self._basedir)
 
266
        mutter("%r destroyed" % self)
 
267
 
 
268
def copy_all(store_from, store_to):
 
269
    """Copy all ids from one store to another."""
 
270
    if not hasattr(store_from, "__iter__"):
 
271
        raise UnlistableStore(store_from)
 
272
    ids = [f for f in store_from]
 
273
    store_to.copy_multi(store_from, ids)