dmg-builder 26.0.17 → 26.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/dmg.d.ts +33 -1
- package/out/dmg.js +7 -170
- package/out/dmg.js.map +1 -1
- package/out/dmgUtil.d.ts +16 -2
- package/out/dmgUtil.js +152 -1
- package/out/dmgUtil.js.map +1 -1
- package/out/hdiuil.js +1 -1
- package/out/hdiuil.js.map +1 -1
- package/package.json +3 -4
- package/vendor/biplist/__init__.py +75 -75
- package/vendor/dmgbuild/__init__.py +5 -0
- package/vendor/dmgbuild/__main__.py +67 -0
- package/vendor/dmgbuild/badge.py +104 -78
- package/vendor/dmgbuild/colors.py +239 -229
- package/vendor/dmgbuild/core.py +945 -274
- package/vendor/dmgbuild/licensing.py +329 -0
- package/vendor/dmgbuild/resources/builtin-arrow.tiff +0 -0
- package/vendor/ds_store/__init__.py +3 -1
- package/vendor/ds_store/__main__.py +102 -0
- package/vendor/ds_store/buddy.py +142 -134
- package/vendor/ds_store/store.py +333 -322
- package/vendor/mac_alias/__init__.py +43 -23
- package/vendor/mac_alias/alias.py +334 -224
- package/vendor/mac_alias/bookmark.py +283 -278
- package/vendor/mac_alias/osx.py +406 -302
- package/vendor/mac_alias/utils.py +10 -8
- package/vendor/run_dmgbuild.py +14 -0
package/vendor/ds_store/store.py
CHANGED
|
@@ -1,48 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
from __future__ import unicode_literals
|
|
3
|
-
from __future__ import print_function
|
|
4
|
-
from __future__ import division
|
|
5
|
-
|
|
6
|
-
import binascii
|
|
1
|
+
import plistlib
|
|
7
2
|
import struct
|
|
8
|
-
import biplist
|
|
9
|
-
import mac_alias
|
|
10
3
|
|
|
11
|
-
|
|
12
|
-
next
|
|
13
|
-
except NameError:
|
|
14
|
-
next = lambda x: x.next()
|
|
15
|
-
try:
|
|
16
|
-
unicode
|
|
17
|
-
except NameError:
|
|
18
|
-
unicode = str
|
|
4
|
+
import mac_alias
|
|
19
5
|
|
|
20
6
|
from . import buddy
|
|
21
7
|
|
|
22
|
-
|
|
8
|
+
|
|
9
|
+
class ILocCodec:
|
|
23
10
|
@staticmethod
|
|
24
11
|
def encode(point):
|
|
25
|
-
return struct.pack(b
|
|
26
|
-
0xffffffff, 0xffff0000)
|
|
12
|
+
return struct.pack(b">IIII", point[0], point[1], 0xFFFFFFFF, 0xFFFF0000)
|
|
27
13
|
|
|
28
14
|
@staticmethod
|
|
29
15
|
def decode(bytesData):
|
|
30
16
|
if isinstance(bytesData, bytearray):
|
|
31
|
-
x, y = struct.unpack_from(b
|
|
17
|
+
x, y = struct.unpack_from(b">II", bytes(bytesData[:8]))
|
|
32
18
|
else:
|
|
33
|
-
x, y = struct.unpack(b
|
|
19
|
+
x, y = struct.unpack(b">II", bytesData[:8])
|
|
34
20
|
return (x, y)
|
|
35
21
|
|
|
36
|
-
|
|
22
|
+
|
|
23
|
+
class PlistCodec:
|
|
37
24
|
@staticmethod
|
|
38
25
|
def encode(plist):
|
|
39
|
-
return
|
|
26
|
+
return plistlib.dumps(plist, fmt=plistlib.FMT_BINARY)
|
|
40
27
|
|
|
41
28
|
@staticmethod
|
|
42
29
|
def decode(bytes):
|
|
43
|
-
return
|
|
30
|
+
return plistlib.loads(bytes)
|
|
44
31
|
|
|
45
|
-
|
|
32
|
+
|
|
33
|
+
class BookmarkCodec:
|
|
46
34
|
@staticmethod
|
|
47
35
|
def encode(bmk):
|
|
48
36
|
return bmk.to_bytes()
|
|
@@ -51,70 +39,73 @@ class BookmarkCodec(object):
|
|
|
51
39
|
def decode(bytes):
|
|
52
40
|
return mac_alias.Bookmark.from_bytes(bytes)
|
|
53
41
|
|
|
42
|
+
|
|
54
43
|
# This list tells the code how to decode particular kinds of entry in the
|
|
55
44
|
# .DS_Store file. This is really a convenience, and we currently only
|
|
56
45
|
# support a tiny subset of the possible entry types.
|
|
57
46
|
codecs = {
|
|
58
|
-
b
|
|
59
|
-
b
|
|
60
|
-
b
|
|
61
|
-
b
|
|
62
|
-
b
|
|
63
|
-
b
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
47
|
+
b"Iloc": ILocCodec,
|
|
48
|
+
b"bwsp": PlistCodec,
|
|
49
|
+
b"lsvp": PlistCodec,
|
|
50
|
+
b"lsvP": PlistCodec,
|
|
51
|
+
b"icvp": PlistCodec,
|
|
52
|
+
b"pBBk": BookmarkCodec,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class DSStoreEntry:
|
|
67
57
|
"""Holds the data from an entry in a ``.DS_Store`` file. Note that this is
|
|
68
|
-
not meant to represent the entry itself---i.e. if you change the type
|
|
69
|
-
|
|
58
|
+
not meant to represent the entry itself---i.e. if you change the type or
|
|
59
|
+
value, your changes will *not* be reflected in the underlying file.
|
|
70
60
|
|
|
71
|
-
If you want to make a change, you should either use the
|
|
72
|
-
object's :meth:`DSStore.insert` method (which will
|
|
73
|
-
already exists), or the mapping access mode for
|
|
74
|
-
simpler anyway).
|
|
61
|
+
If you want to make a change, you should either use the
|
|
62
|
+
:class:`DSStore` object's :meth:`DSStore.insert` method (which will
|
|
63
|
+
replace a key if it already exists), or the mapping access mode for
|
|
64
|
+
:class:`DSStore` (often simpler anyway).
|
|
75
65
|
"""
|
|
66
|
+
|
|
76
67
|
def __init__(self, filename, code, typecode, value=None):
|
|
77
68
|
if str != bytes and type(filename) == bytes:
|
|
78
|
-
filename = filename.decode(
|
|
69
|
+
filename = filename.decode("utf-8")
|
|
79
70
|
|
|
80
71
|
if not isinstance(code, bytes):
|
|
81
|
-
code = code.encode(
|
|
72
|
+
code = code.encode("latin_1")
|
|
82
73
|
|
|
83
74
|
self.filename = filename
|
|
84
75
|
self.code = code
|
|
85
76
|
self.type = typecode
|
|
86
77
|
self.value = value
|
|
87
|
-
|
|
78
|
+
|
|
88
79
|
@classmethod
|
|
89
80
|
def read(cls, block):
|
|
90
|
-
"""Read a ``.DS_Store`` entry from the containing Block"""
|
|
81
|
+
"""Read a ``.DS_Store`` entry from the containing Block."""
|
|
91
82
|
# First read the filename
|
|
92
|
-
nlen = block.read(b
|
|
93
|
-
filename = block.read(2 * nlen).decode(
|
|
83
|
+
nlen = block.read(b">I")[0]
|
|
84
|
+
filename = block.read(2 * nlen).decode("utf-16be")
|
|
94
85
|
|
|
95
86
|
# Next, read the code and type
|
|
96
|
-
code, typecode = block.read(b
|
|
87
|
+
code, typecode = block.read(b">4s4s")
|
|
97
88
|
|
|
98
89
|
# Finally, read the data
|
|
99
|
-
if typecode == b
|
|
100
|
-
value = block.read(b
|
|
101
|
-
elif typecode == b
|
|
102
|
-
value = block.read(b
|
|
103
|
-
elif typecode == b
|
|
104
|
-
vlen = block.read(b
|
|
90
|
+
if typecode == b"bool":
|
|
91
|
+
value = block.read(b">?")[0]
|
|
92
|
+
elif typecode == b"long" or typecode == b"shor":
|
|
93
|
+
value = block.read(b">I")[0]
|
|
94
|
+
elif typecode == b"blob":
|
|
95
|
+
vlen = block.read(b">I")[0]
|
|
105
96
|
value = block.read(vlen)
|
|
106
97
|
|
|
107
98
|
codec = codecs.get(code, None)
|
|
108
99
|
if codec:
|
|
109
100
|
value = codec.decode(value)
|
|
110
101
|
typecode = codec
|
|
111
|
-
elif typecode == b
|
|
112
|
-
vlen = block.read(b
|
|
113
|
-
value = block.read(2 * vlen).decode(
|
|
114
|
-
elif typecode == b
|
|
115
|
-
value = block.read(b
|
|
116
|
-
elif typecode == b
|
|
117
|
-
value = block.read(b
|
|
102
|
+
elif typecode == b"ustr":
|
|
103
|
+
vlen = block.read(b">I")[0]
|
|
104
|
+
value = block.read(2 * vlen).decode("utf-16be")
|
|
105
|
+
elif typecode == b"type":
|
|
106
|
+
value = block.read(b">4s")[0]
|
|
107
|
+
elif typecode == b"comp" or typecode == b"dutc":
|
|
108
|
+
value = block.read(b">Q")[0]
|
|
118
109
|
else:
|
|
119
110
|
raise ValueError('Unknown type code "%s"' % typecode)
|
|
120
111
|
|
|
@@ -122,152 +113,138 @@ class DSStoreEntry(object):
|
|
|
122
113
|
|
|
123
114
|
def __lt__(self, other):
|
|
124
115
|
if not isinstance(other, DSStoreEntry):
|
|
125
|
-
raise TypeError(
|
|
116
|
+
raise TypeError("Can only compare against other DSStoreEntry objects")
|
|
126
117
|
sfl = self.filename.lower()
|
|
127
118
|
ofl = other.filename.lower()
|
|
128
|
-
return
|
|
129
|
-
or (self.filename == other.filename
|
|
130
|
-
and self.code < other.code))
|
|
119
|
+
return sfl < ofl or (self.filename == other.filename and self.code < other.code)
|
|
131
120
|
|
|
132
121
|
def __le__(self, other):
|
|
133
122
|
if not isinstance(other, DSStoreEntry):
|
|
134
|
-
raise TypeError(
|
|
123
|
+
raise TypeError("Can only compare against other DSStoreEntry objects")
|
|
135
124
|
sfl = self.filename.lower()
|
|
136
125
|
ofl = other.filename.lower()
|
|
137
|
-
return
|
|
138
|
-
or (sfl == ofl
|
|
139
|
-
and self.code <= other.code))
|
|
126
|
+
return sfl < ofl or (sfl == ofl and self.code <= other.code)
|
|
140
127
|
|
|
141
128
|
def __eq__(self, other):
|
|
142
129
|
if not isinstance(other, DSStoreEntry):
|
|
143
|
-
raise TypeError(
|
|
130
|
+
raise TypeError("Can only compare against other DSStoreEntry objects")
|
|
144
131
|
sfl = self.filename.lower()
|
|
145
132
|
ofl = other.filename.lower()
|
|
146
|
-
return
|
|
147
|
-
and self.code == other.code)
|
|
133
|
+
return sfl == ofl and self.code == other.code
|
|
148
134
|
|
|
149
135
|
def __ne__(self, other):
|
|
150
136
|
if not isinstance(other, DSStoreEntry):
|
|
151
|
-
raise TypeError(
|
|
137
|
+
raise TypeError("Can only compare against other DSStoreEntry objects")
|
|
152
138
|
sfl = self.filename.lower()
|
|
153
139
|
ofl = other.filename.lower()
|
|
154
|
-
return
|
|
155
|
-
or self.code != other.code)
|
|
140
|
+
return sfl != ofl or self.code != other.code
|
|
156
141
|
|
|
157
142
|
def __gt__(self, other):
|
|
158
143
|
if not isinstance(other, DSStoreEntry):
|
|
159
|
-
raise TypeError(
|
|
144
|
+
raise TypeError("Can only compare against other DSStoreEntry objects")
|
|
160
145
|
sfl = self.filename.lower()
|
|
161
146
|
ofl = other.filename.lower()
|
|
162
|
-
|
|
147
|
+
|
|
163
148
|
selfCode = self.code
|
|
164
149
|
if str != bytes and type(selfCode) is bytes:
|
|
165
|
-
selfCode = selfCode.decode(
|
|
150
|
+
selfCode = selfCode.decode("utf-8")
|
|
166
151
|
otherCode = other.code
|
|
167
152
|
if str != bytes and type(otherCode) is bytes:
|
|
168
|
-
otherCode = otherCode.decode(
|
|
169
|
-
|
|
170
|
-
return
|
|
153
|
+
otherCode = otherCode.decode("utf-8")
|
|
154
|
+
|
|
155
|
+
return sfl > ofl or (sfl == ofl and selfCode > otherCode)
|
|
171
156
|
|
|
172
157
|
def __ge__(self, other):
|
|
173
158
|
if not isinstance(other, DSStoreEntry):
|
|
174
|
-
raise TypeError(
|
|
159
|
+
raise TypeError("Can only compare against other DSStoreEntry objects")
|
|
175
160
|
sfl = self.filename.lower()
|
|
176
161
|
ofl = other.filename.lower()
|
|
177
|
-
return
|
|
178
|
-
or (sfl == ofl
|
|
179
|
-
and self.code >= other.code))
|
|
162
|
+
return sfl > ofl or (sfl == ofl and self.code >= other.code)
|
|
180
163
|
|
|
181
|
-
def __cmp__(self, other):
|
|
182
|
-
if not isinstance(other, DSStoreEntry):
|
|
183
|
-
raise TypeError('Can only compare against other DSStoreEntry objects')
|
|
184
|
-
r = cmp(self.filename.lower(), other.filename.lower())
|
|
185
|
-
if r:
|
|
186
|
-
return r
|
|
187
|
-
return cmp(self.code, other.code)
|
|
188
|
-
|
|
189
164
|
def byte_length(self):
|
|
190
|
-
"""Compute the length of this entry, in bytes"""
|
|
191
|
-
utf16 = self.filename.encode(
|
|
192
|
-
|
|
165
|
+
"""Compute the length of this entry, in bytes."""
|
|
166
|
+
utf16 = self.filename.encode("utf-16be")
|
|
167
|
+
length = 4 + len(utf16) + 8
|
|
193
168
|
|
|
194
|
-
if isinstance(self.type,
|
|
195
|
-
entry_type = self.type.encode(
|
|
169
|
+
if isinstance(self.type, str):
|
|
170
|
+
entry_type = self.type.encode("latin_1")
|
|
196
171
|
value = self.value
|
|
197
172
|
elif isinstance(self.type, (bytes, str)):
|
|
198
173
|
entry_type = self.type
|
|
199
174
|
value = self.value
|
|
200
175
|
else:
|
|
201
|
-
entry_type = b
|
|
176
|
+
entry_type = b"blob"
|
|
202
177
|
value = self.type.encode(self.value)
|
|
203
|
-
|
|
204
|
-
if entry_type == b
|
|
205
|
-
|
|
206
|
-
elif entry_type == b
|
|
207
|
-
|
|
208
|
-
elif entry_type == b
|
|
209
|
-
|
|
210
|
-
elif entry_type == b
|
|
211
|
-
utf16 = value.encode(
|
|
212
|
-
|
|
213
|
-
elif entry_type == b
|
|
214
|
-
|
|
215
|
-
elif entry_type == b
|
|
216
|
-
|
|
178
|
+
|
|
179
|
+
if entry_type == b"bool":
|
|
180
|
+
length += 1
|
|
181
|
+
elif entry_type == b"long" or entry_type == b"shor":
|
|
182
|
+
length += 4
|
|
183
|
+
elif entry_type == b"blob":
|
|
184
|
+
length += 4 + len(value)
|
|
185
|
+
elif entry_type == b"ustr":
|
|
186
|
+
utf16 = value.encode("utf-16be")
|
|
187
|
+
length += 4 + len(utf16)
|
|
188
|
+
elif entry_type == b"type":
|
|
189
|
+
length += 4
|
|
190
|
+
elif entry_type == b"comp" or entry_type == b"dutc":
|
|
191
|
+
length += 8
|
|
217
192
|
else:
|
|
218
193
|
raise ValueError('Unknown type code "%s"' % entry_type)
|
|
219
194
|
|
|
220
|
-
return
|
|
221
|
-
|
|
195
|
+
return length
|
|
196
|
+
|
|
222
197
|
def write(self, block, insert=False):
|
|
223
|
-
"""Write this entry to the specified Block"""
|
|
198
|
+
"""Write this entry to the specified Block."""
|
|
224
199
|
if insert:
|
|
225
200
|
w = block.insert
|
|
226
201
|
else:
|
|
227
202
|
w = block.write
|
|
228
203
|
|
|
229
|
-
if isinstance(self.type,
|
|
230
|
-
entry_type = self.type.encode(
|
|
204
|
+
if isinstance(self.type, str):
|
|
205
|
+
entry_type = self.type.encode("latin_1")
|
|
231
206
|
value = self.value
|
|
232
207
|
elif isinstance(self.type, (bytes, str)):
|
|
233
208
|
entry_type = self.type
|
|
234
209
|
value = self.value
|
|
235
210
|
else:
|
|
236
|
-
entry_type = b
|
|
211
|
+
entry_type = b"blob"
|
|
237
212
|
value = self.type.encode(self.value)
|
|
238
213
|
|
|
239
|
-
utf16 = self.filename.encode(
|
|
240
|
-
w(b
|
|
214
|
+
utf16 = self.filename.encode("utf-16be")
|
|
215
|
+
w(b">I", len(utf16) // 2)
|
|
241
216
|
w(utf16)
|
|
242
|
-
w(b
|
|
243
|
-
|
|
244
|
-
if entry_type == b
|
|
245
|
-
w(b
|
|
246
|
-
elif entry_type == b
|
|
247
|
-
w(b
|
|
248
|
-
elif entry_type == b
|
|
249
|
-
w(b
|
|
217
|
+
w(b">4s4s", self.code, entry_type)
|
|
218
|
+
|
|
219
|
+
if entry_type == b"bool":
|
|
220
|
+
w(b">?", value)
|
|
221
|
+
elif entry_type == b"long" or entry_type == b"shor":
|
|
222
|
+
w(b">I", value)
|
|
223
|
+
elif entry_type == b"blob":
|
|
224
|
+
w(b">I", len(value))
|
|
250
225
|
w(value)
|
|
251
|
-
elif entry_type == b
|
|
252
|
-
utf16 = value.encode(
|
|
253
|
-
w(b
|
|
226
|
+
elif entry_type == b"ustr":
|
|
227
|
+
utf16 = value.encode("utf-16be")
|
|
228
|
+
w(b">I", len(utf16) // 2)
|
|
254
229
|
w(utf16)
|
|
255
|
-
elif entry_type == b
|
|
256
|
-
if isinstance(value,
|
|
257
|
-
value = value.encode(
|
|
258
|
-
w(b
|
|
259
|
-
elif entry_type == b
|
|
260
|
-
w(b
|
|
230
|
+
elif entry_type == b"type":
|
|
231
|
+
if isinstance(value, str):
|
|
232
|
+
value = value.encode("latin_1")
|
|
233
|
+
w(b">4s", value)
|
|
234
|
+
elif entry_type == b"comp" or entry_type == b"dutc":
|
|
235
|
+
w(b">Q", value)
|
|
261
236
|
else:
|
|
262
237
|
raise ValueError('Unknown type code "%s"' % entry_type)
|
|
263
|
-
|
|
238
|
+
|
|
264
239
|
def __repr__(self):
|
|
265
|
-
return
|
|
240
|
+
return f"<{self.filename} {self.code}>"
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class DSStore:
|
|
244
|
+
"""Python interface to a ``.DS_Store`` file. Works by manipulating the
|
|
245
|
+
file on the disk---so this code will work with ``.DS_Store`` files for.
|
|
266
246
|
|
|
267
|
-
|
|
268
|
-
"""Python interface to a ``.DS_Store`` file. Works by manipulating the file
|
|
269
|
-
on the disk---so this code will work with ``.DS_Store`` files for *very*
|
|
270
|
-
large directories.
|
|
247
|
+
*very* large directories.
|
|
271
248
|
|
|
272
249
|
A :class:`DSStore` object can be used as if it was a mapping, e.g.::
|
|
273
250
|
|
|
@@ -280,7 +257,7 @@ class DSStore(object):
|
|
|
280
257
|
|
|
281
258
|
Currently, we know how to decode "Iloc", "bwsp", "lsvp", "lsvP" and "icvp"
|
|
282
259
|
blobs. "Iloc" decodes to an (x, y) tuple, while the others are all decoded
|
|
283
|
-
using ``biplist
|
|
260
|
+
using ``biplist`` or ``plistlib`` depending on Python version.
|
|
284
261
|
|
|
285
262
|
Assignment also works, e.g.::
|
|
286
263
|
|
|
@@ -292,38 +269,48 @@ class DSStore(object):
|
|
|
292
269
|
|
|
293
270
|
This is usually going to be the most convenient interface, though
|
|
294
271
|
occasionally (for instance when creating a new ``.DS_Store`` file) you
|
|
295
|
-
may wish to drop down to using :class:`DSStoreEntry` objects directly.
|
|
272
|
+
may wish to drop down to using :class:`DSStoreEntry` objects directly.
|
|
273
|
+
"""
|
|
274
|
+
|
|
296
275
|
def __init__(self, store):
|
|
297
276
|
self._store = store
|
|
298
|
-
self._superblk = self._store[
|
|
277
|
+
self._superblk = self._store["DSDB"]
|
|
299
278
|
with self._get_block(self._superblk) as s:
|
|
300
|
-
|
|
301
|
-
|
|
279
|
+
(
|
|
280
|
+
self._rootnode,
|
|
281
|
+
self._levels,
|
|
282
|
+
self._records,
|
|
283
|
+
self._nodes,
|
|
284
|
+
self._page_size,
|
|
285
|
+
) = s.read(b">IIIII")
|
|
302
286
|
self._min_usage = 2 * self._page_size // 3
|
|
303
287
|
self._dirty = False
|
|
304
|
-
|
|
288
|
+
|
|
305
289
|
@classmethod
|
|
306
|
-
def open(cls, file_or_name, mode=
|
|
290
|
+
def open(cls, file_or_name, mode="r+", initial_entries=None):
|
|
307
291
|
"""Open a ``.DS_Store`` file; pass either a Python file object, or a
|
|
308
|
-
filename in the ``file_or_name`` argument and a file access mode in
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
292
|
+
filename in the ``file_or_name`` argument and a file access mode in the
|
|
293
|
+
``mode`` argument.
|
|
294
|
+
|
|
295
|
+
If you are creating a new file using the "w" or "w+" modes, you
|
|
296
|
+
may also specify a list of entries with which to initialise the
|
|
297
|
+
file.
|
|
298
|
+
"""
|
|
312
299
|
store = buddy.Allocator.open(file_or_name, mode)
|
|
313
|
-
|
|
314
|
-
if mode ==
|
|
300
|
+
|
|
301
|
+
if mode == "w" or mode == "w+":
|
|
315
302
|
superblk = store.allocate(20)
|
|
316
|
-
store[
|
|
303
|
+
store["DSDB"] = superblk
|
|
317
304
|
page_size = 4096
|
|
318
|
-
|
|
305
|
+
|
|
319
306
|
if not initial_entries:
|
|
320
307
|
root = store.allocate(page_size)
|
|
321
|
-
|
|
308
|
+
|
|
322
309
|
with store.get_block(root) as rootblk:
|
|
323
310
|
rootblk.zero_fill()
|
|
324
311
|
|
|
325
312
|
with store.get_block(superblk) as s:
|
|
326
|
-
s.write(b
|
|
313
|
+
s.write(b">IIIII", root, 0, 0, 1, page_size)
|
|
327
314
|
else:
|
|
328
315
|
# Make sure they're in sorted order
|
|
329
316
|
initial_entries = list(initial_entries)
|
|
@@ -357,48 +344,54 @@ class DSStore(object):
|
|
|
357
344
|
|
|
358
345
|
if len(nodes) == 1:
|
|
359
346
|
break
|
|
360
|
-
|
|
347
|
+
|
|
361
348
|
current_level = next_level
|
|
362
349
|
next_level = []
|
|
363
350
|
ptr_size = 4
|
|
364
351
|
|
|
365
352
|
# Allocate nodes
|
|
366
353
|
ptrs = [store.allocate(page_size) for n in range(node_count)]
|
|
367
|
-
|
|
354
|
+
|
|
368
355
|
# Generate nodes
|
|
369
356
|
pointers = []
|
|
370
357
|
prev_pointers = None
|
|
371
358
|
for level in levels:
|
|
372
359
|
ppndx = 0
|
|
373
|
-
lptrs = ptrs[-len(level):]
|
|
374
|
-
del ptrs[-len(level):]
|
|
360
|
+
lptrs = ptrs[-len(level) :]
|
|
361
|
+
del ptrs[-len(level) :]
|
|
375
362
|
for node in level:
|
|
376
363
|
ndx = lptrs.pop(0)
|
|
377
364
|
if prev_pointers is None:
|
|
378
365
|
with store.get_block(ndx) as block:
|
|
379
|
-
block.write(b
|
|
366
|
+
block.write(b">II", 0, len(node))
|
|
380
367
|
for e in node:
|
|
381
368
|
e.write(block)
|
|
382
369
|
else:
|
|
383
370
|
next_node = prev_pointers[ppndx + len(node)]
|
|
384
|
-
node_ptrs = prev_pointers[ppndx:ppndx+len(node)]
|
|
385
|
-
|
|
371
|
+
node_ptrs = prev_pointers[ppndx : ppndx + len(node)]
|
|
372
|
+
|
|
386
373
|
with store.get_block(ndx) as block:
|
|
387
|
-
block.write(b
|
|
374
|
+
block.write(b">II", next_node, len(node))
|
|
388
375
|
for ptr, e in zip(node_ptrs, node):
|
|
389
|
-
block.write(b
|
|
376
|
+
block.write(b">I", ptr)
|
|
390
377
|
e.write(block)
|
|
391
|
-
|
|
378
|
+
|
|
392
379
|
pointers.append(ndx)
|
|
393
380
|
prev_pointers = pointers
|
|
394
381
|
pointers = []
|
|
395
|
-
|
|
382
|
+
|
|
396
383
|
root = prev_pointers[0]
|
|
397
384
|
|
|
398
385
|
with store.get_block(superblk) as s:
|
|
399
|
-
s.write(
|
|
400
|
-
|
|
401
|
-
|
|
386
|
+
s.write(
|
|
387
|
+
b">IIIII",
|
|
388
|
+
root,
|
|
389
|
+
len(levels),
|
|
390
|
+
len(initial_entries),
|
|
391
|
+
node_count,
|
|
392
|
+
page_size,
|
|
393
|
+
)
|
|
394
|
+
|
|
402
395
|
return DSStore(store)
|
|
403
396
|
|
|
404
397
|
def _get_block(self, number):
|
|
@@ -410,25 +403,31 @@ class DSStore(object):
|
|
|
410
403
|
self._dirty = False
|
|
411
404
|
|
|
412
405
|
with self._get_block(self._superblk) as s:
|
|
413
|
-
s.write(
|
|
414
|
-
|
|
406
|
+
s.write(
|
|
407
|
+
b">IIIII",
|
|
408
|
+
self._rootnode,
|
|
409
|
+
self._levels,
|
|
410
|
+
self._records,
|
|
411
|
+
self._nodes,
|
|
412
|
+
self._page_size,
|
|
413
|
+
)
|
|
415
414
|
self._store.flush()
|
|
416
415
|
|
|
417
416
|
def close(self):
|
|
418
417
|
"""Flush dirty data and close the underlying file."""
|
|
419
418
|
self.flush()
|
|
420
419
|
self._store.close()
|
|
421
|
-
|
|
420
|
+
|
|
422
421
|
def __enter__(self):
|
|
423
422
|
return self
|
|
424
423
|
|
|
425
424
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
426
425
|
self.close()
|
|
427
|
-
|
|
426
|
+
|
|
428
427
|
# Internal B-Tree nodes look like this:
|
|
429
428
|
#
|
|
430
429
|
# [ next | count | (ptr0 | rec0) | (ptr1 | rec1) ... (ptrN | recN) ]
|
|
431
|
-
|
|
430
|
+
|
|
432
431
|
# Leaf nodes look like this:
|
|
433
432
|
#
|
|
434
433
|
# [ 0 | count | rec0 | rec1 ... recN ]
|
|
@@ -438,10 +437,10 @@ class DSStore(object):
|
|
|
438
437
|
if node is None:
|
|
439
438
|
node = self._rootnode
|
|
440
439
|
with self._get_block(node) as block:
|
|
441
|
-
next_node, count = block.read(b
|
|
440
|
+
next_node, count = block.read(b">II")
|
|
442
441
|
if next_node:
|
|
443
442
|
for n in range(count):
|
|
444
|
-
ptr = block.read(b
|
|
443
|
+
ptr = block.read(b">I")[0]
|
|
445
444
|
for e in self._traverse(ptr):
|
|
446
445
|
yield e
|
|
447
446
|
e = DSStoreEntry.read(block)
|
|
@@ -456,23 +455,30 @@ class DSStore(object):
|
|
|
456
455
|
# Display the data in `node'
|
|
457
456
|
def _dump_node(self, node):
|
|
458
457
|
with self._get_block(node) as block:
|
|
459
|
-
next_node, count = block.read(b
|
|
460
|
-
print(
|
|
458
|
+
next_node, count = block.read(b">II")
|
|
459
|
+
print("next: %u\ncount: %u\n" % (next_node, count))
|
|
461
460
|
for n in range(count):
|
|
462
461
|
if next_node:
|
|
463
|
-
ptr = block.read(b
|
|
464
|
-
print(
|
|
462
|
+
ptr = block.read(b">I")[0]
|
|
463
|
+
print("%8u " % ptr, end=" ")
|
|
465
464
|
else:
|
|
466
|
-
print(
|
|
465
|
+
print(" ", end=" ")
|
|
467
466
|
e = DSStoreEntry.read(block)
|
|
468
|
-
print(e,
|
|
469
|
-
print(
|
|
467
|
+
print(e, " (%u)" % e.byte_length())
|
|
468
|
+
print("used: %u" % block.tell())
|
|
470
469
|
|
|
471
470
|
# Display the data in the super block
|
|
472
471
|
def _dump_super(self):
|
|
473
|
-
print(
|
|
474
|
-
|
|
475
|
-
|
|
472
|
+
print(
|
|
473
|
+
"root: %u\nlevels: %u\nrecords: %u\nnodes: %u\npage-size: %u"
|
|
474
|
+
% (
|
|
475
|
+
self._rootnode,
|
|
476
|
+
self._levels,
|
|
477
|
+
self._records,
|
|
478
|
+
self._nodes,
|
|
479
|
+
self._page_size,
|
|
480
|
+
)
|
|
481
|
+
)
|
|
476
482
|
|
|
477
483
|
# Splits entries across two blocks, returning one pivot
|
|
478
484
|
#
|
|
@@ -480,9 +486,9 @@ class DSStore(object):
|
|
|
480
486
|
def _split2(self, blocks, entries, pointers, before, internal):
|
|
481
487
|
left_block = blocks[0]
|
|
482
488
|
right_block = blocks[1]
|
|
483
|
-
|
|
489
|
+
|
|
484
490
|
count = len(entries)
|
|
485
|
-
|
|
491
|
+
|
|
486
492
|
# Find the feasible splits
|
|
487
493
|
best_split = None
|
|
488
494
|
best_diff = None
|
|
@@ -492,7 +498,7 @@ class DSStore(object):
|
|
|
492
498
|
# We can use a *single* node for this
|
|
493
499
|
best_split = count
|
|
494
500
|
else:
|
|
495
|
-
# Split into two nodes
|
|
501
|
+
# Split into two nodes
|
|
496
502
|
for n in range(1, count - 1):
|
|
497
503
|
left_size = 8 + before[n]
|
|
498
504
|
right_size = 8 + total - before[n + 1]
|
|
@@ -501,7 +507,7 @@ class DSStore(object):
|
|
|
501
507
|
break
|
|
502
508
|
if right_size > self._page_size:
|
|
503
509
|
continue
|
|
504
|
-
|
|
510
|
+
|
|
505
511
|
diff = abs(left_size - right_size)
|
|
506
512
|
|
|
507
513
|
if best_split is None or diff < best_diff:
|
|
@@ -510,54 +516,53 @@ class DSStore(object):
|
|
|
510
516
|
|
|
511
517
|
if best_split is None:
|
|
512
518
|
return None
|
|
513
|
-
|
|
519
|
+
|
|
514
520
|
# Write the nodes
|
|
515
521
|
left_block.seek(0)
|
|
516
522
|
if internal:
|
|
517
523
|
next_node = pointers[best_split]
|
|
518
524
|
else:
|
|
519
525
|
next_node = 0
|
|
520
|
-
left_block.write(b
|
|
526
|
+
left_block.write(b">II", next_node, best_split)
|
|
521
527
|
|
|
522
528
|
for n in range(best_split):
|
|
523
529
|
if internal:
|
|
524
|
-
left_block.write(b
|
|
530
|
+
left_block.write(b">I", pointers[n])
|
|
525
531
|
entries[n].write(left_block)
|
|
526
532
|
|
|
527
533
|
left_block.zero_fill()
|
|
528
534
|
|
|
529
535
|
if best_split == count:
|
|
530
536
|
return []
|
|
531
|
-
|
|
537
|
+
|
|
532
538
|
right_block.seek(0)
|
|
533
539
|
if internal:
|
|
534
540
|
next_node = pointers[count]
|
|
535
541
|
else:
|
|
536
542
|
next_node = 0
|
|
537
|
-
right_block.write(b
|
|
543
|
+
right_block.write(b">II", next_node, count - best_split - 1)
|
|
538
544
|
|
|
539
545
|
for n in range(best_split + 1, count):
|
|
540
546
|
if internal:
|
|
541
|
-
right_block.write(b
|
|
547
|
+
right_block.write(b">I", pointers[n])
|
|
542
548
|
entries[n].write(right_block)
|
|
543
549
|
|
|
544
550
|
right_block.zero_fill()
|
|
545
|
-
|
|
551
|
+
|
|
546
552
|
pivot = entries[best_split]
|
|
547
553
|
|
|
548
554
|
return [pivot]
|
|
549
|
-
|
|
555
|
+
|
|
550
556
|
def _split(self, node, entry, right_ptr=0):
|
|
551
557
|
self._nodes += 1
|
|
552
558
|
self._dirty = True
|
|
553
559
|
new_right = self._store.allocate(self._page_size)
|
|
554
|
-
with self._get_block(node) as block,
|
|
555
|
-
self._get_block(new_right) as right_block:
|
|
560
|
+
with self._get_block(node) as block, self._get_block(new_right) as right_block:
|
|
556
561
|
|
|
557
562
|
# First, measure and extract all the elements
|
|
558
563
|
entry_size = entry.byte_length()
|
|
559
|
-
entry_pos = None
|
|
560
|
-
next_node, count = block.read(b
|
|
564
|
+
# ?? entry_pos = None
|
|
565
|
+
next_node, count = block.read(b">II")
|
|
561
566
|
if next_node:
|
|
562
567
|
entry_size += 4
|
|
563
568
|
pointers = []
|
|
@@ -567,11 +572,11 @@ class DSStore(object):
|
|
|
567
572
|
for n in range(count):
|
|
568
573
|
pos = block.tell()
|
|
569
574
|
if next_node:
|
|
570
|
-
ptr = block.read(b
|
|
575
|
+
ptr = block.read(b">I")[0]
|
|
571
576
|
pointers.append(ptr)
|
|
572
577
|
e = DSStoreEntry.read(block)
|
|
573
578
|
if e > entry:
|
|
574
|
-
entry_pos = n
|
|
579
|
+
# ?? entry_pos = n
|
|
575
580
|
entries.append(entry)
|
|
576
581
|
pointers.append(right_ptr)
|
|
577
582
|
before.append(total)
|
|
@@ -583,10 +588,10 @@ class DSStore(object):
|
|
|
583
588
|
if next_node:
|
|
584
589
|
pointers.append(next_node)
|
|
585
590
|
|
|
586
|
-
pivot = self._split2(
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
591
|
+
pivot = self._split2(
|
|
592
|
+
[block, right_block], entries, pointers, before, bool(next_node)
|
|
593
|
+
)[0]
|
|
594
|
+
|
|
590
595
|
self._records += 1
|
|
591
596
|
self._nodes += 1
|
|
592
597
|
self._dirty = True
|
|
@@ -598,7 +603,7 @@ class DSStore(object):
|
|
|
598
603
|
def _new_root(self, left, pivot, right):
|
|
599
604
|
new_root = self._store.allocate(self._page_size)
|
|
600
605
|
with self._get_block(new_root) as block:
|
|
601
|
-
block.write(b
|
|
606
|
+
block.write(b">III", right, 1, left)
|
|
602
607
|
pivot.write(block)
|
|
603
608
|
self._rootnode = new_root
|
|
604
609
|
self._levels += 1
|
|
@@ -610,21 +615,21 @@ class DSStore(object):
|
|
|
610
615
|
# pointer (inserted to the RIGHT of `entry')
|
|
611
616
|
def _insert_inner(self, path, node, entry, right_ptr):
|
|
612
617
|
with self._get_block(node) as block:
|
|
613
|
-
next_node, count = block.read(b
|
|
618
|
+
next_node, count = block.read(b">II")
|
|
614
619
|
insert_pos = None
|
|
615
620
|
insert_ndx = None
|
|
616
621
|
n = 0
|
|
617
622
|
while n < count:
|
|
618
623
|
pos = block.tell()
|
|
619
|
-
ptr = block.read(b
|
|
624
|
+
ptr = block.read(b">I")[0]
|
|
620
625
|
e = DSStoreEntry.read(block)
|
|
621
626
|
if e == entry:
|
|
622
627
|
if n == count - 1:
|
|
623
628
|
right_ptr = next_node
|
|
624
629
|
next_node = ptr
|
|
625
|
-
|
|
630
|
+
block.seek(pos)
|
|
626
631
|
else:
|
|
627
|
-
right_ptr = block.read(b
|
|
632
|
+
right_ptr = block.read(b">I")[0]
|
|
628
633
|
block.seek(pos + 4)
|
|
629
634
|
insert_pos = pos
|
|
630
635
|
insert_ndx = n
|
|
@@ -651,32 +656,32 @@ class DSStore(object):
|
|
|
651
656
|
else:
|
|
652
657
|
if insert_ndx == count:
|
|
653
658
|
block.seek(insert_pos)
|
|
654
|
-
block.write(b
|
|
659
|
+
block.write(b">I", next_node)
|
|
655
660
|
entry.write(block)
|
|
656
661
|
next_node = right_ptr
|
|
657
662
|
else:
|
|
658
663
|
block.seek(insert_pos + 4)
|
|
659
664
|
entry.write(block, True)
|
|
660
|
-
block.insert(
|
|
665
|
+
block.insert(">I", right_ptr)
|
|
661
666
|
block.seek(0)
|
|
662
667
|
count += 1
|
|
663
|
-
block.write(b
|
|
668
|
+
block.write(b">II", next_node, count)
|
|
664
669
|
self._records += 1
|
|
665
670
|
self._dirty = True
|
|
666
671
|
|
|
667
672
|
# Insert `entry' into the leaf node `node'
|
|
668
673
|
def _insert_leaf(self, path, node, entry):
|
|
669
674
|
with self._get_block(node) as block:
|
|
670
|
-
next_node, count = block.read(b
|
|
675
|
+
next_node, count = block.read(b">II")
|
|
671
676
|
insert_pos = None
|
|
672
|
-
insert_ndx = None
|
|
677
|
+
# ?? insert_ndx = None
|
|
673
678
|
n = 0
|
|
674
679
|
while n < count:
|
|
675
680
|
pos = block.tell()
|
|
676
681
|
e = DSStoreEntry.read(block)
|
|
677
682
|
if e == entry:
|
|
678
683
|
insert_pos = pos
|
|
679
|
-
insert_ndx = n
|
|
684
|
+
# ?? insert_ndx = n
|
|
680
685
|
block.seek(pos)
|
|
681
686
|
block.delete(e.byte_length())
|
|
682
687
|
count -= 1
|
|
@@ -685,11 +690,11 @@ class DSStore(object):
|
|
|
685
690
|
continue
|
|
686
691
|
elif insert_pos is None and e > entry:
|
|
687
692
|
insert_pos = pos
|
|
688
|
-
insert_ndx = n
|
|
693
|
+
# ?? insert_ndx = n
|
|
689
694
|
n += 1
|
|
690
695
|
if insert_pos is None:
|
|
691
696
|
insert_pos = block.tell()
|
|
692
|
-
insert_ndx = count
|
|
697
|
+
# ?? insert_ndx = count
|
|
693
698
|
remaining = self._page_size - block.tell()
|
|
694
699
|
|
|
695
700
|
if remaining < entry.byte_length():
|
|
@@ -703,21 +708,21 @@ class DSStore(object):
|
|
|
703
708
|
entry.write(block, True)
|
|
704
709
|
block.seek(0)
|
|
705
710
|
count += 1
|
|
706
|
-
block.write(b
|
|
711
|
+
block.write(b">II", next_node, count)
|
|
707
712
|
self._records += 1
|
|
708
713
|
self._dirty = True
|
|
709
714
|
|
|
710
715
|
def insert(self, entry):
|
|
711
|
-
"""Insert ``entry`` (which should be a :class:`DSStoreEntry`)
|
|
712
|
-
|
|
716
|
+
"""Insert ``entry`` (which should be a :class:`DSStoreEntry`) into the
|
|
717
|
+
B-Tree."""
|
|
713
718
|
path = []
|
|
714
719
|
node = self._rootnode
|
|
715
720
|
while True:
|
|
716
721
|
with self._get_block(node) as block:
|
|
717
|
-
next_node, count = block.read(b
|
|
722
|
+
next_node, count = block.read(b">II")
|
|
718
723
|
if next_node:
|
|
719
724
|
for n in range(count):
|
|
720
|
-
ptr = block.read(b
|
|
725
|
+
ptr = block.read(b">I")[0]
|
|
721
726
|
e = DSStoreEntry.read(block)
|
|
722
727
|
if entry < e:
|
|
723
728
|
next_node = ptr
|
|
@@ -735,12 +740,12 @@ class DSStore(object):
|
|
|
735
740
|
# Return usage information for the specified `node'
|
|
736
741
|
def _block_usage(self, node):
|
|
737
742
|
with self._get_block(node) as block:
|
|
738
|
-
next_node, count = block.read(b
|
|
743
|
+
next_node, count = block.read(b">II")
|
|
739
744
|
|
|
740
745
|
for n in range(count):
|
|
741
746
|
if next_node:
|
|
742
|
-
|
|
743
|
-
|
|
747
|
+
block.read(b">I")[0]
|
|
748
|
+
DSStoreEntry.read(block)
|
|
744
749
|
|
|
745
750
|
used = block.tell()
|
|
746
751
|
|
|
@@ -773,14 +778,14 @@ class DSStore(object):
|
|
|
773
778
|
continue
|
|
774
779
|
|
|
775
780
|
diff = abs(left_size - mid_size) * abs(right_size - mid_size)
|
|
776
|
-
|
|
781
|
+
|
|
777
782
|
if best_split is None or diff < best_diff:
|
|
778
783
|
best_split = (n, m, count)
|
|
779
784
|
best_diff = diff
|
|
780
785
|
|
|
781
786
|
if best_split is None:
|
|
782
787
|
return None
|
|
783
|
-
|
|
788
|
+
|
|
784
789
|
# Write the nodes
|
|
785
790
|
prev_split = -1
|
|
786
791
|
for block, split in zip(blocks, best_split):
|
|
@@ -789,15 +794,15 @@ class DSStore(object):
|
|
|
789
794
|
next_node = pointers[split]
|
|
790
795
|
else:
|
|
791
796
|
next_node = 0
|
|
792
|
-
block.write(b
|
|
797
|
+
block.write(b">II", next_node, split)
|
|
793
798
|
|
|
794
799
|
for n in range(prev_split + 1, split):
|
|
795
800
|
if internal:
|
|
796
|
-
block.write(b
|
|
801
|
+
block.write(b">I", pointers[n])
|
|
797
802
|
entries[n].write(block)
|
|
798
803
|
|
|
799
804
|
block.zero_fill()
|
|
800
|
-
|
|
805
|
+
|
|
801
806
|
prev_split = split
|
|
802
807
|
|
|
803
808
|
return (entries[best_split[0]], entries[best_split[1]])
|
|
@@ -811,13 +816,13 @@ class DSStore(object):
|
|
|
811
816
|
before = []
|
|
812
817
|
total = 0
|
|
813
818
|
ppivots = pivots + [None]
|
|
814
|
-
for b,p in zip(blocks, ppivots):
|
|
819
|
+
for b, p in zip(blocks, ppivots):
|
|
815
820
|
b.seek(0)
|
|
816
|
-
next_node, count = b.read(b
|
|
821
|
+
next_node, count = b.read(b">II")
|
|
817
822
|
for n in range(count):
|
|
818
823
|
pos = b.tell()
|
|
819
824
|
if next_node:
|
|
820
|
-
ptr = b.read(b
|
|
825
|
+
ptr = b.read(b">I")[0]
|
|
821
826
|
pointers.append(ptr)
|
|
822
827
|
e = DSStoreEntry.read(b)
|
|
823
828
|
entries.append(e)
|
|
@@ -842,11 +847,11 @@ class DSStore(object):
|
|
|
842
847
|
return
|
|
843
848
|
|
|
844
849
|
with self._get_block(node) as block:
|
|
845
|
-
next_node, count = block.read(b
|
|
846
|
-
|
|
850
|
+
next_node, count = block.read(b">II")
|
|
851
|
+
|
|
847
852
|
with self._get_block(path[-1]) as parent:
|
|
848
853
|
# Find the left and right siblings and respective pivots
|
|
849
|
-
parent_next, parent_count = parent.read(b
|
|
854
|
+
parent_next, parent_count = parent.read(b">II")
|
|
850
855
|
left_pos = None
|
|
851
856
|
left_node = None
|
|
852
857
|
left_pivot = None
|
|
@@ -857,7 +862,7 @@ class DSStore(object):
|
|
|
857
862
|
prev_e = prev_ptr = prev_pos = None
|
|
858
863
|
for n in range(parent_count):
|
|
859
864
|
pos = parent.tell()
|
|
860
|
-
ptr = parent.read(b
|
|
865
|
+
ptr = parent.read(b">I")[0]
|
|
861
866
|
e = DSStoreEntry.read(parent)
|
|
862
867
|
|
|
863
868
|
if ptr == node:
|
|
@@ -870,7 +875,7 @@ class DSStore(object):
|
|
|
870
875
|
right_node = ptr
|
|
871
876
|
right_pos = pos
|
|
872
877
|
break
|
|
873
|
-
|
|
878
|
+
|
|
874
879
|
prev_e = e
|
|
875
880
|
prev_ptr = ptr
|
|
876
881
|
prev_pos = pos
|
|
@@ -884,24 +889,27 @@ class DSStore(object):
|
|
|
884
889
|
right_node = parent_next
|
|
885
890
|
right_pos = parent.tell()
|
|
886
891
|
|
|
887
|
-
|
|
888
|
-
|
|
892
|
+
_ = parent.tell()
|
|
893
|
+
|
|
889
894
|
if left_node and right_node:
|
|
890
|
-
with self._get_block(left_node) as left,
|
|
891
|
-
|
|
895
|
+
with self._get_block(left_node) as left, self._get_block(
|
|
896
|
+
right_node
|
|
897
|
+
) as right:
|
|
892
898
|
blocks = [left, block, right]
|
|
893
899
|
pivots = [left_pivot, right_pivot]
|
|
894
|
-
|
|
900
|
+
|
|
895
901
|
entries, pointers, before = self._extract(blocks, pivots)
|
|
896
902
|
|
|
897
903
|
# If there's a chance that we could use two pages instead
|
|
898
904
|
# of three, go for it
|
|
899
|
-
pivots = self._split2(
|
|
900
|
-
|
|
905
|
+
pivots = self._split2(
|
|
906
|
+
blocks, entries, pointers, before, bool(next_node)
|
|
907
|
+
)
|
|
901
908
|
if pivots is None:
|
|
902
909
|
ptrs = [left_node, node, right_node]
|
|
903
|
-
pivots = self._split3(
|
|
904
|
-
|
|
910
|
+
pivots = self._split3(
|
|
911
|
+
blocks, entries, pointers, before, bool(next_node)
|
|
912
|
+
)
|
|
905
913
|
else:
|
|
906
914
|
if pivots:
|
|
907
915
|
ptrs = [left_node, node]
|
|
@@ -913,7 +921,7 @@ class DSStore(object):
|
|
|
913
921
|
self._store.release(right_node)
|
|
914
922
|
self._nodes -= 1
|
|
915
923
|
self._dirty = True
|
|
916
|
-
|
|
924
|
+
|
|
917
925
|
# Remove the pivots from the parent
|
|
918
926
|
with self._get_block(path[-1]) as parent:
|
|
919
927
|
if right_node == parent_next:
|
|
@@ -925,11 +933,11 @@ class DSStore(object):
|
|
|
925
933
|
parent.delete(right_pos - left_pos)
|
|
926
934
|
parent.seek(0)
|
|
927
935
|
parent_count -= 2
|
|
928
|
-
parent.write(b
|
|
936
|
+
parent.write(b">II", parent_next, parent_count)
|
|
929
937
|
self._records -= 2
|
|
930
|
-
|
|
938
|
+
|
|
931
939
|
# Replace with those in pivots
|
|
932
|
-
for e,rp in zip(pivots, ptrs[1:]):
|
|
940
|
+
for e, rp in zip(pivots, ptrs[1:]):
|
|
933
941
|
self._insert_inner(path[:-1], path[-1], e, rp)
|
|
934
942
|
elif left_node:
|
|
935
943
|
with self._get_block(left_node) as left:
|
|
@@ -938,8 +946,9 @@ class DSStore(object):
|
|
|
938
946
|
|
|
939
947
|
entries, pointers, before = self._extract(blocks, pivots)
|
|
940
948
|
|
|
941
|
-
pivots = self._split2(
|
|
942
|
-
|
|
949
|
+
pivots = self._split2(
|
|
950
|
+
blocks, entries, pointers, before, bool(next_node)
|
|
951
|
+
)
|
|
943
952
|
|
|
944
953
|
# Remove the pivot from the parent
|
|
945
954
|
with self._get_block(path[-1]) as parent:
|
|
@@ -952,7 +961,7 @@ class DSStore(object):
|
|
|
952
961
|
parent.delete(node_pos - left_pos)
|
|
953
962
|
parent.seek(0)
|
|
954
963
|
parent_count -= 1
|
|
955
|
-
parent.write(b
|
|
964
|
+
parent.write(b">II", parent_next, parent_count)
|
|
956
965
|
self._records -= 1
|
|
957
966
|
|
|
958
967
|
# Replace the pivot
|
|
@@ -965,8 +974,9 @@ class DSStore(object):
|
|
|
965
974
|
|
|
966
975
|
entries, pointers, before = self._extract(blocks, pivots)
|
|
967
976
|
|
|
968
|
-
pivots = self._split2(
|
|
969
|
-
|
|
977
|
+
pivots = self._split2(
|
|
978
|
+
blocks, entries, pointers, before, bool(next_node)
|
|
979
|
+
)
|
|
970
980
|
|
|
971
981
|
# Remove the pivot from the parent
|
|
972
982
|
with self._get_block(path[-1]) as parent:
|
|
@@ -979,13 +989,12 @@ class DSStore(object):
|
|
|
979
989
|
parent.delete(right_pos - node_pos)
|
|
980
990
|
parent.seek(0)
|
|
981
991
|
parent_count -= 1
|
|
982
|
-
parent.write(b
|
|
992
|
+
parent.write(b">II", parent_next, parent_count)
|
|
983
993
|
self._records -= 1
|
|
984
994
|
|
|
985
995
|
# Replace the pivot
|
|
986
996
|
if pivots:
|
|
987
|
-
self._insert_inner(path[:-1], path[-1], pivots[0],
|
|
988
|
-
right_node)
|
|
997
|
+
self._insert_inner(path[:-1], path[-1], pivots[0], right_node)
|
|
989
998
|
|
|
990
999
|
if not path and not parent_count:
|
|
991
1000
|
self._store.release(path[-1])
|
|
@@ -994,7 +1003,7 @@ class DSStore(object):
|
|
|
994
1003
|
self._rootnode = node
|
|
995
1004
|
else:
|
|
996
1005
|
count, used = self._block_usage(path[-1])
|
|
997
|
-
|
|
1006
|
+
|
|
998
1007
|
if used < self._page_size // 2:
|
|
999
1008
|
self._rebalance(path[:-1], path[-1])
|
|
1000
1009
|
|
|
@@ -1002,31 +1011,32 @@ class DSStore(object):
|
|
|
1002
1011
|
# lower-cased.
|
|
1003
1012
|
def _delete_leaf(self, node, filename_lc, code):
|
|
1004
1013
|
found = False
|
|
1005
|
-
|
|
1014
|
+
|
|
1006
1015
|
with self._get_block(node) as block:
|
|
1007
|
-
next_node, count = block.read(b
|
|
1016
|
+
next_node, count = block.read(b">II")
|
|
1008
1017
|
|
|
1009
1018
|
for n in range(count):
|
|
1010
1019
|
pos = block.tell()
|
|
1011
1020
|
e = DSStoreEntry.read(block)
|
|
1012
|
-
if e.filename.lower() == filename_lc
|
|
1013
|
-
|
|
1021
|
+
if e.filename.lower() == filename_lc and (
|
|
1022
|
+
code is None or e.code == code
|
|
1023
|
+
):
|
|
1014
1024
|
block.seek(pos)
|
|
1015
1025
|
block.delete(e.byte_length())
|
|
1016
1026
|
found = True
|
|
1017
|
-
|
|
1027
|
+
|
|
1018
1028
|
# This does not affect the loop; THIS IS NOT A BUG
|
|
1019
1029
|
count -= 1
|
|
1020
1030
|
|
|
1021
1031
|
self._records -= 1
|
|
1022
1032
|
self._dirty = True
|
|
1023
|
-
|
|
1033
|
+
|
|
1024
1034
|
if found:
|
|
1025
1035
|
used = block.tell()
|
|
1026
|
-
|
|
1036
|
+
|
|
1027
1037
|
block.seek(0)
|
|
1028
|
-
block.write(b
|
|
1029
|
-
|
|
1038
|
+
block.write(b">II", next_node, count)
|
|
1039
|
+
|
|
1030
1040
|
return used < self._page_size // 2
|
|
1031
1041
|
else:
|
|
1032
1042
|
return False
|
|
@@ -1040,7 +1050,7 @@ class DSStore(object):
|
|
|
1040
1050
|
rebalance = None
|
|
1041
1051
|
while True:
|
|
1042
1052
|
with self._get_block(node) as block:
|
|
1043
|
-
next_node, count = block.read(b
|
|
1053
|
+
next_node, count = block.read(b">II")
|
|
1044
1054
|
|
|
1045
1055
|
if next_node:
|
|
1046
1056
|
path.append(node)
|
|
@@ -1053,7 +1063,7 @@ class DSStore(object):
|
|
|
1053
1063
|
|
|
1054
1064
|
count -= 1
|
|
1055
1065
|
block.seek(0)
|
|
1056
|
-
block.write(b
|
|
1066
|
+
block.write(b">II", next_node, count)
|
|
1057
1067
|
|
|
1058
1068
|
if pos < self._page_size // 2:
|
|
1059
1069
|
rebalance = (path, node)
|
|
@@ -1064,16 +1074,17 @@ class DSStore(object):
|
|
|
1064
1074
|
# Delete an entry from an inner node, `node'
|
|
1065
1075
|
def _delete_inner(self, path, node, filename_lc, code):
|
|
1066
1076
|
rebalance = False
|
|
1067
|
-
|
|
1077
|
+
|
|
1068
1078
|
with self._get_block(node) as block:
|
|
1069
|
-
next_node, count = block.read(b
|
|
1079
|
+
next_node, count = block.read(b">II")
|
|
1070
1080
|
|
|
1071
1081
|
for n in range(count):
|
|
1072
1082
|
pos = block.tell()
|
|
1073
|
-
ptr = block.read(b
|
|
1083
|
+
ptr = block.read(b">I")[0]
|
|
1074
1084
|
e = DSStoreEntry.read(block)
|
|
1075
|
-
if e.filename.lower() == filename_lc
|
|
1076
|
-
|
|
1085
|
+
if e.filename.lower() == filename_lc and (
|
|
1086
|
+
code is None or e.code == code
|
|
1087
|
+
):
|
|
1077
1088
|
# Take the largest from the left subtree
|
|
1078
1089
|
rebalance, largest = self._take_largest(path, ptr)
|
|
1079
1090
|
|
|
@@ -1083,20 +1094,20 @@ class DSStore(object):
|
|
|
1083
1094
|
next_node = ptr
|
|
1084
1095
|
block.seek(pos)
|
|
1085
1096
|
else:
|
|
1086
|
-
right_ptr = block.read(b
|
|
1097
|
+
right_ptr = block.read(b">I")[0]
|
|
1087
1098
|
block.seek(pos + 4)
|
|
1088
|
-
|
|
1099
|
+
|
|
1089
1100
|
block.delete(e.byte_length() + 4)
|
|
1090
1101
|
|
|
1091
1102
|
count -= 1
|
|
1092
1103
|
block.seek(0)
|
|
1093
|
-
block.write(b
|
|
1104
|
+
block.write(b">II", next_node, count)
|
|
1094
1105
|
|
|
1095
1106
|
self._records -= 1
|
|
1096
1107
|
self._dirty = True
|
|
1097
|
-
|
|
1108
|
+
|
|
1098
1109
|
break
|
|
1099
|
-
|
|
1110
|
+
|
|
1100
1111
|
# Replace the pivot value
|
|
1101
1112
|
self._insert_inner(path, node, largest, right_ptr)
|
|
1102
1113
|
|
|
@@ -1107,16 +1118,16 @@ class DSStore(object):
|
|
|
1107
1118
|
return False
|
|
1108
1119
|
|
|
1109
1120
|
def delete(self, filename, code):
|
|
1110
|
-
"""Delete an item, identified by ``filename`` and ``code``
|
|
1111
|
-
|
|
1121
|
+
"""Delete an item, identified by ``filename`` and ``code`` from the
|
|
1122
|
+
B-Tree."""
|
|
1112
1123
|
if isinstance(filename, DSStoreEntry):
|
|
1113
1124
|
code = filename.code
|
|
1114
1125
|
filename = filename.filename
|
|
1115
1126
|
|
|
1116
1127
|
# If we're deleting *every* node for "filename", we must recurse
|
|
1117
1128
|
if code is None:
|
|
1118
|
-
|
|
1119
|
-
raise ValueError(
|
|
1129
|
+
# TODO: Fix this so we can do bulk deletes
|
|
1130
|
+
raise ValueError("You must delete items individually. Sorry")
|
|
1120
1131
|
|
|
1121
1132
|
# Otherwise, we're deleting *one* specific node
|
|
1122
1133
|
filename_lc = filename.lower()
|
|
@@ -1124,14 +1135,15 @@ class DSStore(object):
|
|
|
1124
1135
|
node = self._rootnode
|
|
1125
1136
|
while True:
|
|
1126
1137
|
with self._get_block(node) as block:
|
|
1127
|
-
next_node, count = block.read(b
|
|
1138
|
+
next_node, count = block.read(b">II")
|
|
1128
1139
|
if next_node:
|
|
1129
1140
|
for n in range(count):
|
|
1130
|
-
ptr = block.read(b
|
|
1141
|
+
ptr = block.read(b">I")[0]
|
|
1131
1142
|
e = DSStoreEntry.read(block)
|
|
1132
1143
|
e_lc = e.filename.lower()
|
|
1133
|
-
if filename_lc < e_lc
|
|
1134
|
-
|
|
1144
|
+
if filename_lc < e_lc or (
|
|
1145
|
+
filename_lc == e_lc and code < e.code
|
|
1146
|
+
):
|
|
1135
1147
|
next_node = ptr
|
|
1136
1148
|
break
|
|
1137
1149
|
elif filename_lc == e_lc and code == e.code:
|
|
@@ -1147,12 +1159,12 @@ class DSStore(object):
|
|
|
1147
1159
|
# Find implementation
|
|
1148
1160
|
def _find(self, node, filename_lc, code=None):
|
|
1149
1161
|
if code is not None and not isinstance(code, bytes):
|
|
1150
|
-
code = code.encode(
|
|
1162
|
+
code = code.encode("latin_1")
|
|
1151
1163
|
with self._get_block(node) as block:
|
|
1152
|
-
next_node, count = block.read(b
|
|
1164
|
+
next_node, count = block.read(b">II")
|
|
1153
1165
|
if next_node:
|
|
1154
1166
|
for n in range(count):
|
|
1155
|
-
ptr = block.read(b
|
|
1167
|
+
ptr = block.read(b">I")[0]
|
|
1156
1168
|
e = DSStoreEntry.read(block)
|
|
1157
1169
|
if filename_lc < e.filename.lower():
|
|
1158
1170
|
for e in self._find(ptr, filename_lc, code):
|
|
@@ -1176,16 +1188,16 @@ class DSStore(object):
|
|
|
1176
1188
|
yield e
|
|
1177
1189
|
elif code < e.code:
|
|
1178
1190
|
return
|
|
1179
|
-
|
|
1191
|
+
|
|
1180
1192
|
def find(self, filename, code=None):
|
|
1181
|
-
"""Returns a generator that will iterate over matching entries in
|
|
1182
|
-
|
|
1193
|
+
"""Returns a generator that will iterate over matching entries in the
|
|
1194
|
+
B-Tree."""
|
|
1183
1195
|
if isinstance(filename, DSStoreEntry):
|
|
1184
1196
|
code = filename.code
|
|
1185
1197
|
filename = filename.filename
|
|
1186
1198
|
|
|
1187
1199
|
filename_lc = filename.lower()
|
|
1188
|
-
|
|
1200
|
+
|
|
1189
1201
|
return self._find(self._rootnode, filename_lc, code)
|
|
1190
1202
|
|
|
1191
1203
|
def __len__(self):
|
|
@@ -1194,36 +1206,36 @@ class DSStore(object):
|
|
|
1194
1206
|
def __iter__(self):
|
|
1195
1207
|
return self._traverse(self._rootnode)
|
|
1196
1208
|
|
|
1197
|
-
class Partial
|
|
1209
|
+
class Partial:
|
|
1198
1210
|
"""This is used to implement indexing."""
|
|
1211
|
+
|
|
1199
1212
|
def __init__(self, store, filename):
|
|
1200
1213
|
self._store = store
|
|
1201
1214
|
self._filename = filename
|
|
1202
1215
|
|
|
1203
1216
|
def __getitem__(self, code):
|
|
1204
1217
|
if code is None:
|
|
1205
|
-
raise KeyError(
|
|
1218
|
+
raise KeyError("no such key - [%s][None]" % self._filename)
|
|
1206
1219
|
|
|
1207
1220
|
if not isinstance(code, bytes):
|
|
1208
|
-
code = code.encode(
|
|
1221
|
+
code = code.encode("latin_1")
|
|
1209
1222
|
|
|
1210
1223
|
try:
|
|
1211
1224
|
item = next(self._store.find(self._filename, code))
|
|
1212
1225
|
except StopIteration:
|
|
1213
|
-
raise KeyError(
|
|
1214
|
-
code))
|
|
1226
|
+
raise KeyError(f"no such key - [{self._filename}][{code}]")
|
|
1215
1227
|
|
|
1216
|
-
if not isinstance(item.type, (bytes, str
|
|
1228
|
+
if not isinstance(item.type, (bytes, str)):
|
|
1217
1229
|
return item.value
|
|
1218
|
-
|
|
1230
|
+
|
|
1219
1231
|
return (item.type, item.value)
|
|
1220
|
-
|
|
1232
|
+
|
|
1221
1233
|
def __setitem__(self, code, value):
|
|
1222
1234
|
if code is None:
|
|
1223
|
-
raise KeyError(
|
|
1235
|
+
raise KeyError("bad key - [%s][None]" % self._filename)
|
|
1224
1236
|
|
|
1225
1237
|
if not isinstance(code, bytes):
|
|
1226
|
-
code = code.encode(
|
|
1238
|
+
code = code.encode("latin_1")
|
|
1227
1239
|
|
|
1228
1240
|
codec = codecs.get(code, None)
|
|
1229
1241
|
if codec:
|
|
@@ -1232,20 +1244,19 @@ class DSStore(object):
|
|
|
1232
1244
|
else:
|
|
1233
1245
|
entry_type = value[0]
|
|
1234
1246
|
entry_value = value[1]
|
|
1235
|
-
|
|
1236
|
-
self._store.insert(
|
|
1237
|
-
|
|
1247
|
+
|
|
1248
|
+
self._store.insert(
|
|
1249
|
+
DSStoreEntry(self._filename, code, entry_type, entry_value)
|
|
1250
|
+
)
|
|
1238
1251
|
|
|
1239
1252
|
def __delitem__(self, code):
|
|
1240
1253
|
if code is None:
|
|
1241
|
-
raise KeyError(
|
|
1254
|
+
raise KeyError("no such key - [%s][None]" % self._filename)
|
|
1242
1255
|
|
|
1243
1256
|
self._store.delete(self._filename, code)
|
|
1244
1257
|
|
|
1245
1258
|
def __iter__(self):
|
|
1246
|
-
|
|
1247
|
-
yield item
|
|
1259
|
+
yield from self._store.find(self._filename)
|
|
1248
1260
|
|
|
1249
1261
|
def __getitem__(self, filename):
|
|
1250
1262
|
return self.Partial(self, filename)
|
|
1251
|
-
|