13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
# TODO: Could remember a bias towards whether a particular store is typically
18
18
# compressed or not.
28
from cStringIO import StringIO
30
from zlib import adler32
33
29
from bzrlib import (
39
from bzrlib.errors import BzrError, UnlistableStore, TransportNotPossible
40
from bzrlib.symbol_versioning import (
33
from bzrlib.errors import BzrError, UnlistableStore
45
34
from bzrlib.trace import mutter
46
from bzrlib.transport import Transport
47
from bzrlib.transport.local import LocalTransport
49
36
######################################################################
56
43
class Store(object):
57
44
"""This class represents the abstract storage layout for saving information.
59
46
Files can be added, but not modified once they are in. Typically
60
47
the hash is used as the name, or something else known to be unique,
67
54
def get(self, fileid, suffix=None):
68
55
"""Returns a file reading from a particular entry.
70
57
If suffix is present, retrieve the named suffix for fileid.
72
59
raise NotImplementedError
75
62
"""DEPRECATED. Please use .get(fileid) instead."""
76
63
raise NotImplementedError
78
#def __contains__(self, fileid):
79
# """Deprecated, please use has_id"""
80
# raise NotImplementedError
82
65
def __iter__(self):
83
66
raise NotImplementedError
89
72
def has_id(self, fileid, suffix=None):
90
73
"""Return True or false for the presence of fileid in the store.
92
suffix, if present, is a per file suffix, i.e. for digital signature
75
suffix, if present, is a per file suffix, i.e. for digital signature
94
77
raise NotImplementedError
148
130
failed.add(fileid)
151
assert count == len(ids)
154
135
return count, failed
156
137
def _copy_one(self, fileid, suffix, other, pb):
157
138
"""Most generic copy-one object routine.
159
140
Subclasses can override this to provide an optimised
160
141
copy between their own instances. Such overriden routines
161
should call this if they have no optimised facility for a
142
should call this if they have no optimised facility for a
162
143
specific 'other'.
164
145
mutter('Store._copy_one: %r', fileid)
175
156
f -- A file-like object
177
fileid = osutils.safe_file_id(fileid)
178
158
mutter("add store entry %r", fileid)
179
if isinstance(f, str):
180
symbol_versioning.warn(zero_eleven % 'Passing a string to Store.add',
181
DeprecationWarning, stacklevel=2)
184
159
names = self._id_to_names(fileid, suffix)
185
160
if self._transport.has_any(names):
186
raise BzrError("store %r already contains id %r"
161
raise BzrError("store %r already contains id %r"
187
162
% (self._transport.base, fileid))
189
164
# Most of the time, just adding the file will work
198
173
raise NotImplementedError('children need to implement this function.')
200
175
def _check_fileid(self, fileid):
201
if not isinstance(fileid, basestring):
202
raise TypeError('Fileids should be a string type: %s %r' % (type(fileid), fileid))
176
if type(fileid) != str:
177
raise TypeError('Fileids should be bytestrings: %s %r' % (
178
type(fileid), fileid))
203
179
if '\\' in fileid or '/' in fileid:
204
180
raise ValueError("invalid store id %r" % fileid)
220
196
def has_id(self, fileid, suffix=None):
221
197
"""See Store.has_id."""
222
fileid = osutils.safe_file_id(fileid)
223
198
return self._transport.has_any(self._id_to_names(fileid, suffix))
225
200
def _get_name(self, fileid, suffix=None):
226
201
"""A special check, which returns the name of an existing file.
228
203
This is similar in spirit to 'has_id', but it is designed
229
204
to return information about which file the store has.
236
211
def _get(self, filename):
237
212
"""Return an vanilla file stream for clients to read from.
239
This is the body of a template method on 'get', and should be
214
This is the body of a template method on 'get', and should be
240
215
implemented by subclasses.
242
217
raise NotImplementedError
244
219
def get(self, fileid, suffix=None):
245
220
"""See Store.get()."""
246
fileid = osutils.safe_file_id(fileid)
247
221
names = self._id_to_names(fileid, suffix)
248
222
for name in names:
255
229
def __init__(self, a_transport, prefixed=False, compressed=False,
256
230
dir_mode=None, file_mode=None,
258
assert isinstance(a_transport, Transport)
259
232
super(TransportStore, self).__init__()
260
233
self._transport = a_transport
261
234
self._prefixed = prefixed
268
241
# will just use the filesystem defaults
269
242
self._dir_mode = dir_mode
270
243
self._file_mode = file_mode
272
def _unescape(self, file_id):
273
"""If filename escaping is enabled for this store, unescape and return the filename."""
275
return urllib.unquote(file_id)
244
# Create a key mapper to use
245
if escaped and prefixed:
246
self._mapper = versionedfile.HashEscapedPrefixMapper()
247
elif not escaped and prefixed:
248
self._mapper = versionedfile.HashPrefixMapper()
251
"%r: escaped unprefixed stores are not permitted."
254
self._mapper = versionedfile.PrefixMapper()
279
256
def _iter_files_recursive(self):
280
257
"""Iterate through the files in the transport."""
281
258
for quoted_relpath in self._transport.iter_files_recursive():
282
# transport iterator always returns quoted paths, regardless of
284
yield urllib.unquote(quoted_relpath)
286
261
def __iter__(self):
287
262
for relpath in self._iter_files_recursive():
309
284
self._check_fileid(suffix)
312
fileid = self._escape_file_id(fileid)
314
# hash_prefix adds the '/' separator
315
prefix = self.hash_prefix(fileid, escaped=True)
318
path = prefix + fileid
319
full_path = u'.'.join([path] + suffixes)
320
return urlutils.escape(full_path)
322
def _escape_file_id(self, file_id):
323
"""Turn a file id into a filesystem safe string.
325
This is similar to a plain urllib.quote, except
326
it uses specific safe characters, so that it doesn't
327
have to translate a lot of valid file ids.
329
if not self._escaped:
331
if isinstance(file_id, unicode):
332
file_id = file_id.encode('utf-8')
333
# @ does not get escaped. This is because it is a valid
334
# filesystem character we use all the time, and it looks
335
# a lot better than seeing %40 all the time.
336
safe = "abcdefghijklmnopqrstuvwxyz0123456789-_@,."
337
r = [((c in safe) and c or ('%%%02x' % ord(c)))
341
def hash_prefix(self, fileid, escaped=False):
342
# fileid should be unescaped
343
if not escaped and self._escaped:
344
fileid = self._escape_file_id(fileid)
345
return "%02x/" % (adler32(fileid) & 0xff)
287
path = self._mapper.map((fileid,))
288
full_path = '.'.join([path] + suffixes)
347
291
def __repr__(self):
348
292
if self._transport is None:
373
317
for relpath in self._transport.iter_files_recursive():
375
319
total += self._transport.stat(relpath).st_size
377
321
return count, total
380
@deprecated_function(zero_eight)
381
def copy_all(store_from, store_to, pb=None):
382
"""Copy all ids from one store to another."""
383
store_to.copy_all_ids(store_from, pb)