1
+ /* eslint-disable antfu/consistent-list-newline */
1
2
// reference from https://github.com/rhashimoto/wa-sqlite/blob/master/demo/file/index.js
2
3
import type { FacadeVFS , Promisable } from '../types'
3
4
import {
@@ -15,79 +16,69 @@ import {
15
16
} from '../constant'
16
17
import { check , ignoredDataView } from './common'
17
18
18
- async function * pagify ( stream : ReadableStream < Uint8Array > ) : AsyncGenerator < Uint8Array > {
19
- const chunks : Uint8Array [ ] = [ ]
20
- const reader = stream . getReader ( )
19
+ const SQLITE_BINARY_HEADER = new Uint8Array ( [
20
+ 0x53 , 0x51 , 0x4C , 0x69 , 0x74 , 0x65 , 0x20 , 0x66 , // SQLite f
21
+ 0x6F , 0x72 , 0x6D , 0x61 , 0x74 , 0x20 , 0x33 , 0x00 , // ormat 3\0
22
+ ] )
23
+
24
+ async function parseHeaderAndVerify (
25
+ reader : ReadableStreamDefaultReader < Uint8Array < ArrayBufferLike > > ,
26
+ ) : Promise < Uint8Array < ArrayBufferLike > > {
27
+ const headerData = await readExactBytes ( reader , 32 )
28
+ for ( let i = 0 ; i < SQLITE_BINARY_HEADER . length ; i ++ ) {
29
+ if ( headerData [ i ] !== SQLITE_BINARY_HEADER [ i ] ) {
30
+ throw new Error ( 'Not a SQLite database file' )
31
+ }
32
+ }
33
+ return headerData
34
+ }
35
+
36
+ async function readExactBytes (
37
+ reader : ReadableStreamDefaultReader < Uint8Array > ,
38
+ size : number ,
39
+ ) : Promise < Uint8Array > {
40
+ const result = new Uint8Array ( size )
41
+ let offset = 0
21
42
22
- // read file header
23
- while ( chunks . reduce ( ( sum , chunk ) => sum + chunk . byteLength , 0 ) < 32 ) {
43
+ while ( offset < size ) {
24
44
const { done, value } = await reader . read ( )
25
45
if ( done ) {
26
46
throw new Error ( 'Unexpected EOF' )
27
47
}
28
- chunks . push ( value ! )
29
- }
30
48
31
- let copyOffset = 0
32
- const header = new DataView ( new ArrayBuffer ( 32 ) )
33
- for ( const chunk of chunks ) {
34
- const src = chunk . subarray ( 0 , header . byteLength - copyOffset )
35
- const dst = new Uint8Array ( header . buffer , copyOffset )
36
- dst . set ( src )
37
- copyOffset += src . byteLength
49
+ const bytesToCopy = Math . min ( size - offset , value ! . length )
50
+ result . set ( value ! . subarray ( 0 , bytesToCopy ) , offset )
51
+ offset += bytesToCopy
38
52
}
39
53
40
- if ( new TextDecoder ( ) . decode ( header . buffer . slice ( 0 , 16 ) ) !== 'SQLite format 3\x00' ) {
41
- throw new Error ( 'Not a SQLite database file' )
42
- }
54
+ return result
55
+ }
43
56
44
- const pageSize = ( field => field === 1 ? 65536 : field ) ( header . getUint16 ( 16 ) )
45
- const pageCount = header . getUint32 ( 28 )
46
- // console.log(`${pageCount} pages, ${pageSize} bytes each, ${pageCount * pageSize} bytes total`)
47
-
48
- // copy pages
49
- for ( let i = 0 ; i < pageCount ; ++ i ) {
50
- while ( chunks . reduce ( ( sum , chunk ) => sum + chunk . byteLength , 0 ) < pageSize ) {
51
- const { done, value } = await reader . read ( )
52
- if ( done ) {
53
- throw new Error ( 'Unexpected EOF' )
54
- }
55
- chunks . push ( value ! )
56
- }
57
+ async function * pagify ( stream : ReadableStream < Uint8Array > ) : AsyncGenerator < Uint8Array > {
58
+ const reader = stream . getReader ( )
57
59
58
- let page : Uint8Array
59
- if ( chunks [ 0 ] ?. byteLength >= pageSize ) {
60
- page = chunks [ 0 ] . subarray ( 0 , pageSize )
61
- chunks [ 0 ] = chunks [ 0 ] . subarray ( pageSize )
62
- if ( ! chunks [ 0 ] . byteLength ) {
63
- chunks . shift ( )
64
- }
65
- } else {
66
- let copyOffset = 0
67
- page = new Uint8Array ( pageSize )
68
- while ( copyOffset < pageSize ) {
69
- const src = chunks [ 0 ] . subarray ( 0 , pageSize - copyOffset )
70
- const dst = new Uint8Array ( page . buffer , copyOffset )
71
- dst . set ( src )
72
- copyOffset += src . byteLength
73
-
74
- chunks [ 0 ] = chunks [ 0 ] . subarray ( src . byteLength )
75
- if ( ! chunks [ 0 ] . byteLength ) {
76
- chunks . shift ( )
77
- }
78
- }
79
- }
60
+ try {
61
+ const headerData = await parseHeaderAndVerify ( reader )
80
62
81
- yield page
82
- }
63
+ const view = new DataView ( headerData . buffer )
64
+ const rawPageSize = view . getUint16 ( 16 )
65
+ const pageSize = rawPageSize === 1 ? 65536 : rawPageSize
66
+ const pageCount = view . getUint32 ( 28 )
67
+
68
+ for ( let i = 0 ; i < pageCount ; i ++ ) {
69
+ yield await readExactBytes ( reader , pageSize )
70
+ }
83
71
84
- const { done } = await reader . read ( )
85
- if ( ! done ) {
86
- throw new Error ( 'Unexpected data after last page' )
72
+ const { done } = await reader . read ( )
73
+ if ( ! done ) {
74
+ throw new Error ( 'Unexpected data after last page' )
75
+ }
76
+ } finally {
77
+ reader . releaseLock ( )
87
78
}
88
79
}
89
80
90
- export async function importDatabaseStream (
81
+ export async function importDatabaseToIdb (
91
82
vfs : FacadeVFS ,
92
83
path : string ,
93
84
stream : ReadableStream < Uint8Array > ,
@@ -129,6 +120,21 @@ export async function importDatabaseStream(
129
120
}
130
121
}
131
122
123
+ async function importDatabaseToOpfs (
124
+ path : string ,
125
+ source : ReadableStream < Uint8Array > ,
126
+ ) : Promise < void > {
127
+ const root = await navigator . storage . getDirectory ( )
128
+ const handle = await root . getFileHandle ( path , { create : true } )
129
+ const [ streamForVerify , streamData ] = source . tee ( )
130
+
131
+ await parseHeaderAndVerify ( streamForVerify . getReader ( ) )
132
+
133
+ const writable = await handle . createWritable ( )
134
+ // `pipeTo()` will auto close `writable`
135
+ await streamData . pipeTo ( writable )
136
+ }
137
+
132
138
/**
133
139
* Import database from `File` or `ReadableStream`
134
140
* @param vfs SQLite VFS
@@ -140,9 +146,11 @@ export async function importDatabase(
140
146
path : string ,
141
147
data : File | ReadableStream < Uint8Array > ,
142
148
) : Promise < void > {
143
- return await importDatabaseStream (
144
- vfs ,
145
- path ,
146
- data instanceof File ? data . stream ( ) : data ,
147
- )
149
+ const stream = data instanceof globalThis . File ? data . stream ( ) : data
150
+ // is `OPFSCoopSyncVFS`
151
+ if ( 'releaser' in vfs ) {
152
+ await importDatabaseToOpfs ( path , stream )
153
+ } else {
154
+ await importDatabaseToIdb ( vfs , path , stream )
155
+ }
148
156
}
0 commit comments