-
Notifications
You must be signed in to change notification settings - Fork 6
/
index.js
168 lines (137 loc) · 3.94 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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
const queueMicrotask = require('queue-microtask')
class WebFsChunkStore {
constructor (chunkLength, opts = {}) {
this.chunkLength = Number(chunkLength)
if (!this.chunkLength) {
throw new Error('First argument must be a chunk length')
}
this.closed = false
this.length = Number(opts.length) || Infinity
if (this.length !== Infinity) {
this.lastChunkLength = this.length % this.chunkLength || this.chunkLength
this.lastChunkIndex = Math.ceil(this.length / this.chunkLength) - 1
}
this.name = opts.name || 'default'
this.rootDirPromise = opts.rootDir || navigator.storage.getDirectory()
this.storageDirPromise = this._getStorageDirectoryHandle()
this.chunks = []
}
async _getStorageDirectoryHandle () {
const rootDir = await this.rootDirPromise
return await rootDir.getDirectoryHandle(this.name, { create: true })
}
async _getChunk (index) {
let chunk = this.chunks[index]
if (!chunk) {
const fileName = index.toString()
const storageDir = await this.storageDirPromise
chunk = this.chunks[index] = {
fileHandlePromise: storageDir.getFileHandle(fileName, { create: true })
}
}
return chunk
}
put (index, buf, cb = () => {}) {
if (this.closed) {
queueMicrotask(() => cb(new Error('Storage is closed')))
return
}
const isLastChunk = index === this.lastChunkIndex
if (isLastChunk && buf.length !== this.lastChunkLength) {
queueMicrotask(() => {
cb(new Error(`Last chunk length must be ${this.lastChunkLength}`))
})
return
}
if (!isLastChunk && buf.length !== this.chunkLength) {
queueMicrotask(() => {
cb(new Error(`Chunk length must be ${this.chunkLength}`))
})
return
}
;(async () => {
try {
const chunk = await this._getChunk(index)
const fileHandle = await chunk.fileHandlePromise
const stream = await fileHandle.createWritable({
keepExistingData: false
})
await stream.write(buf)
await stream.close()
} catch (err) {
cb(err)
return
}
cb(null)
})()
}
get (index, opts, cb = () => {}) {
if (typeof opts === 'function') {
return this.get(index, null, opts)
}
if (this.closed) {
queueMicrotask(() => cb(new Error('Storage is closed')))
return
}
const isLastChunk = index === this.lastChunkIndex
const chunkLength = isLastChunk ? this.lastChunkLength : this.chunkLength
if (!opts) opts = {}
const offset = opts.offset || 0
const len = opts.length || chunkLength - offset
;(async () => {
let buf
try {
const chunk = await this._getChunk(index)
const fileHandle = await chunk.fileHandlePromise
let file = await fileHandle.getFile()
if (offset !== 0 || len !== chunkLength) {
file = file.slice(offset, len + offset)
}
buf = await file.arrayBuffer()
} catch (err) {
cb(err)
return
}
if (buf.byteLength === 0) {
const err = new Error(`Index ${index} does not exist`)
err.notFound = true
cb(err)
return
}
cb(null, Buffer.from(buf))
})()
}
close (cb = () => {}) {
if (this.closed) {
queueMicrotask(() => cb(new Error('Storage is closed')))
return
}
this.closed = true
this.chunks = []
queueMicrotask(() => {
cb(null)
})
}
destroy (cb = () => {}) {
if (this.closed) {
queueMicrotask(() => cb(new Error('Storage is closed')))
return
}
const handleClose = async err => {
if (err) {
cb(err)
return
}
try {
const rootDir = await this.rootDirPromise
await rootDir.removeEntry(this.name, { recursive: true })
} catch (err) {
cb(err)
return
}
cb(null)
}
this.close(handleClose)
}
}
module.exports = WebFsChunkStore