~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/store.py

  • Committer: Martin Pool
  • Date: 2005-09-22 12:11:16 UTC
  • Revision ID: mbp@sourcefrog.net-20050922121116-8b8c80c401a0a4fa
- upgrade fixes regarding what files we expect to be present or not

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#! /usr/bin/env python
2
 
# -*- coding: UTF-8 -*-
 
1
# Copyright (C) 2005 by Canonical Development Ltd
3
2
 
4
3
# This program is free software; you can redistribute it and/or modify
5
4
# it under the terms of the GNU General Public License as published by
15
14
# along with this program; if not, write to the Free Software
16
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
16
 
18
 
"""Stores are the main data-storage mechanism for Bazaar-NG.
 
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.
19
22
 
20
23
A store is a simple write-once container indexed by a universally
21
 
unique ID, which is typically the SHA-1 of the content."""
22
 
 
23
 
__copyright__ = "Copyright (C) 2005 Canonical Ltd."
24
 
__author__ = "Martin Pool <mbp@canonical.com>"
 
24
unique ID.
 
25
"""
25
26
 
26
27
import os, tempfile, types, osutils, gzip, errno
27
28
from stat import ST_SIZE
28
29
from StringIO import StringIO
29
 
from trace import mutter
 
30
from bzrlib.errors import BzrError
 
31
from bzrlib.trace import mutter
 
32
import bzrlib.ui
30
33
 
31
34
######################################################################
32
35
# stores
35
38
    pass
36
39
 
37
40
 
38
 
class ImmutableStore:
 
41
class ImmutableStore(object):
39
42
    """Store that holds files indexed by unique names.
40
43
 
41
44
    Files can be added, but not modified once they are in.  Typically
58
61
    >>> st['123123'].read()
59
62
    'goodbye'
60
63
 
61
 
    :todo: Atomic add by writing to a temporary file and renaming.
62
 
 
63
 
    :todo: Perhaps automatically transform to/from XML in a method?
64
 
           Would just need to tell the constructor what class to
65
 
           use...
66
 
 
67
 
    :todo: Even within a simple disk store like this, we could
68
 
           gzip the files.  But since many are less than one disk
69
 
           block, that might not help a lot.
70
 
 
 
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.
71
69
    """
72
70
 
73
71
    def __init__(self, basedir):
74
 
        """ImmutableStore constructor."""
75
72
        self._basedir = basedir
76
73
 
77
 
    def _path(self, id):
78
 
        return os.path.join(self._basedir, id)
 
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)
79
80
 
80
81
    def __repr__(self):
81
82
        return "%s(%r)" % (self.__class__.__name__, self._basedir)
83
84
    def add(self, f, fileid, compressed=True):
84
85
        """Add contents of a file into the store.
85
86
 
86
 
        :param f: An open file, or file-like object."""
87
 
        # FIXME: Only works on smallish files
88
 
        # TODO: Can be optimized by copying at the same time as
89
 
        # computing the sum.
 
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
        
90
92
        mutter("add store entry %r" % (fileid))
91
93
        if isinstance(f, types.StringTypes):
92
94
            content = f
93
95
        else:
94
96
            content = f.read()
95
 
 
 
97
            
96
98
        p = self._path(fileid)
97
99
        if os.access(p, os.F_OK) or os.access(p + '.gz', os.F_OK):
98
 
            bailout("store %r already contains id %r" % (self._basedir, fileid))
 
100
            raise BzrError("store %r already contains id %r" % (self._basedir, fileid))
99
101
 
 
102
        fn = p
100
103
        if compressed:
101
 
            f = gzip.GzipFile(p + '.gz', 'wb')
102
 
            os.chmod(p + '.gz', 0444)
103
 
        else:
104
 
            f = file(p, 'wb')
105
 
            os.chmod(p, 0444)
106
 
            
107
 
        f.write(content)
108
 
        f.close()
109
 
 
 
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
    
110
183
 
111
184
    def __contains__(self, fileid):
112
185
        """"""
114
187
        return (os.access(p, os.R_OK)
115
188
                or os.access(p + '.gz', os.R_OK))
116
189
 
117
 
    # TODO: Guard against the same thing being stored twice, compressed and uncompresse
 
190
    # TODO: Guard against the same thing being stored twice,
 
191
    # compressed and uncompressed
118
192
 
119
193
    def __iter__(self):
120
194
        for f in os.listdir(self._basedir):
127
201
    def __len__(self):
128
202
        return len(os.listdir(self._basedir))
129
203
 
 
204
 
130
205
    def __getitem__(self, fileid):
131
206
        """Returns a file reading from a particular entry."""
132
207
        p = self._path(fileid)
133
208
        try:
134
209
            return gzip.GzipFile(p + '.gz', 'rb')
135
210
        except IOError, e:
136
 
            if e.errno == errno.ENOENT:
137
 
                return file(p, 'rb')
138
 
            else:
139
 
                raise 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
 
140
222
 
141
223
    def total_size(self):
142
224
        """Return (count, bytes)
162
244
    """Self-destructing test subclass of ImmutableStore.
163
245
 
164
246
    The Store only exists for the lifetime of the Python object.
165
 
    Obviously you should not put anything precious in it.
 
247
 Obviously you should not put anything precious in it.
166
248
    """
167
249
    def __init__(self):
168
250
        ImmutableStore.__init__(self, tempfile.mkdtemp())