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
18
"""Stores are the main data-storage mechanism for Bazaar-NG.
17
# TODO: Could remember a bias towards whether a particular store is typically
21
Stores are the main data-storage mechanism for Bazaar-NG.
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."""
23
__copyright__ = "Copyright (C) 2005 Canonical Ltd."
24
__author__ = "Martin Pool <mbp@canonical.com>"
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
31
34
######################################################################
58
61
>>> st['123123'].read()
61
:todo: Atomic add by writing to a temporary file and renaming.
63
:todo: Perhaps automatically transform to/from XML in a method?
64
Would just need to tell the constructor what class to
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.
64
TODO: Atomic add by writing to a temporary file and renaming.
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
73
71
def __init__(self, basedir):
74
"""ImmutableStore constructor."""
75
72
self._basedir = basedir
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)
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.
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
87
f -- An open file, or file-like object."""
88
# FIXME: Only works on files that will fit in memory
90
from bzrlib.atomicfile import AtomicFile
90
92
mutter("add store entry %r" % (fileid))
91
93
if isinstance(f, types.StringTypes):
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))
101
f = gzip.GzipFile(p + '.gz', 'wb')
102
os.chmod(p + '.gz', 0444)
106
af = AtomicFile(fn, 'wb')
109
gf = gzip.GzipFile(mode='wb', fileobj=af)
119
def copy_multi(self, other, ids, permit_failure=False):
120
"""Copy texts for ids from other into self.
122
If an id is present in self, it is skipped.
124
Returns (count_copied, failed), where failed is a collection of ids
125
that could not be copied.
127
pb = bzrlib.ui.ui_factory.progress_bar()
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)
137
pb.update('copy', count, len(to_copy))
138
if not permit_failure:
139
self.add(other[id], id)
148
if not permit_failure:
149
assert count == len(to_copy)
153
def copy_multi_immutable(self, other, to_copy, pb, permit_failure=False):
154
from shutil import copyfile
159
other_p = other._path(id)
163
if e.errno == errno.ENOENT:
164
if not permit_failure:
165
copyfile(other_p+".gz", p+".gz")
168
copyfile(other_p+".gz", p+".gz")
170
if e.errno == errno.ENOENT:
178
pb.update('copy', count, len(to_copy))
179
assert count == len(to_copy)
111
184
def __contains__(self, fileid):
114
187
return (os.access(p, os.R_OK)
115
188
or os.access(p + '.gz', os.R_OK))
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
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))
130
205
def __getitem__(self, fileid):
131
206
"""Returns a file reading from a particular entry."""
132
207
p = self._path(fileid)
134
209
return gzip.GzipFile(p + '.gz', 'rb')
135
210
except IOError, e:
136
if e.errno == errno.ENOENT:
211
if e.errno != errno.ENOENT:
217
if e.errno != errno.ENOENT:
220
raise IndexError(fileid)
141
223
def total_size(self):
142
224
"""Return (count, bytes)