~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/store.py

  • Committer: Martin Pool
  • Date: 2005-09-29 09:07:09 UTC
  • mto: (1185.12.2) (1393.1.12)
  • mto: This revision was merged to the branch mainline in revision 1396.
  • Revision ID: mbp@sourcefrog.net-20050929090709-36eebfb3ba67d0a5
- weave tool now finds bzrlib if it's not on the default path

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>"
25
 
 
26
 
import os, tempfile, types, osutils, gzip, errno
 
24
unique ID.
 
25
"""
 
26
 
 
27
import errno
 
28
import gzip
 
29
import os
 
30
import tempfile
 
31
import types
27
32
from stat import ST_SIZE
28
33
from StringIO import StringIO
29
 
from trace import mutter
 
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
 
30
40
 
31
41
######################################################################
32
42
# stores
35
45
    pass
36
46
 
37
47
 
38
 
class ImmutableStore:
 
48
class ImmutableStore(object):
39
49
    """Store that holds files indexed by unique names.
40
50
 
41
51
    Files can be added, but not modified once they are in.  Typically
58
68
    >>> st['123123'].read()
59
69
    'goodbye'
60
70
 
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
 
 
 
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.
71
76
    """
72
77
 
73
78
    def __init__(self, basedir):
74
 
        """ImmutableStore constructor."""
75
79
        self._basedir = basedir
76
80
 
77
 
    def _path(self, id):
78
 
        return os.path.join(self._basedir, id)
 
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)
79
87
 
80
88
    def __repr__(self):
81
89
        return "%s(%r)" % (self.__class__.__name__, self._basedir)
83
91
    def add(self, f, fileid, compressed=True):
84
92
        """Add contents of a file into the store.
85
93
 
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.
 
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
        
90
99
        mutter("add store entry %r" % (fileid))
91
100
        if isinstance(f, types.StringTypes):
92
101
            content = f
93
102
        else:
94
103
            content = f.read()
95
 
 
 
104
            
96
105
        p = self._path(fileid)
97
106
        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))
 
107
            raise BzrError("store %r already contains id %r" % (self._basedir, fileid))
99
108
 
 
109
        fn = p
100
110
        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
 
 
 
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
    
110
190
 
111
191
    def __contains__(self, fileid):
112
192
        """"""
114
194
        return (os.access(p, os.R_OK)
115
195
                or os.access(p + '.gz', os.R_OK))
116
196
 
117
 
    # TODO: Guard against the same thing being stored twice, compressed and uncompresse
 
197
    # TODO: Guard against the same thing being stored twice,
 
198
    # compressed and uncompressed
118
199
 
119
200
    def __iter__(self):
120
201
        for f in os.listdir(self._basedir):
127
208
    def __len__(self):
128
209
        return len(os.listdir(self._basedir))
129
210
 
 
211
 
130
212
    def __getitem__(self, fileid):
131
213
        """Returns a file reading from a particular entry."""
132
214
        p = self._path(fileid)
133
215
        try:
134
216
            return gzip.GzipFile(p + '.gz', 'rb')
135
217
        except IOError, e:
136
 
            if e.errno == errno.ENOENT:
137
 
                return file(p, 'rb')
138
 
            else:
139
 
                raise 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
 
140
229
 
141
230
    def total_size(self):
142
231
        """Return (count, bytes)
162
251
    """Self-destructing test subclass of ImmutableStore.
163
252
 
164
253
    The Store only exists for the lifetime of the Python object.
165
 
    Obviously you should not put anything precious in it.
 
254
 Obviously you should not put anything precious in it.
166
255
    """
167
256
    def __init__(self):
168
257
        ImmutableStore.__init__(self, tempfile.mkdtemp())
175
264
            os.remove(fpath)
176
265
        os.rmdir(self._basedir)
177
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)