/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: Martin Pool
  • Date: 2005-09-23 04:13:53 UTC
  • Revision ID: mbp@sourcefrog.net-20050923041353-6242de1098970ffe
- remove dud import

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