~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

 * New ``versionedfile.KeyMapper`` interface to abstract out the access to
   underyling .knit/.kndx etc files in repositories with partitioned
   storage. (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
 
20
20
"""Versioned text file storage api."""
21
21
 
 
22
from cStringIO import StringIO
 
23
import urllib
 
24
from zlib import adler32
 
25
 
22
26
from bzrlib.lazy_import import lazy_import
23
27
lazy_import(globals(), """
24
28
 
33
37
from bzrlib.graph import Graph
34
38
from bzrlib.transport.memory import MemoryTransport
35
39
""")
36
 
 
37
 
from cStringIO import StringIO
38
 
 
39
40
from bzrlib.inter import InterObject
40
41
from bzrlib.registry import Registry
41
42
from bzrlib.symbol_versioning import *
799
800
                    else:
800
801
                        new_version_ids.add(version)
801
802
                return new_version_ids
 
803
 
 
804
 
 
805
class KeyMapper(object):
 
806
    """KeyMappers map between keys and underlying paritioned storage."""
 
807
 
 
808
    def map(self, key):
 
809
        """Map key to an underlying storage identifier.
 
810
 
 
811
        :param key: A key tuple e.g. ('file-id', 'revision-id').
 
812
        :return: An underlying storage identifier, specific to the partitioning
 
813
            mechanism.
 
814
        """
 
815
 
 
816
    def unmap(self, partition_id):
 
817
        """Map a partitioned storage id back to a key prefix.
 
818
        
 
819
        :param partition_id: The underlying partition id.
 
820
        :return: As much of a key (or prefix) as is derivable from the parition
 
821
            id.
 
822
        """
 
823
 
 
824
 
 
825
class ConstantMapper(KeyMapper):
 
826
    """A key mapper that maps to a constant result."""
 
827
 
 
828
    def __init__(self, result):
 
829
        """Create a ConstantMapper which will return result for all maps."""
 
830
        self._result = result
 
831
 
 
832
    def map(self, key):
 
833
        """See KeyMapper.map()."""
 
834
        return self._result
 
835
 
 
836
 
 
837
class PrefixMapper(KeyMapper):
 
838
    """A key mapper that extracts the first component of a key."""
 
839
 
 
840
    def map(self, key):
 
841
        """See KeyMapper.map()."""
 
842
        return key[0]
 
843
 
 
844
    def unmap(self, partition_id):
 
845
        """See KeyMapper.unmap()."""
 
846
        return (partition_id,)
 
847
 
 
848
 
 
849
class HashPrefixMapper(KeyMapper):
 
850
    """A key mapper that combines the first component of a key with a hash."""
 
851
 
 
852
    def map(self, key):
 
853
        """See KeyMapper.map()."""
 
854
        prefix = self._escape(key[0])
 
855
        return "%02x/%s" % (adler32(prefix) & 0xff, prefix)
 
856
 
 
857
    def _escape(self, prefix):
 
858
        """No escaping needed here."""
 
859
        return prefix
 
860
 
 
861
    def unmap(self, partition_id):
 
862
        """See KeyMapper.unmap()."""
 
863
        return (self._unescape(osutils.basename(partition_id)),)
 
864
 
 
865
    def _unescape(self, basename):
 
866
        """No unescaping needed for HashPrefixMapper."""
 
867
        return basename
 
868
 
 
869
 
 
870
class HashEscapedPrefixMapper(HashPrefixMapper):
 
871
    """Combines the escaped first component of a key with a hash."""
 
872
 
 
873
    _safe = "abcdefghijklmnopqrstuvwxyz0123456789-_@,."
 
874
 
 
875
    def _escape(self, prefix):
 
876
        """Turn a key element into a filesystem safe string.
 
877
 
 
878
        This is similar to a plain urllib.quote, except
 
879
        it uses specific safe characters, so that it doesn't
 
880
        have to translate a lot of valid file ids.
 
881
        """
 
882
        # @ does not get escaped. This is because it is a valid
 
883
        # filesystem character we use all the time, and it looks
 
884
        # a lot better than seeing %40 all the time.
 
885
        r = [((c in self._safe) and c or ('%%%02x' % ord(c)))
 
886
             for c in prefix]
 
887
        return ''.join(r)
 
888
 
 
889
    def _unescape(self, basename):
 
890
        """Escaped names are unescaped by urlutils."""
 
891
        return urllib.unquote(basename)