~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/store.py

  • Committer: mbp at sourcefrog
  • Date: 2005-04-05 08:24:51 UTC
  • Revision ID: mbp@sourcefrog.net-20050405082451-408ebb0fd108440f
start adding quotes

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 by Canonical Development Ltd
 
1
#! /usr/bin/env python
 
2
# -*- coding: UTF-8 -*-
2
3
 
3
4
# This program is free software; you can redistribute it and/or modify
4
5
# it under the terms of the GNU General Public License as published by
14
15
# along with this program; if not, write to the Free Software
15
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
 
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.
 
18
"""Stores are the main data-storage mechanism for Bazaar-NG.
22
19
 
23
20
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
 
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
32
27
from stat import ST_SIZE
33
28
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
 
 
 
29
from trace import mutter
40
30
 
41
31
######################################################################
42
32
# stores
45
35
    pass
46
36
 
47
37
 
48
 
class ImmutableStore(object):
 
38
class ImmutableStore:
49
39
    """Store that holds files indexed by unique names.
50
40
 
51
41
    Files can be added, but not modified once they are in.  Typically
68
58
    >>> st['123123'].read()
69
59
    'goodbye'
70
60
 
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.
 
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
 
76
71
    """
77
72
 
78
73
    def __init__(self, basedir):
 
74
        """ImmutableStore constructor."""
79
75
        self._basedir = basedir
80
76
 
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)
 
77
    def _path(self, id):
 
78
        return os.path.join(self._basedir, id)
87
79
 
88
80
    def __repr__(self):
89
81
        return "%s(%r)" % (self.__class__.__name__, self._basedir)
91
83
    def add(self, f, fileid, compressed=True):
92
84
        """Add contents of a file into the store.
93
85
 
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
 
        
 
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.
99
90
        mutter("add store entry %r" % (fileid))
100
91
        if isinstance(f, types.StringTypes):
101
92
            content = f
102
93
        else:
103
94
            content = f.read()
104
 
            
 
95
 
105
96
        p = self._path(fileid)
106
97
        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))
 
98
            bailout("store %r already contains id %r" % (self._basedir, fileid))
108
99
 
109
 
        fn = p
110
100
        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
 
    
 
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
 
190
110
 
191
111
    def __contains__(self, fileid):
192
112
        """"""
194
114
        return (os.access(p, os.R_OK)
195
115
                or os.access(p + '.gz', os.R_OK))
196
116
 
197
 
    # TODO: Guard against the same thing being stored twice,
198
 
    # compressed and uncompressed
 
117
    # TODO: Guard against the same thing being stored twice, compressed and uncompresse
199
118
 
200
119
    def __iter__(self):
201
120
        for f in os.listdir(self._basedir):
208
127
    def __len__(self):
209
128
        return len(os.listdir(self._basedir))
210
129
 
211
 
 
212
130
    def __getitem__(self, fileid):
213
131
        """Returns a file reading from a particular entry."""
214
132
        p = self._path(fileid)
215
133
        try:
216
134
            return gzip.GzipFile(p + '.gz', 'rb')
217
135
        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
 
 
 
136
            if e.errno == errno.ENOENT:
 
137
                return file(p, 'rb')
 
138
            else:
 
139
                raise e
229
140
 
230
141
    def total_size(self):
231
142
        """Return (count, bytes)
251
162
    """Self-destructing test subclass of ImmutableStore.
252
163
 
253
164
    The Store only exists for the lifetime of the Python object.
254
 
 Obviously you should not put anything precious in it.
 
165
    Obviously you should not put anything precious in it.
255
166
    """
256
167
    def __init__(self):
257
168
        ImmutableStore.__init__(self, tempfile.mkdtemp())
264
175
            os.remove(fpath)
265
176
        os.rmdir(self._basedir)
266
177
        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)