/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.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
104
    def _map(self, key, value):
105
        """Map key to value."""
106
        self._ensure_root()
107
        # Store the value
108
        bytes = ValueNode(value).serialise()
109
        sha1, _, _ = self._store.add_lines((None,), (), osutils.split_lines(bytes))
110
        # And link into the root
111
        self._root_node.add_child(key, ("sha1:" + sha1,))
112
113
    def _unmap(self, key):
114
        """remove key from the map."""
115
        self._ensure_root()
116
        self._root_node.remove_child(key)
117
118
    def _save(self):
119
        """Save the map completely.
120
121
        :return: The key of the root node.
122
        """
123
        if type(self._root_node) == tuple:
124
            # Already saved.
125
            return self._root_node
126
        # TODO: flush root_nodes children?
127
        bytes = self._root_node.serialise()
128
        sha1, _, _ = self._store.add_lines((None,), (),
129
            osutils.split_lines(bytes))
130
        result = ("sha1:" + sha1,)
131
        self._root_node._key = result
132
        return result
133
134
135
class RootNode(object):
136
    """A root node in a CHKMap."""
137
138
    def __init__(self):
139
        self._nodes = {}
3735.2.10 by Robert Collins
Teach CHKInventory how to make a new inventory from an inventory delta.
140
        self._key = None
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
141
142
    def add_child(self, name, child):
143
        """Add a child to the node.
144
145
        If the node changes, it's key is reset.
146
147
        :param name: The name of the child. A bytestring.
148
        :param child: The child, a key tuple for the childs value.
149
        """
150
        self._nodes[name] = child
151
        self._key = None
152
153
    def deserialise(self, bytes, key):
154
        """Set the nodes value to that contained in bytes.
155
        
156
        :param bytes: The bytes of the node.
157
        :param key: The key that the serialised node has.
158
        """
159
        lines = bytes.splitlines()
160
        nodes = {}
161
        if lines[0] != 'chkroot:':
162
            raise ValueError("not a serialised root node: %r" % bytes)
163
        for line in lines[1:]:
164
            name, value = line.split('\x00')
165
            nodes[name] = (value,)
166
        self._nodes = nodes
167
        self._key = key
168
3735.2.9 by Robert Collins
Get a working chk_map using inventory implementation bootstrapped.
169
    def refs(self):
170
        """Get the CHK key references this node holds."""
171
        return self._nodes.values()
172
3735.2.8 by Robert Collins
New chk_map module for use in tree based inventory storage.
173
    def remove_child(self, name):
174
        """Remove name from the node.
175
176
        If the node changes, it's key is reset.
177
178
        :param name: The name to remove from the node.
179
        """
180
        del self._nodes[name]
181
        self._key = None
182
183
    def serialise(self):
184
        """Flatten the node to a bytestring.
185
186
        :return: A bytestring.
187
        """
188
        lines = ["chkroot:\n"]
189
        for name, child in sorted(self._nodes.items()):
190
            lines.append("%s\x00%s\n" % (name, child[0]))
191
        return "".join(lines)
192
193
194
class ValueNode(object):
195
    """A value in a CHKMap."""
196
197
    def __init__(self, value):
198
        """Create a ValueNode.
199
200
        :param value: The value of this node, must be a bytestring.
201
        """
202
        self.value = value
203
204
    @classmethod
205
    def deserialise(klass, bytes):
206
        """Get a ValueNode from a serialised bytestring.
207
        
208
        :param bytes: The bytes returned by an earlier serialisation.
209
        """
210
        if not bytes.startswith("chkvalue:\n"):
211
            raise ValueError("not a chkvalue %r" % bytes)
212
        return ValueNode(bytes[10:])
213
214
    def serialise(self):
215
        """Flatten the value to a bytestring.
216
217
        :return: A bytestring.
218
        """
219
        return "chkvalue:\n" + self.value