/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
1
# Copyright (C) 2008 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
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
16
17
"""Persistent maps from string->string using CHK stores."""
18
19
import osutils
20
21
22
class CHKMap(object):
23
    """A persistent map from string to string backed by a CHK store."""
24
25
    def __init__(self, store, root_key):
26
        """Create a CHKMap object.
27
28
        :param store: The store the CHKMap is stored in.
29
        :param root_key: The root key of the map. None to create an empty
30
            CHKMap.
31
        """
32
        self._store = store
33
        if root_key is None:
34
            self._root_node = RootNode()
35
        else:
36
            self._root_node = root_key
37
38
    def apply_delta(self, delta):
39
        """Apply a delta to the map.
40
41
        :param delta: An iterable of old_key, new_key, new_value tuples.
42
            If new_key is not None, then new_key->new_value is inserted
43
            into the map; if old_key is not None, then the old mapping
44
            of old_key is removed.
45
        """
46
        for old, new, value in delta:
47
            if old is not None and old != new:
48
                # unmap
49
                self._unmap(old)
50
        for old, new, value in delta:
51
            if new is not None:
52
                # map
53
                self._map(new, value)
54
        return self._save()
55
56
    def _ensure_root(self):
57
        """Ensure that the root node is an object not a key."""
58
        if type(self._root_node) == tuple:
59
            # Demand-load the root
60
            bytes = self._read_bytes(self._root_node)
61
            root_key = self._root_node
62
            self._root_node = RootNode()
63
            self._root_node.deserialise(bytes, root_key)
64
65
    def _read_bytes(self, key):
66
        stream = self._store.get_record_stream([key], 'unordered', True)
67
        return stream.next().get_bytes_as('fulltext')
68
69
    @classmethod
70
    def from_dict(klass, store, initial_value):
71
        """Create a CHKMap in store with initial_value as the content.
72
        
73
        :param store: The store to record initial_value in, a VersionedFiles
74
            object with 1-tuple keys supporting CHK key generation.
75
        :param initial_value: A dict to store in store. Its keys and values
76
            must be bytestrings.
77
        :return: The root chk of te resulting CHKMap.
78
        """
79
        result = CHKMap(store, None)
80
        for key, value in initial_value.items():
81
            result._map(key, value)
82
        return result._save()
83
84
    def iteritems(self):
85
        """Iterate over the entire CHKMap's contents."""
86
        self._ensure_root()
87
        for name, key, in self._root_node._nodes.iteritems():
88
            bytes = self._read_bytes(key)
89
            yield name, ValueNode.deserialise(bytes).value
90
91
    def _map(self, key, value):
92
        """Map key to value."""
93
        self._ensure_root()
94
        # Store the value
95
        bytes = ValueNode(value).serialise()
96
        sha1, _, _ = self._store.add_lines((None,), (), osutils.split_lines(bytes))
97
        # And link into the root
98
        self._root_node.add_child(key, ("sha1:" + sha1,))
99
100
    def _unmap(self, key):
101
        """remove key from the map."""
102
        self._ensure_root()
103
        self._root_node.remove_child(key)
104
105
    def _save(self):
106
        """Save the map completely.
107
108
        :return: The key of the root node.
109
        """
110
        if type(self._root_node) == tuple:
111
            # Already saved.
112
            return self._root_node
113
        # TODO: flush root_nodes children?
114
        bytes = self._root_node.serialise()
115
        sha1, _, _ = self._store.add_lines((None,), (),
116
            osutils.split_lines(bytes))
117
        result = ("sha1:" + sha1,)
118
        self._root_node._key = result
119
        return result
120
121
122
class RootNode(object):
123
    """A root node in a CHKMap."""
124
125
    def __init__(self):
126
        self._nodes = {}
127
128
    def add_child(self, name, child):
129
        """Add a child to the node.
130
131
        If the node changes, it's key is reset.
132
133
        :param name: The name of the child. A bytestring.
134
        :param child: The child, a key tuple for the childs value.
135
        """
136
        self._nodes[name] = child
137
        self._key = None
138
139
    def deserialise(self, bytes, key):
140
        """Set the nodes value to that contained in bytes.
141
        
142
        :param bytes: The bytes of the node.
143
        :param key: The key that the serialised node has.
144
        """
145
        lines = bytes.splitlines()
146
        nodes = {}
147
        if lines[0] != 'chkroot:':
148
            raise ValueError("not a serialised root node: %r" % bytes)
149
        for line in lines[1:]:
150
            name, value = line.split('\x00')
151
            nodes[name] = (value,)
152
        self._nodes = nodes
153
        self._key = key
154
155
    def remove_child(self, name):
156
        """Remove name from the node.
157
158
        If the node changes, it's key is reset.
159
160
        :param name: The name to remove from the node.
161
        """
162
        del self._nodes[name]
163
        self._key = None
164
165
    def serialise(self):
166
        """Flatten the node to a bytestring.
167
168
        :return: A bytestring.
169
        """
170
        lines = ["chkroot:\n"]
171
        for name, child in sorted(self._nodes.items()):
172
            lines.append("%s\x00%s\n" % (name, child[0]))
173
        return "".join(lines)
174
175
176
class ValueNode(object):
177
    """A value in a CHKMap."""
178
179
    def __init__(self, value):
180
        """Create a ValueNode.
181
182
        :param value: The value of this node, must be a bytestring.
183
        """
184
        self.value = value
185
186
    @classmethod
187
    def deserialise(klass, bytes):
188
        """Get a ValueNode from a serialised bytestring.
189
        
190
        :param bytes: The bytes returned by an earlier serialisation.
191
        """
192
        if not bytes.startswith("chkvalue:\n"):
193
            raise ValueError("not a chkvalue %r" % bytes)
194
        return ValueNode(bytes[10:])
195
196
    def serialise(self):
197
        """Flatten the value to a bytestring.
198
199
        :return: A bytestring.
200
        """
201
        return "chkvalue:\n" + self.value