/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
3735.2.9 by Robert Collins
Get a working chk_map using inventory implementation bootstrapped.
84
    def iteritems(self, key_filter=None):
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
85
        """Iterate over the entire CHKMap's contents."""
86
        self._ensure_root()
3735.2.9 by Robert Collins
Get a working chk_map using inventory implementation bootstrapped.
87
        if key_filter is not None:
88
            for name, key, in self._root_node._nodes.iteritems():
89
                if name in key_filter:
90
                    bytes = self._read_bytes(key)
91
                    yield name, ValueNode.deserialise(bytes).value
92
        else:
93
            for name, key, in self._root_node._nodes.iteritems():
94
                bytes = self._read_bytes(key)
95
                yield name, ValueNode.deserialise(bytes).value
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
96
3735.2.12 by Robert Collins
Implement commit-via-deltas for split inventory repositories.
97
    def key(self):
98
        """Return the key for this map."""
99
        if isinstance(self._root_node, tuple):
100
            return self._root_node
101
        else:
102
            return self._root_node._key
103
3735.2.17 by Robert Collins
Cache node length to avoid full iteration on __len__ calls.
104
    def __len__(self):
105
        self._ensure_root()
106
        return len(self._root_node)
107
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
108
    def _map(self, key, value):
109
        """Map key to value."""
110
        self._ensure_root()
111
        # Store the value
112
        bytes = ValueNode(value).serialise()
3735.2.15 by Robert Collins
More direct implementation of fetch between different serializers.
113
        # Experimental code to probe for keys rather than just adding; its not
114
        # clear if it is an improvement.
115
        #chk = ("sha1:%s" % osutils.sha_string(bytes),)
116
        #if not self._store.get_parent_map([key]):
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
117
        sha1, _, _ = self._store.add_lines((None,), (), osutils.split_lines(bytes))
3735.2.15 by Robert Collins
More direct implementation of fetch between different serializers.
118
        chk = ("sha1:" + sha1,)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
119
        # And link into the root
3735.2.15 by Robert Collins
More direct implementation of fetch between different serializers.
120
        self._root_node.add_child(key, chk)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
121
122
    def _unmap(self, key):
123
        """remove key from the map."""
124
        self._ensure_root()
125
        self._root_node.remove_child(key)
126
127
    def _save(self):
128
        """Save the map completely.
129
130
        :return: The key of the root node.
131
        """
132
        if type(self._root_node) == tuple:
133
            # Already saved.
134
            return self._root_node
135
        # TODO: flush root_nodes children?
136
        bytes = self._root_node.serialise()
137
        sha1, _, _ = self._store.add_lines((None,), (),
138
            osutils.split_lines(bytes))
139
        result = ("sha1:" + sha1,)
140
        self._root_node._key = result
141
        return result
142
143
144
class RootNode(object):
145
    """A root node in a CHKMap."""
146
147
    def __init__(self):
148
        self._nodes = {}
3735.2.10 by Robert Collins
Teach CHKInventory how to make a new inventory from an inventory delta.
149
        self._key = None
3735.2.17 by Robert Collins
Cache node length to avoid full iteration on __len__ calls.
150
        self._len = 0
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
151
152
    def add_child(self, name, child):
153
        """Add a child to the node.
154
155
        If the node changes, it's key is reset.
156
157
        :param name: The name of the child. A bytestring.
158
        :param child: The child, a key tuple for the childs value.
159
        """
160
        self._nodes[name] = child
3735.2.17 by Robert Collins
Cache node length to avoid full iteration on __len__ calls.
161
        self._len += 1
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
162
        self._key = None
163
164
    def deserialise(self, bytes, key):
165
        """Set the nodes value to that contained in bytes.
166
        
167
        :param bytes: The bytes of the node.
168
        :param key: The key that the serialised node has.
169
        """
170
        lines = bytes.splitlines()
171
        nodes = {}
172
        if lines[0] != 'chkroot:':
173
            raise ValueError("not a serialised root node: %r" % bytes)
3735.2.17 by Robert Collins
Cache node length to avoid full iteration on __len__ calls.
174
        length = int(lines[1])
175
        for line in lines[2:]:
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
176
            name, value = line.split('\x00')
177
            nodes[name] = (value,)
178
        self._nodes = nodes
3735.2.17 by Robert Collins
Cache node length to avoid full iteration on __len__ calls.
179
        self._len = length
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
180
        self._key = key
181
3735.2.17 by Robert Collins
Cache node length to avoid full iteration on __len__ calls.
182
    def __len__(self):
183
        return self._len
184
3735.2.9 by Robert Collins
Get a working chk_map using inventory implementation bootstrapped.
185
    def refs(self):
186
        """Get the CHK key references this node holds."""
187
        return self._nodes.values()
188
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
189
    def remove_child(self, name):
190
        """Remove name from the node.
191
192
        If the node changes, it's key is reset.
193
194
        :param name: The name to remove from the node.
195
        """
196
        del self._nodes[name]
3735.2.17 by Robert Collins
Cache node length to avoid full iteration on __len__ calls.
197
        self._len -= 1
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
198
        self._key = None
199
200
    def serialise(self):
201
        """Flatten the node to a bytestring.
202
203
        :return: A bytestring.
204
        """
205
        lines = ["chkroot:\n"]
3735.2.17 by Robert Collins
Cache node length to avoid full iteration on __len__ calls.
206
        lines.append("%d\n" % self._len)
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
207
        for name, child in sorted(self._nodes.items()):
208
            lines.append("%s\x00%s\n" % (name, child[0]))
209
        return "".join(lines)
210
211
212
class ValueNode(object):
213
    """A value in a CHKMap."""
214
215
    def __init__(self, value):
216
        """Create a ValueNode.
217
218
        :param value: The value of this node, must be a bytestring.
219
        """
220
        self.value = value
221
222
    @classmethod
223
    def deserialise(klass, bytes):
224
        """Get a ValueNode from a serialised bytestring.
225
        
226
        :param bytes: The bytes returned by an earlier serialisation.
227
        """
228
        if not bytes.startswith("chkvalue:\n"):
229
            raise ValueError("not a chkvalue %r" % bytes)
230
        return ValueNode(bytes[10:])
231
232
    def serialise(self):
233
        """Flatten the value to a bytestring.
234
235
        :return: A bytestring.
236
        """
237
        return "chkvalue:\n" + self.value
3735.2.16 by Robert Collins
Untested extensions to support repodetails
238
239
    def refs(self):
240
        """ValueNodes have no refs within the dict."""
241
        return []
242
243
244
def _deserialise(bytes, key):
245
    """Helper for repositorydetails - convert bytes to a node."""
246
    if bytes.startswith("chkvalue:\n"):
247
        return ValueNode.deserialise(bytes)
248
    elif bytes.startswith("chkroot:\n"):
249
        result = RootNode()
250
        result.deserialise(bytes, key)
251
        return result
252
    else:
253
        raise AssertionError("Unknown node type.")