1 | var RandomAccess = require('random-access-storage')
|
2 | var inherits = require('inherits')
|
3 | var nextTick = require('next-tick')
|
4 | var once = require('once')
|
5 | var blocks = require('./lib/blocks.js')
|
6 | var bufferFrom = require('buffer-from')
|
7 | var bufferAlloc = require('buffer-alloc')
|
8 |
|
9 | var DELIM = '\0'
|
10 | var win = typeof window !== 'undefined' ? window
|
11 | : (typeof self !== 'undefined' ? self : {})
|
12 |
|
13 | module.exports = function (dbname, xopts) {
|
14 | if (!xopts) xopts = {}
|
15 | var idb = xopts.idb || (typeof win !== 'undefined'
|
16 | ? win.indexedDB || win.mozIndexedDB
|
17 | || win.webkitIndexedDB || win.msIndexedDB
|
18 | : null)
|
19 | if (!idb) throw new Error('indexedDB not present and not given')
|
20 | var db = null, dbqueue = []
|
21 | if (typeof idb.open === 'function') {
|
22 | var req = idb.open(dbname)
|
23 | req.addEventListener('upgradeneeded', function () {
|
24 | db = req.result
|
25 | db.createObjectStore('data')
|
26 | })
|
27 | req.addEventListener('success', function () {
|
28 | db = req.result
|
29 | dbqueue.forEach(function (cb) { cb(db) })
|
30 | dbqueue = null
|
31 | })
|
32 | } else {
|
33 | db = idb
|
34 | }
|
35 | return function (name, opts) {
|
36 | if (typeof name === 'object') {
|
37 | opts = name
|
38 | name = opts.name
|
39 | }
|
40 |
|
41 | if (!opts) opts = {}
|
42 | opts.name = name
|
43 |
|
44 | return new Store(Object.assign({ db: getdb }, xopts, opts))
|
45 | }
|
46 | function getdb (cb) {
|
47 | if (db) nextTick(function () { cb(db) })
|
48 | else dbqueue.push(cb)
|
49 | }
|
50 | }
|
51 |
|
52 | function Store (opts) {
|
53 | if (!(this instanceof Store)) return new Store(opts)
|
54 | RandomAccess.call(this)
|
55 | if (!opts) opts = {}
|
56 | if (typeof opts === 'string') opts = { name: opts }
|
57 | this.size = opts.size || 4096
|
58 | this.name = opts.name
|
59 | this.length = opts.length || 0
|
60 | this._getdb = opts.db
|
61 | }
|
62 | inherits(Store, RandomAccess)
|
63 |
|
64 | Store.prototype._blocks = function (i, j) {
|
65 | return blocks(this.size, i, j)
|
66 | }
|
67 |
|
68 | Store.prototype._read = function (req) {
|
69 | var self = this
|
70 | var buffers = []
|
71 | self._store('readonly', function (err, store) {
|
72 | if ((self.length || 0) < req.offset+req.size) {
|
73 | return req.callback(new Error('Could not satisfy length'))
|
74 | }
|
75 | if (err) return req.callback(err)
|
76 | var offsets = self._blocks(req.offset, req.offset+req.size)
|
77 | var pending = offsets.length + 1
|
78 | var firstBlock = offsets.length > 0 ? offsets[0].block : 0
|
79 | var j = 0
|
80 | for (var i = 0; i < offsets.length; i++) (function (o) {
|
81 | var key = self.name + DELIM + o.block
|
82 | backify(store.get(key), function (err, ev) {
|
83 | if (err) return req.callback(err)
|
84 | buffers[o.block-firstBlock] = ev.target.result
|
85 | ? bufferFrom(ev.target.result.subarray(o.start,o.end))
|
86 | : bufferAlloc(o.end-o.start)
|
87 | if (--pending === 0) req.callback(null, Buffer.concat(buffers))
|
88 | })
|
89 | })(offsets[i])
|
90 | if (--pending === 0) req.callback(null, Buffer.concat(buffers))
|
91 | })
|
92 | }
|
93 |
|
94 | Store.prototype._write = function (req) {
|
95 | var self = this
|
96 | self._store('readwrite', function (err, store) {
|
97 | if (err) return req.callback(err)
|
98 | var offsets = self._blocks(req.offset, req.offset + req.data.length)
|
99 | var pending = 1
|
100 | var buffers = {}
|
101 | for (var i = 0; i < offsets.length; i++) (function (o,i) {
|
102 | if (o.end-o.start === self.size) return
|
103 | pending++
|
104 | var key = self.name + DELIM + o.block
|
105 | backify(store.get(key), function (err, ev) {
|
106 | if (err) return req.callback(err)
|
107 | buffers[i] = bufferFrom(ev.target.result || bufferAlloc(self.size))
|
108 | if (--pending === 0) write(store, offsets, buffers)
|
109 | })
|
110 | })(offsets[i],i)
|
111 | if (--pending === 0) write(store, offsets, buffers)
|
112 | })
|
113 | function write (store, offsets, buffers) {
|
114 | for (var i = 0, j = 0; i < offsets.length; i++) {
|
115 | var o = offsets[i]
|
116 | var len = o.end - o.start
|
117 | if (len === self.size) {
|
118 | block = bufferFrom(req.data.slice(j, j+len))
|
119 | } else {
|
120 | block = buffers[i]
|
121 | req.data.copy(block, o.start, j, j+len)
|
122 | }
|
123 | store.put(block,self.name + DELIM + o.block)
|
124 | j += len
|
125 | }
|
126 | var length = Math.max(self.length || 0, req.offset + req.data.length)
|
127 | store.put(length, self.name + DELIM + 'length')
|
128 | store.transaction.addEventListener('complete', function () {
|
129 | self.length = length
|
130 | req.callback(null)
|
131 | })
|
132 | store.transaction.addEventListener('error', function (err) {
|
133 | req.callback(err)
|
134 | })
|
135 | }
|
136 | }
|
137 |
|
138 | Store.prototype._store = function (mode, cb) {
|
139 | cb = once(cb)
|
140 | var self = this
|
141 | self._getdb(function (db) {
|
142 | var tx = db.transaction(['data'], mode)
|
143 | var store = tx.objectStore('data')
|
144 | tx.addEventListener('error', cb)
|
145 | cb(null, store)
|
146 | })
|
147 | }
|
148 |
|
149 | Store.prototype._open = function (req) {
|
150 | var self = this
|
151 | this._getdb(function(db) {
|
152 | self._store('readonly', function (err, store) {
|
153 | backify(store.get(self.name + DELIM + "length"), function(err, ev) {
|
154 | self.length = ev.target.result || 0
|
155 | req.callback(null)
|
156 | })
|
157 | })
|
158 | })
|
159 | }
|
160 |
|
161 | Store.prototype._close = function (req) {
|
162 | this._getdb(function (db) {
|
163 | db.close()
|
164 | req.callback()
|
165 | })
|
166 | }
|
167 |
|
168 | Store.prototype._stat = function (req) {
|
169 | var self = this
|
170 | nextTick(function () {
|
171 | req.callback(null, { size: self.length })
|
172 | })
|
173 | }
|
174 |
|
175 | function backify (r, cb) {
|
176 | r.addEventListener('success', function (ev) { cb(null, ev) })
|
177 | r.addEventListener('error', cb)
|
178 | }
|