hypercore-storage 0.0.41 → 1.0.1
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/LICENSE +201 -0
- package/README.md +17 -0
- package/index.js +467 -783
- package/lib/block-dependency-stream.js +107 -0
- package/lib/keys.js +242 -0
- package/lib/streams.js +112 -0
- package/lib/tx.js +286 -0
- package/lib/view.js +345 -0
- package/migrations/0/index.js +628 -0
- package/migrations/0/messages.js +1069 -0
- package/package.json +33 -18
- package/spec/hyperschema/index.js +510 -0
- package/lib/dependency-stream.js +0 -111
- package/lib/memory-overlay.js +0 -438
- package/lib/messages.js +0 -190
- package/lib/tip-list.js +0 -93
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const { Readable } = require('streamx')
|
|
4
|
+
const b4a = require('b4a')
|
|
5
|
+
const flat = require('flat-tree')
|
|
6
|
+
const crypto = require('hypercore-crypto')
|
|
7
|
+
const c = require('compact-encoding')
|
|
8
|
+
const m = require('./messages.js')
|
|
9
|
+
const View = require('../../lib/view.js')
|
|
10
|
+
const { CorestoreTX, CoreTX, CorestoreRX } = require('../../lib/tx.js')
|
|
11
|
+
|
|
12
|
+
const EMPTY_NODE = b4a.alloc(40)
|
|
13
|
+
const EMPTY_PAGE = b4a.alloc(4096)
|
|
14
|
+
|
|
15
|
+
class CoreListStream extends Readable {
|
|
16
|
+
constructor (storage) {
|
|
17
|
+
super()
|
|
18
|
+
|
|
19
|
+
this.storage = storage
|
|
20
|
+
this.stack = []
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async _open (cb) {
|
|
24
|
+
for (const a of await readdir(path.join(this.storage, 'cores'))) {
|
|
25
|
+
for (const b of await readdir(path.join(this.storage, 'cores', a))) {
|
|
26
|
+
for (const dkey of await readdir(path.join(this.storage, 'cores', a, b))) {
|
|
27
|
+
this.stack.push(path.join(this.storage, 'cores', a, b, dkey))
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
cb(null)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async _read (cb) {
|
|
36
|
+
while (true) {
|
|
37
|
+
const next = this.stack.pop()
|
|
38
|
+
if (!next) {
|
|
39
|
+
this.push(null)
|
|
40
|
+
break
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const oplog = path.join(next, 'oplog')
|
|
44
|
+
const result = await readOplog(oplog)
|
|
45
|
+
if (!result) continue
|
|
46
|
+
|
|
47
|
+
this.push(result)
|
|
48
|
+
break
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
cb(null)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function decodeOplogHeader (state) {
|
|
56
|
+
c.uint32.decode(state) // cksum, ignore for now
|
|
57
|
+
|
|
58
|
+
const l = c.uint32.decode(state)
|
|
59
|
+
const length = l >> 2
|
|
60
|
+
const headerBit = l & 1
|
|
61
|
+
const partialBit = l & 2
|
|
62
|
+
|
|
63
|
+
if (state.end - state.start < length) return null
|
|
64
|
+
|
|
65
|
+
const end = state.start + length
|
|
66
|
+
const result = { header: headerBit, partial: partialBit !== 0, byteLength: length + 8, message: null }
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
result.message = m.oplog.header.decode({ start: state.start, end, buffer: state.buffer })
|
|
70
|
+
} catch {
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
state.start = end
|
|
75
|
+
return result
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function decodeOplogEntry (state) {
|
|
79
|
+
if (state.end - state.start < 8) return null
|
|
80
|
+
|
|
81
|
+
c.uint32.decode(state) // cksum, ignore for now
|
|
82
|
+
|
|
83
|
+
const l = c.uint32.decode(state)
|
|
84
|
+
const length = l >>> 2
|
|
85
|
+
const headerBit = l & 1
|
|
86
|
+
const partialBit = l & 2
|
|
87
|
+
|
|
88
|
+
if (state.end - state.start < length) return null
|
|
89
|
+
|
|
90
|
+
const end = state.start + length
|
|
91
|
+
|
|
92
|
+
const result = { header: headerBit, partial: partialBit !== 0, byteLength: length + 8, message: null }
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
result.message = m.oplog.entry.decode({ start: state.start, end, buffer: state.buffer })
|
|
96
|
+
} catch {
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
state.start = end
|
|
101
|
+
|
|
102
|
+
return result
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = { store, core }
|
|
106
|
+
|
|
107
|
+
async function store (storage, { version, dryRun = true, gc = true }) {
|
|
108
|
+
const stream = new CoreListStream(storage.path)
|
|
109
|
+
const view = new View()
|
|
110
|
+
|
|
111
|
+
const tx = new CorestoreTX(view)
|
|
112
|
+
const head = await storage._getHead(view)
|
|
113
|
+
const primaryKeyFile = path.join(storage.path, 'primary-key')
|
|
114
|
+
|
|
115
|
+
const primaryKey = await readFile(primaryKeyFile)
|
|
116
|
+
|
|
117
|
+
if (!head.seed) head.seed = primaryKey
|
|
118
|
+
|
|
119
|
+
for await (const data of stream) {
|
|
120
|
+
const key = data.header.key
|
|
121
|
+
const discoveryKey = crypto.discoveryKey(data.header.key)
|
|
122
|
+
const files = getFiles(data.path)
|
|
123
|
+
|
|
124
|
+
if (head.defaultDiscoveryKey === null) head.defaultDiscoveryKey = discoveryKey
|
|
125
|
+
|
|
126
|
+
const core = {
|
|
127
|
+
version: 0, // need later migration
|
|
128
|
+
corePointer: head.allocated.cores++,
|
|
129
|
+
dataPointer: head.allocated.datas++,
|
|
130
|
+
alias: null
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const ptr = { version: 0, corePointer: core.corePointer, dataPointer: core.dataPointer, dependencies: [] }
|
|
134
|
+
const ctx = new CoreTX(ptr, storage.db, view, [])
|
|
135
|
+
const userData = new Map()
|
|
136
|
+
const treeNodes = new Map()
|
|
137
|
+
|
|
138
|
+
const auth = {
|
|
139
|
+
key,
|
|
140
|
+
discoveryKey,
|
|
141
|
+
manifest: data.header.manifest,
|
|
142
|
+
keyPair: data.header.keyPair,
|
|
143
|
+
encryptionKey: null
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const tree = {
|
|
147
|
+
length: 0,
|
|
148
|
+
fork: 0,
|
|
149
|
+
rootHash: null,
|
|
150
|
+
signature: null
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (data.header.tree && data.header.tree.length) {
|
|
154
|
+
tree.length = data.header.tree.length
|
|
155
|
+
tree.fork = data.header.tree.fork
|
|
156
|
+
tree.rootHash = data.header.tree.rootHash
|
|
157
|
+
tree.signature = data.header.tree.signature
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
for (const { key, value } of data.header.userData) {
|
|
161
|
+
userData.set(key, value)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for (const e of data.entries) {
|
|
165
|
+
if (e.userData) userData.set(e.userData.key, e.userData.value)
|
|
166
|
+
|
|
167
|
+
if (e.treeNodes) {
|
|
168
|
+
for (const node of e.treeNodes) {
|
|
169
|
+
treeNodes.set(node.index, node)
|
|
170
|
+
ctx.putTreeNode(node)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (e.treeUpgrade) {
|
|
175
|
+
if (e.treeUpgrade.ancestors !== tree.length) {
|
|
176
|
+
throw new Error('Unflushed truncations not migrate-able atm')
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
tree.length = e.treeUpgrade.length
|
|
180
|
+
tree.fork = e.treeUpgrade.fork
|
|
181
|
+
tree.rootHash = null
|
|
182
|
+
tree.signature = e.treeUpgrade.signature
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (userData.has('corestore/name') && userData.has('corestore/namespace')) {
|
|
187
|
+
core.alias = {
|
|
188
|
+
name: b4a.toString(userData.get('corestore/name')),
|
|
189
|
+
namespace: userData.get('corestore/namespace')
|
|
190
|
+
}
|
|
191
|
+
userData.delete('corestore/name')
|
|
192
|
+
userData.delete('corestore/namespace')
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
for (const [key, value] of userData) {
|
|
196
|
+
ctx.putUserData(key, value)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
ctx.setAuth(auth)
|
|
200
|
+
|
|
201
|
+
const getTreeNode = (index) => (treeNodes.get(index) || getTreeNodeFromFile(files.tree, index))
|
|
202
|
+
|
|
203
|
+
if (tree.length) {
|
|
204
|
+
if (tree.rootHash === null) tree.rootHash = crypto.tree(await getRoots(tree.length, getTreeNode))
|
|
205
|
+
ctx.setHead(tree)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
tx.putCore(discoveryKey, core)
|
|
209
|
+
if (core.alias) tx.putCoreByAlias(core.alias, discoveryKey)
|
|
210
|
+
|
|
211
|
+
await ctx.flush()
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
head.version = version
|
|
215
|
+
tx.setHead(head)
|
|
216
|
+
tx.apply()
|
|
217
|
+
|
|
218
|
+
if (dryRun) return
|
|
219
|
+
|
|
220
|
+
await View.flush(view.changes, storage.db)
|
|
221
|
+
|
|
222
|
+
if (gc) await rm(primaryKeyFile)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
class Slicer {
|
|
226
|
+
constructor () {
|
|
227
|
+
this.buffer = null
|
|
228
|
+
this.offset = 0
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
get size () {
|
|
232
|
+
return this.buffer === null ? 0 : this.buffer.byteLength
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
push (data) {
|
|
236
|
+
if (this.buffer === null) this.buffer = data
|
|
237
|
+
else this.buffer = b4a.concat([this.buffer, data])
|
|
238
|
+
this.offset += data.byteLength
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
take (len) {
|
|
242
|
+
if (len <= this.size) {
|
|
243
|
+
const chunk = this.buffer.subarray(0, len)
|
|
244
|
+
this.buffer = this.buffer.subarray(len)
|
|
245
|
+
return chunk
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return null
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function core (core, { version, dryRun = true, gc = true }) {
|
|
253
|
+
if (dryRun) return // dryRun mode not supported atm
|
|
254
|
+
|
|
255
|
+
const rx = core.read()
|
|
256
|
+
|
|
257
|
+
const promises = [rx.getAuth(), rx.getHead()]
|
|
258
|
+
rx.tryFlush()
|
|
259
|
+
|
|
260
|
+
const [auth, head] = await Promise.all(promises)
|
|
261
|
+
|
|
262
|
+
if (!auth) return
|
|
263
|
+
|
|
264
|
+
const dk = b4a.toString(auth.discoveryKey, 'hex')
|
|
265
|
+
const files = getFiles(path.join(core.store.path, 'cores', dk.slice(0, 2), dk.slice(2, 4), dk))
|
|
266
|
+
|
|
267
|
+
if (head === null || head.length === 0) {
|
|
268
|
+
if (gc) await runGC()
|
|
269
|
+
return // no data
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const oplog = await readOplog(files.oplog)
|
|
273
|
+
if (!oplog) throw new Error('No oplog available')
|
|
274
|
+
|
|
275
|
+
const treeData = new Slicer()
|
|
276
|
+
|
|
277
|
+
let treeIndex = 0
|
|
278
|
+
|
|
279
|
+
if (await exists(files.tree)) {
|
|
280
|
+
for await (const data of fs.createReadStream(files.tree)) {
|
|
281
|
+
treeData.push(data)
|
|
282
|
+
|
|
283
|
+
const write = core.write()
|
|
284
|
+
|
|
285
|
+
while (true) {
|
|
286
|
+
const buf = treeData.take(40)
|
|
287
|
+
if (buf === null) break
|
|
288
|
+
|
|
289
|
+
const index = treeIndex++
|
|
290
|
+
if (b4a.equals(buf, EMPTY_NODE)) continue
|
|
291
|
+
|
|
292
|
+
write.putTreeNode(decodeTreeNode(index, buf))
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
await write.flush()
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const buf = []
|
|
300
|
+
if (await exists(files.bitfield)) {
|
|
301
|
+
for await (const data of fs.createReadStream(files.bitfield)) {
|
|
302
|
+
buf.push(data)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
let bitfield = b4a.concat(buf)
|
|
307
|
+
if (bitfield.byteLength & 4095) bitfield = b4a.concat([bitfield, b4a.alloc(4096 - (bitfield.byteLength & 4095))])
|
|
308
|
+
|
|
309
|
+
const pages = new Map()
|
|
310
|
+
const headerBits = new Map()
|
|
311
|
+
|
|
312
|
+
const roots = await getRoots(head.length, getTreeNode)
|
|
313
|
+
|
|
314
|
+
for (const e of oplog.entries) {
|
|
315
|
+
if (!e.bitfield) continue
|
|
316
|
+
|
|
317
|
+
for (let i = 0; i < e.bitfield.length; i++) {
|
|
318
|
+
headerBits.set(i + e.bitfield.start, !e.bitfield.drop)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
let w = core.write()
|
|
323
|
+
for (const index of allBits(bitfield)) {
|
|
324
|
+
if (headerBits.get(index) === false) continue
|
|
325
|
+
|
|
326
|
+
setBitInPage(index)
|
|
327
|
+
|
|
328
|
+
const blk = await getBlockFromFile(files.data, index, roots, getTreeNode)
|
|
329
|
+
|
|
330
|
+
if (w.changes.length > 1024) {
|
|
331
|
+
await w.flush()
|
|
332
|
+
w = core.write()
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
w.putBlock(index, blk)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
for (const [index, bit] of headerBits) {
|
|
339
|
+
if (!bit) continue
|
|
340
|
+
|
|
341
|
+
setBitInPage(index)
|
|
342
|
+
|
|
343
|
+
const blk = await getBlockFromFile(files.data, index, roots, getTreeNode)
|
|
344
|
+
w.putBlock(index, blk)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
for (const [index, page] of pages) {
|
|
348
|
+
w.putBitfieldPage(index, b4a.from(page.buffer, page.byteOffset, page.byteLength))
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
await w.flush()
|
|
352
|
+
|
|
353
|
+
let contiguousLength = 0
|
|
354
|
+
for await (const data of core.createBlockStream()) {
|
|
355
|
+
if (data.index === contiguousLength) contiguousLength++
|
|
356
|
+
else break
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (contiguousLength) {
|
|
360
|
+
const w = core.write()
|
|
361
|
+
w.setHints({ contiguousLength })
|
|
362
|
+
await w.flush()
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
await commitCoreMigration(auth, core, version)
|
|
366
|
+
|
|
367
|
+
if (gc) await runGC()
|
|
368
|
+
|
|
369
|
+
async function runGC () {
|
|
370
|
+
await rm(files.path)
|
|
371
|
+
await rmdir(path.join(files.path, '..'))
|
|
372
|
+
await rmdir(path.join(files.path, '../..'))
|
|
373
|
+
await rmdir(path.join(core.store.path, 'cores'))
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function setBitInPage (index) {
|
|
377
|
+
const n = index & 32767
|
|
378
|
+
const p = (index - n) / 32768
|
|
379
|
+
|
|
380
|
+
let page = pages.get(p)
|
|
381
|
+
|
|
382
|
+
if (!page) {
|
|
383
|
+
page = new Uint32Array(1024)
|
|
384
|
+
pages.set(p, page)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const o = n & 31
|
|
388
|
+
const b = (n - o) / 32
|
|
389
|
+
const v = 1 << o
|
|
390
|
+
|
|
391
|
+
page[b] |= v
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function getTreeNode (index) {
|
|
395
|
+
const read = core.read()
|
|
396
|
+
const promise = read.getTreeNode(index)
|
|
397
|
+
read.tryFlush()
|
|
398
|
+
return promise
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async function commitCoreMigration (auth, core, version) {
|
|
403
|
+
const view = new View()
|
|
404
|
+
const rx = new CorestoreRX(core.db, view)
|
|
405
|
+
|
|
406
|
+
const storeCorePromise = rx.getCore(auth.discoveryKey)
|
|
407
|
+
rx.tryFlush()
|
|
408
|
+
|
|
409
|
+
const storeCore = await storeCorePromise
|
|
410
|
+
|
|
411
|
+
storeCore.version = version
|
|
412
|
+
|
|
413
|
+
const tx = new CorestoreTX(view)
|
|
414
|
+
|
|
415
|
+
tx.putCore(auth.discoveryKey, storeCore)
|
|
416
|
+
tx.apply()
|
|
417
|
+
|
|
418
|
+
await View.flush(view.changes, core.db)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function getFiles (dir) {
|
|
422
|
+
return {
|
|
423
|
+
path: dir,
|
|
424
|
+
oplog: path.join(dir, 'oplog'),
|
|
425
|
+
data: path.join(dir, 'data'),
|
|
426
|
+
tree: path.join(dir, 'tree'),
|
|
427
|
+
bitfield: path.join(dir, 'bitfield')
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async function getRoots (length, getTreeNode) {
|
|
432
|
+
const all = []
|
|
433
|
+
for (const index of flat.fullRoots(2 * length)) {
|
|
434
|
+
all.push(await getTreeNode(index))
|
|
435
|
+
}
|
|
436
|
+
return all
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async function getBlockFromFile (file, index, roots, getTreeNode) {
|
|
440
|
+
const size = (await getTreeNode(2 * index)).size
|
|
441
|
+
const offset = await getByteOffset(2 * index, roots, getTreeNode)
|
|
442
|
+
|
|
443
|
+
return new Promise(function (resolve) {
|
|
444
|
+
readAll(file, size, offset, function (err, buf) {
|
|
445
|
+
if (err) return resolve(null)
|
|
446
|
+
resolve(buf)
|
|
447
|
+
})
|
|
448
|
+
})
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async function getByteOffset (index, roots, getTreeNode) {
|
|
452
|
+
if (index === 0) return 0
|
|
453
|
+
if ((index & 1) === 1) index = flat.leftSpan(index)
|
|
454
|
+
|
|
455
|
+
let head = 0
|
|
456
|
+
let offset = 0
|
|
457
|
+
|
|
458
|
+
for (const node of roots) { // all async ticks happen once we find the root so safe
|
|
459
|
+
head += 2 * ((node.index - head) + 1)
|
|
460
|
+
|
|
461
|
+
if (index >= head) {
|
|
462
|
+
offset += node.size
|
|
463
|
+
continue
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const ite = flat.iterator(node.index)
|
|
467
|
+
|
|
468
|
+
while (ite.index !== index) {
|
|
469
|
+
if (index < ite.index) {
|
|
470
|
+
ite.leftChild()
|
|
471
|
+
} else {
|
|
472
|
+
offset += (await getTreeNode(ite.leftChild())).size
|
|
473
|
+
ite.sibling()
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return offset
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
throw new Error('Failed to find offset')
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function decodeTreeNode (index, buf) {
|
|
484
|
+
return { index, size: c.decode(c.uint64, buf), hash: buf.subarray(8) }
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async function getTreeNodeFromFile (file, index) {
|
|
488
|
+
return new Promise(function (resolve) {
|
|
489
|
+
readAll(file, 40, index * 40, function (err, buf) {
|
|
490
|
+
if (err) return resolve(null)
|
|
491
|
+
resolve(decodeTreeNode(index, buf))
|
|
492
|
+
})
|
|
493
|
+
})
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function readAll (filename, length, pos, cb) {
|
|
497
|
+
const buf = b4a.alloc(length)
|
|
498
|
+
|
|
499
|
+
fs.open(filename, 'r', function (err, fd) {
|
|
500
|
+
if (err) return cb(err)
|
|
501
|
+
|
|
502
|
+
let offset = 0
|
|
503
|
+
|
|
504
|
+
fs.read(fd, buf, offset, buf.byteLength, pos, function loop (err, read) {
|
|
505
|
+
if (err) return done(err)
|
|
506
|
+
if (read === 0) return done(new Error('Partial read'))
|
|
507
|
+
offset += read
|
|
508
|
+
if (offset === buf.byteLength) return done(null, buf)
|
|
509
|
+
fs.read(fd, offset, buf.byteLength - offset, buf, pos + offset, loop)
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
function done (err, value) {
|
|
513
|
+
fs.close(fd, () => cb(err, value))
|
|
514
|
+
}
|
|
515
|
+
})
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async function readdir (dir) {
|
|
519
|
+
try {
|
|
520
|
+
return await fs.promises.readdir(dir)
|
|
521
|
+
} catch {
|
|
522
|
+
return []
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async function exists (file) {
|
|
527
|
+
try {
|
|
528
|
+
await fs.promises.stat(file)
|
|
529
|
+
return true
|
|
530
|
+
} catch {
|
|
531
|
+
return false
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
async function readFile (file) {
|
|
536
|
+
try {
|
|
537
|
+
return await fs.promises.readFile(file)
|
|
538
|
+
} catch {
|
|
539
|
+
return null
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
async function rm (dir) {
|
|
544
|
+
try {
|
|
545
|
+
await fs.promises.rm(dir, { recursive: true })
|
|
546
|
+
} catch {}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
async function rmdir (dir) {
|
|
550
|
+
try {
|
|
551
|
+
await fs.promises.rmdir(dir)
|
|
552
|
+
} catch {}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function * allBits (buffer) {
|
|
556
|
+
for (let i = 0; i < buffer.byteLength; i += EMPTY_PAGE.byteLength) {
|
|
557
|
+
const page = buffer.subarray(i, i + EMPTY_NODE.byteLength)
|
|
558
|
+
if (b4a.equals(page, EMPTY_PAGE)) continue
|
|
559
|
+
|
|
560
|
+
const view = new Uint32Array(page.buffer, page.byteOffset, EMPTY_PAGE.byteLength / 4)
|
|
561
|
+
|
|
562
|
+
for (let j = 0; j < view.length; j++) {
|
|
563
|
+
const n = view[j]
|
|
564
|
+
if (n === 0) continue
|
|
565
|
+
|
|
566
|
+
for (let k = 0; k < 32; k++) {
|
|
567
|
+
const m = 1 << k
|
|
568
|
+
if (n & m) yield i * 8 + j * 32 + k
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function readOplog (oplog) {
|
|
575
|
+
return new Promise(function (resolve) {
|
|
576
|
+
fs.readFile(oplog, function (err, buffer) {
|
|
577
|
+
if (err) return resolve(null)
|
|
578
|
+
|
|
579
|
+
const state = { start: 0, end: buffer.byteLength, buffer }
|
|
580
|
+
const headers = [1, 0]
|
|
581
|
+
|
|
582
|
+
const h1 = decodeOplogHeader(state)
|
|
583
|
+
state.start = 4096
|
|
584
|
+
|
|
585
|
+
const h2 = decodeOplogHeader(state)
|
|
586
|
+
state.start = 4096 * 2
|
|
587
|
+
|
|
588
|
+
if (!h1 && !h2) return resolve(null)
|
|
589
|
+
|
|
590
|
+
if (h1 && !h2) {
|
|
591
|
+
headers[0] = h1.header
|
|
592
|
+
headers[1] = h1.header
|
|
593
|
+
} else if (!h1 && h2) {
|
|
594
|
+
headers[0] = (h2.header + 1) & 1
|
|
595
|
+
headers[1] = h2.header
|
|
596
|
+
} else {
|
|
597
|
+
headers[0] = h1.header
|
|
598
|
+
headers[1] = h2.header
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const header = (headers[0] + headers[1]) & 1
|
|
602
|
+
const result = { path: path.dirname(oplog), header: null, entries: [] }
|
|
603
|
+
const decoded = []
|
|
604
|
+
|
|
605
|
+
result.header = header ? h2.message : h1.message
|
|
606
|
+
|
|
607
|
+
if (result.header.external) {
|
|
608
|
+
throw new Error('External headers not migrate-able atm')
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
while (true) {
|
|
612
|
+
const entry = decodeOplogEntry(state)
|
|
613
|
+
if (!entry) break
|
|
614
|
+
if (entry.header !== header) break
|
|
615
|
+
|
|
616
|
+
decoded.push(entry)
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
while (decoded.length > 0 && decoded[decoded.length - 1].partial) decoded.pop()
|
|
620
|
+
|
|
621
|
+
for (const e of decoded) {
|
|
622
|
+
result.entries.push(e.message)
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
resolve(result)
|
|
626
|
+
})
|
|
627
|
+
})
|
|
628
|
+
}
|