1
# Copyright (C) 2005, 2006 by Canonical Development Ltd
1
# Copyright (C) 2005, 2006 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30
30
from zlib import adler32
33
import bzrlib.errors as errors
34
40
from bzrlib.errors import BzrError, UnlistableStore, TransportNotPossible
35
from bzrlib.symbol_versioning import *
41
from bzrlib.symbol_versioning import (
36
44
from bzrlib.trace import mutter
37
45
from bzrlib.transport import Transport
38
46
from bzrlib.transport.local import LocalTransport
39
import bzrlib.urlutils as urlutils
41
48
######################################################################
67
74
"""DEPRECATED. Please use .get(fileid) instead."""
68
75
raise NotImplementedError
70
#def __contains__(self, fileid):
71
# """Deprecated, please use has_id"""
72
# raise NotImplementedError
74
77
def __iter__(self):
75
78
raise NotImplementedError
88
91
def listable(self):
89
92
"""Return True if this store is able to be listed."""
90
return hasattr(self, "__iter__")
93
return (getattr(self, "__iter__", None) is not None)
92
95
def copy_all_ids(self, store_from, pb=None):
93
96
"""Copy all the file ids from store_from into self."""
164
165
def add(self, f, fileid, suffix=None):
165
166
"""Add contents of a file into the store.
167
f -- A file-like object, or string
168
f -- A file-like object
169
170
mutter("add store entry %r", fileid)
171
171
names = self._id_to_names(fileid, suffix)
172
172
if self._transport.has_any(names):
173
173
raise BzrError("store %r already contains id %r"
178
178
# doesn't exist), then create the dir, and try again
179
179
self._add(names[0], f)
182
181
def _add(self, relpath, f):
183
182
"""Actually add the file to the given location.
184
183
This should be overridden by children.
186
185
raise NotImplementedError('children need to implement this function.')
188
187
def _check_fileid(self, fileid):
189
if not isinstance(fileid, basestring):
190
raise TypeError('Fileids should be a string type: %s %r' % (type(fileid), fileid))
188
if type(fileid) != str:
189
raise TypeError('Fileids should be bytestrings: %s %r' % (
190
type(fileid), fileid))
191
191
if '\\' in fileid or '/' in fileid:
192
192
raise ValueError("invalid store id %r" % fileid)
241
241
def __init__(self, a_transport, prefixed=False, compressed=False,
242
242
dir_mode=None, file_mode=None,
244
assert isinstance(a_transport, Transport)
245
244
super(TransportStore, self).__init__()
246
245
self._transport = a_transport
247
246
self._prefixed = prefixed
254
253
# will just use the filesystem defaults
255
254
self._dir_mode = dir_mode
256
255
self._file_mode = file_mode
258
def _unescape(self, file_id):
259
"""If filename escaping is enabled for this store, unescape and return the filename."""
261
return urllib.unquote(file_id)
256
# Create a key mapper to use
257
if escaped and prefixed:
258
self._mapper = versionedfile.HashEscapedPrefixMapper()
259
elif not escaped and prefixed:
260
self._mapper = versionedfile.HashPrefixMapper()
263
"%r: escaped unprefixed stores are not permitted."
266
self._mapper = versionedfile.PrefixMapper()
265
268
def _iter_files_recursive(self):
266
269
"""Iterate through the files in the transport."""
267
270
for quoted_relpath in self._transport.iter_files_recursive():
268
# transport iterator always returns quoted paths, regardless of
270
yield urllib.unquote(quoted_relpath)
272
273
def __iter__(self):
273
274
for relpath in self._iter_files_recursive():
295
296
self._check_fileid(suffix)
298
fileid = self._escape_file_id(fileid)
300
# hash_prefix adds the '/' separator
301
prefix = self.hash_prefix(fileid, escaped=True)
304
path = prefix + fileid
305
full_path = u'.'.join([path] + suffixes)
306
return urlutils.escape(full_path)
308
def _escape_file_id(self, file_id):
309
"""Turn a file id into a filesystem safe string.
311
This is similar to a plain urllib.quote, except
312
it uses specific safe characters, so that it doesn't
313
have to translate a lot of valid file ids.
315
if not self._escaped:
317
if isinstance(file_id, unicode):
318
file_id = file_id.encode('utf-8')
319
# @ does not get escaped. This is because it is a valid
320
# filesystem character we use all the time, and it looks
321
# a lot better than seeing %40 all the time.
322
safe = "abcdefghijklmnopqrstuvwxyz0123456789-_@,."
323
r = [((c in safe) and c or ('%%%02x' % ord(c)))
327
def hash_prefix(self, fileid, escaped=False):
328
# fileid should be unescaped
329
if not escaped and self._escaped:
330
fileid = self._escape_file_id(fileid)
331
return "%02x/" % (adler32(fileid) & 0xff)
299
path = self._mapper.map((fileid,))
300
full_path = '.'.join([path] + suffixes)
333
303
def __repr__(self):
334
304
if self._transport is None:
361
331
total += self._transport.stat(relpath).st_size
363
333
return count, total
366
@deprecated_function(zero_eight)
367
def copy_all(store_from, store_to, pb=None):
368
"""Copy all ids from one store to another."""
369
store_to.copy_all_ids(store_from, pb)