32
32
class StoreError(Exception):
36
class ImmutableStore(object):
35
class Storage(object):
36
"""This class represents the abstract storage layout for saving information.
38
def __init__(self, transport):
39
self._transport = transport
40
self._max_buffered_requests = 10
43
return "%s(%r)" % (self.__class__.__name__, self._transport.base)
48
raise NotImplementedError('Children should define their length')
50
def __getitem__(self, fileid):
51
"""Returns a file reading from a particular entry."""
52
raise NotImplementedError
54
def __contains__(self, fileid):
56
raise NotImplementedError
59
raise NotImplementedError
61
def add(self, f, fileid):
62
"""Add a file object f to the store accessible from the given fileid"""
63
raise NotImplementedError('Children of Storage must define their method of adding entries.')
65
def copy_multi(self, other, ids):
66
"""Copy texts for ids from other into self.
68
If an id is present in self, it is skipped. A count of copied
69
ids is returned, which may be less than len(ids).
71
from bzrlib.progress import ProgressBar
73
pb.update('preparing to copy')
74
to_copy = [fileid for fileid in ids if fileid not in self]
75
return self._do_copy(other, to_copy, pb)
77
def _do_copy(self, other, to_copy, pb):
78
"""This is the standard copying mechanism, just get them one at
79
a time from remote, and store them locally.
82
buffered_requests = []
83
for fileid in to_copy:
84
buffered_requests.append((other[fileid], fileid))
85
if len(buffered_requests) > self._max_buffered_requests:
86
self.add(*buffered_requests.pop(0))
88
pb.update('copy', count, len(to_copy))
90
for req in buffered_requests:
93
pb.update('copy', count, len(to_copy))
95
assert count == len(to_copy)
101
class CompressedTextStore(Storage):
37
102
"""Store that holds files indexed by unique names.
39
104
Files can be added, but not modified once they are in. Typically
40
105
the hash is used as the name, or something else known to be unique,
43
>>> st = ImmutableScratchStore()
108
Files are stored gzip compressed, with no delta compression.
110
>>> st = ScratchFlatTextStore()
45
112
>>> st.add(StringIO('hello'), 'aa')
66
133
def __init__(self, basedir):
67
self._basedir = basedir
134
super(CompressedTextStore, self).__init__(basedir)
70
if '\\' in id or '/' in id:
71
raise ValueError("invalid store id %r" % id)
72
return os.path.join(self._basedir, id)
136
def _path(self, fileid):
137
if '\\' in fileid or '/' in fileid:
138
raise ValueError("invalid store id %r" % fileid)
139
return self._transport.get_filename(fileid)
74
141
def __repr__(self):
75
return "%s(%r)" % (self.__class__.__name__, self._basedir)
142
return "%s(%r)" % (self.__class__.__name__, self._location)
77
144
def add(self, f, fileid, compressed=True):
78
145
"""Add contents of a file into the store.
80
147
f -- An open file, or file-like object."""
81
148
# FIXME: Only works on files that will fit in memory
83
from bzrlib.atomicfile import AtomicFile
150
from cStringIO import StringIO
85
152
mutter("add store entry %r" % (fileid))
86
if isinstance(f, types.StringTypes):
153
if isinstance(f, basestring):
89
156
content = f.read()
91
p = self._path(fileid)
92
if os.access(p, os.F_OK) or os.access(p + '.gz', os.F_OK):
93
raise BzrError("store %r already contains id %r" % (self._basedir, fileid))
158
if self._transport.has(fileid) or self._transport.has(fileid + '.gz'):
159
raise BzrError("store %r already contains id %r" % (self._location, fileid))
99
af = AtomicFile(fn, 'wb')
102
gf = gzip.GzipFile(mode='wb', fileobj=af)
112
def copy_multi(self, other, ids):
113
"""Copy texts for ids from other into self.
115
If an id is present in self, it is skipped. A count of copied
116
ids is returned, which may be less than len(ids).
118
from bzrlib.progress import ProgressBar
120
pb.update('preparing to copy')
121
to_copy = [id for id in ids if id not in self]
122
if isinstance(other, ImmutableStore):
123
return self.copy_multi_immutable(other, to_copy, pb)
127
pb.update('copy', count, len(to_copy))
128
self.add(other[id], id)
129
assert count == len(to_copy)
134
def copy_multi_immutable(self, other, to_copy, pb):
167
gf = gzip.GzipFile(mode='wb', fileobj=sio)
173
self._transport.put(fn, sio)
175
def _do_copy(self, other, to_copy, pb):
176
if isinstance(other, CompressedTextStore):
177
return self._copy_multi_text(other, to_copy, pb)
178
return super(CompressedTextStore, self)._do_copy(other, to_copy, pb)
181
def _copy_multi_text(self, other, to_copy, pb):
135
182
from shutil import copyfile
137
184
for id in to_copy:
205
class ImmutableScratchStore(ImmutableStore):
252
class ScratchFlatTextStore(CompressedTextStore):
206
253
"""Self-destructing test subclass of ImmutableStore.
208
255
The Store only exists for the lifetime of the Python object.
209
Obviously you should not put anything precious in it.
256
Obviously you should not put anything precious in it.
211
258
def __init__(self):
212
ImmutableStore.__init__(self, tempfile.mkdtemp())
259
super(ScratchFlatTextStore, self).__init__(tempfile.mkdtemp())
214
261
def __del__(self):
215
for f in os.listdir(self._basedir):
216
fpath = os.path.join(self._basedir, f)
262
for f in os.listdir(self._location):
263
fpath = os.path.join(self._location, f)
217
264
# needed on windows, and maybe some other filesystems
218
265
os.chmod(fpath, 0600)
220
os.rmdir(self._basedir)
267
os.rmdir(self._location)
221
268
mutter("%r destroyed" % self)