forked from xuset/indexeddb-chunk-store
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
143 lines (116 loc) · 4.24 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
module.exports = IdbChunkStore
var IdbKvStore = require('idb-kv-store')
function IdbChunkStore (chunkLength, opts, cb) {
var self = this
if (typeof chunkLength !== 'number') throw new Error('chunkLength must be a number')
if (typeof opts === 'function') return IdbChunkStore(chunkLength, null, opts)
if (!(self instanceof IdbChunkStore)) return new IdbChunkStore(chunkLength, opts, cb)
if (!opts) opts = {}
self.chunkLength = chunkLength
self.length = Number(opts.length) || Infinity
self.closed = false
self._groupPutDelay = opts.groupPutDelay || 10
self._groupPutCallbacks = []
self._groupPutData = {}
self._groupPutTimeout = null
self._lastGroupPut = 0
if (self.length !== Infinity) {
this.lastChunkLength = (this.length % this.chunkLength) || this.chunkLength
self.lastChunkIndex = Math.ceil(self.length / self.chunkLength) - 1
}
var name = opts.name || '' + Math.round(9e16 * Math.random())
// for webtorrent
if (opts.torrent && opts.torrent.infoHash) name = opts.torrent.infoHash
self._store = new IdbKvStore(name, cb)
self._store.on('close', onClose)
self._store.on('error', onError)
function onClose () {
self._close(new Error('IndexedDB database unexpectedly closed'))
}
function onError (err) {
self._close(err)
}
}
IdbChunkStore.prototype.put = function (index, buffer, cb) {
var self = this
if (self.closed) throw new Error('Store is closed')
if (typeof index !== 'number') throw new Error('index must be a number')
if (!Buffer.isBuffer(buffer)) buffer = Buffer.from(buffer)
var isLastChunk = (index === self.lastChunkIndex)
var badLength = (isLastChunk && buffer.length !== self.lastChunkLength) ||
(!isLastChunk && buffer.length !== self.chunkLength)
if (badLength) return nextTick(cb, new Error('Invalid buffer length'))
self._groupPutData[index] = buffer
if (cb) self._groupPutCallbacks.push(cb)
if (self._lastGroupPut + self._groupPutDelay < Date.now()) {
self._groupPut()
} else if (self._groupPutTimeout == null) {
self._groupPutTimeout = setTimeout(self._groupPut.bind(self), self._groupPutDelay)
}
}
IdbChunkStore.prototype._groupPut = function () {
var self = this
if (self.closed) return
var callbacks = self._groupPutCallbacks
var data = self._groupPutData
self._groupPutCallbacks = []
self._groupPutData = {}
self._lastGroupPut = Date.now()
if (self._groupPutTimeout != null) clearTimeout(self._groupPutTimeout)
self._groupPutTimeout = null
var trans = self._store.transaction('readwrite')
for (var i in data) trans.set(Number(i), data[i], noop)
trans.onfinish = function (err) {
for (var j in callbacks) {
callbacks[j](err)
}
}
}
IdbChunkStore.prototype.get = function (index, opts, cb) {
var self = this
if (typeof opts === 'function') return self.get(index, null, opts)
if (typeof cb !== 'function') throw new Error('cb must be a function')
if (self.closed) throw new Error('Store is closed')
if (typeof index !== 'number') throw new Error('index must be a number')
if (!opts) opts = {}
self._store.get(index, function (err, buffer) {
if (err) return cb(err)
if (typeof buffer === 'undefined') {
var e = new Error('Chunk does not exist')
e.name = 'MissingChunkError'
return cb(e)
}
var offset = 'offset' in opts ? opts.offset : 0
var length = 'length' in opts ? opts.length : buffer.length - offset
cb(null, (Buffer.from(buffer)).slice(offset, offset + length))
})
}
IdbChunkStore.prototype.close = function (cb) {
this._close()
nextTick(cb, null)
}
IdbChunkStore.prototype._close = function (err) {
if (this.closed) return
this.closed = true
this._store.close()
this._store = null
this._groupPutData = null
clearTimeout(this._groupPutTimeout)
err = err || new Error('Store is closed')
for (var i in this._groupPutCallbacks) this._groupPutCallbacks[i](err)
this._groupPutCallbacks = null
}
IdbChunkStore.prototype.destroy = function (cb) {
var self = this
if (self.closed) throw new Error('Store is closed')
self._store.clear(noop)
self.close(cb)
}
function nextTick () {
if (arguments[0] != null) {
process.nextTick.apply(this, arguments)
}
}
function noop () {
// do nothing. This prevents idb-kv-store from defaulting to promises
}