/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-27 07:24:40 UTC
  • mfrom: (1185.1.41)
  • Revision ID: robertc@robertcollins.net-20050927072440-1bf4d99c3e1db5b3
pair programming worx... merge integration and weave

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
 
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
        from shutil import copyfile
 
163
        count = 0
 
164
        failed = set()
 
165
        for id in to_copy:
 
166
            p = self._path(id)
 
167
            other_p = other._path(id)
 
168
            try:
 
169
                copyfile(other_p, p)
 
170
            except IOError, e:
 
171
                if e.errno == errno.ENOENT:
 
172
                    if not permit_failure:
 
173
                        copyfile(other_p+".gz", p+".gz")
 
174
                    else:
 
175
                        try:
 
176
                            copyfile(other_p+".gz", p+".gz")
 
177
                        except IOError, e:
 
178
                            if e.errno == errno.ENOENT:
 
179
                                failed.add(id)
 
180
                            else:
 
181
                                raise
 
182
                else:
 
183
                    raise
 
184
            
 
185
            count += 1
 
186
            pb.update('copy', count, len(to_copy))
 
187
        assert count == len(to_copy)
 
188
        pb.clear()
 
189
        return count, failed
 
190
    
 
191
 
 
192
    def __contains__(self, fileid):
 
193
        """"""
 
194
        p = self._path(fileid)
 
195
        return (os.access(p, os.R_OK)
 
196
                or os.access(p + '.gz', os.R_OK))
 
197
 
 
198
    # TODO: Guard against the same thing being stored twice,
 
199
    # compressed and uncompressed
 
200
 
 
201
    def __iter__(self):
 
202
        for f in os.listdir(self._basedir):
 
203
            if f[-3:] == '.gz':
 
204
                # TODO: case-insensitive?
 
205
                yield f[:-3]
 
206
            else:
 
207
                yield f
 
208
 
 
209
    def __len__(self):
 
210
        return len(os.listdir(self._basedir))
 
211
 
 
212
 
 
213
    def __getitem__(self, fileid):
 
214
        """Returns a file reading from a particular entry."""
 
215
        p = self._path(fileid)
 
216
        try:
 
217
            return gzip.GzipFile(p + '.gz', 'rb')
 
218
        except IOError, e:
 
219
            if e.errno != errno.ENOENT:
 
220
                raise
 
221
 
 
222
        try:
 
223
            return file(p, 'rb')
 
224
        except IOError, e:
 
225
            if e.errno != errno.ENOENT:
 
226
                raise
 
227
 
 
228
        raise KeyError(fileid)
 
229
 
 
230
 
 
231
    def total_size(self):
 
232
        """Return (count, bytes)
 
233
 
 
234
        This is the (compressed) size stored on disk, not the size of
 
235
        the content."""
 
236
        total = 0
 
237
        count = 0
 
238
        for fid in self:
 
239
            count += 1
 
240
            p = self._path(fid)
 
241
            try:
 
242
                total += os.stat(p)[ST_SIZE]
 
243
            except OSError:
 
244
                total += os.stat(p + '.gz')[ST_SIZE]
 
245
                
 
246
        return count, total
 
247
 
 
248
 
 
249
 
 
250
 
 
251
class ImmutableScratchStore(ImmutableStore):
 
252
    """Self-destructing test subclass of ImmutableStore.
 
253
 
 
254
    The Store only exists for the lifetime of the Python object.
 
255
 Obviously you should not put anything precious in it.
 
256
    """
 
257
    def __init__(self):
 
258
        ImmutableStore.__init__(self, tempfile.mkdtemp())
 
259
 
 
260
    def __del__(self):
 
261
        for f in os.listdir(self._basedir):
 
262
            fpath = os.path.join(self._basedir, f)
 
263
            # needed on windows, and maybe some other filesystems
 
264
            os.chmod(fpath, 0600)
 
265
            os.remove(fpath)
 
266
        os.rmdir(self._basedir)
 
267
        mutter("%r destroyed" % self)