5
5
*/
6
6
7
7
addToLibrary ( {
8
+ $wasmFS$JSMemoryRanges : { } ,
9
+
8
10
// Fetch backend: On first access of the file (either a read or a getSize), it
9
11
// will fetch() the data from the network asynchronously. Otherwise, after
10
12
// that fetch it behaves just like JSFile (and it reuses the code from there).
11
13
12
14
_wasmfs_create_fetch_backend_js__deps : [
13
15
'$wasmFS$backends' ,
14
- '$wasmFS$JSMemoryFiles ' ,
15
- '_wasmfs_create_js_file_backend_js ' ,
16
- '_wasmfs_fetch_get_file_path ' ,
16
+ '$wasmFS$JSMemoryRanges ' ,
17
+ '_wasmfs_fetch_get_file_url ' ,
18
+ '_wasmfs_fetch_get_chunk_size ' ,
17
19
] ,
18
20
_wasmfs_create_fetch_backend_js : async function ( backend ) {
19
21
// Get a promise that fetches the data and stores it in JS memory (if it has
20
22
// not already been fetched).
21
- async function getFile ( file ) {
22
- if ( wasmFS$JSMemoryFiles [ file ] ) {
23
- // The data is already here, so nothing to do before we continue on to
24
- // the actual read below.
25
- return Promise . resolve ( ) ;
26
- }
27
- // This is the first time we want the file's data.
23
+ async function getFileRange ( file , offset , len ) {
28
24
var url = '' ;
29
- var fileUrl_p = __wasmfs_fetch_get_file_path ( file ) ;
25
+ var fileUrl_p = __wasmfs_fetch_get_file_url ( file ) ;
30
26
var fileUrl = UTF8ToString ( fileUrl_p ) ;
31
27
var isAbs = fileUrl . indexOf ( '://' ) !== - 1 ;
32
28
if ( isAbs ) {
@@ -35,55 +31,127 @@ addToLibrary({
35
31
try {
36
32
var u = new URL ( fileUrl , self . location . origin ) ;
37
33
url = u . toString ( ) ;
38
- } catch ( e ) {
34
+ } catch ( _e ) {
35
+ throw { status : 404 } ;
39
36
}
40
37
}
41
- var response = await fetch ( url ) ;
42
- if ( response . ok ) {
43
- var buffer = await response [ 'arrayBuffer' ] ( ) ;
44
- wasmFS$JSMemoryFiles [ file ] = new Uint8Array ( buffer ) ;
45
- } else {
38
+ var chunkSize = __wasmfs_fetch_get_chunk_size ( file ) ;
39
+ offset ??= 0 ;
40
+ len ??= chunkSize ;
41
+ // In which chunk does the seeked range start? E.g., 5-14 with chunksize 8 will start in chunk 0.
42
+ var firstChunk = ( offset / chunkSize ) | 0 ;
43
+ // In which chunk does the seeked range end? E.g., 5-14 with chunksize 8 will end in chunk 1, as will 5-16 (since byte 16 isn't requested).
44
+ // This will always give us a chunk >= firstChunk since len > 0.
45
+ var lastChunk = ( ( offset + len - 1 ) / chunkSize ) | 0 ;
46
+ if ( ! ( file in wasmFS$JSMemoryRanges ) ) {
47
+ var fileInfo = await fetch ( url , { method :'HEAD' , headers :{ 'Range' : 'bytes=0-' } } ) ;
48
+ if ( fileInfo . ok &&
49
+ fileInfo . headers . has ( 'Content-Length' ) &&
50
+ fileInfo . headers . get ( 'Accept-Ranges' ) == 'bytes' &&
51
+ ( parseInt ( fileInfo . headers . get ( 'Content-Length' ) , 10 ) > chunkSize * 2 ) ) {
52
+ wasmFS$JSMemoryRanges [ file ] = {
53
+ size : parseInt ( fileInfo . headers . get ( 'Content-Length' ) , 10 ) ,
54
+ chunks : [ ] ,
55
+ chunkSize : chunkSize
56
+ } ;
57
+ } else {
58
+ // may as well/forced to download the whole file
59
+ var wholeFileReq = await fetch ( url ) ;
60
+ if ( ! wholeFileReq . ok ) {
61
+ throw wholeFileReq ;
62
+ }
63
+ var wholeFileData = new Uint8Array ( await wholeFileReq . arrayBuffer ( ) ) ;
64
+ var text = new TextDecoder ( ) . decode ( wholeFileData ) ;
65
+ wasmFS$JSMemoryRanges [ file ] = {
66
+ size : wholeFileData . byteLength ,
67
+ chunks : [ wholeFileData ] ,
68
+ chunkSize : wholeFileData . byteLength
69
+ } ;
70
+ return Promise . resolve ( ) ;
71
+ }
72
+ }
73
+ var allPresent = true ;
74
+ var i ;
75
+ // Do we have all the chunks already? If so, we don't need to do any fetches.
76
+ for ( i = firstChunk ; i <= lastChunk ; i ++ ) {
77
+ if ( ! wasmFS$JSMemoryRanges [ file ] . chunks [ i ] ) {
78
+ allPresent = false ;
79
+ break ;
80
+ }
81
+ }
82
+ if ( allPresent ) {
83
+ // The data is already here, so nothing to do before we continue on to
84
+ // the actual read.
85
+ return Promise . resolve ( ) ;
86
+ }
87
+ // This is the first time we want the chunks' data. We'll make
88
+ // one request for all the chunks we need, rather than one
89
+ // request per chunk.
90
+ var start = firstChunk * chunkSize ;
91
+ // We must fetch *up to* the last byte of the last chunk.
92
+ var end = ( lastChunk + 1 ) * chunkSize ;
93
+ var response = await fetch ( url , { headers :{ 'Range' : `bytes=${ start } -${ end - 1 } ` } } ) ;
94
+ if ( ! response . ok ) {
46
95
throw response ;
47
96
}
97
+ var bytes = await response [ 'bytes' ] ( ) ;
98
+ for ( i = firstChunk ; i <= lastChunk ; i ++ ) {
99
+ wasmFS$JSMemoryRanges [ file ] . chunks [ i ] = bytes . slice ( i * chunkSize - start , ( i + 1 ) * chunkSize - start ) ;
100
+ }
101
+ return Promise . resolve ( ) ;
48
102
}
49
103
50
- // Start with the normal JSFile operations. This sets
51
- // wasmFS$backends[backend]
52
- // which we will then augment.
53
- __wasmfs_create_js_file_backend_js ( backend ) ;
54
-
55
- // Add the async operations on top.
56
- var jsFileOps = wasmFS$backends [ backend ] ;
57
104
wasmFS$backends [ backend ] = {
58
105
// alloc/free operations are not actually async. Just forward to the
59
106
// parent class, but we must return a Promise as the caller expects.
60
107
allocFile : async ( file ) => {
61
- jsFileOps . allocFile ( file ) ;
108
+ // nop
62
109
return Promise . resolve ( ) ;
63
110
} ,
64
111
freeFile : async ( file ) => {
65
- jsFileOps . freeFile ( file ) ;
112
+ // free memory
113
+ wasmFS$JSMemoryRanges [ file ] = undefined ;
66
114
return Promise . resolve ( ) ;
67
115
} ,
68
116
69
117
write : async ( file , buffer , length , offset ) => {
70
- abort ( " TODO: file writing in fetch backend? read-only for now" ) ;
118
+ console . error ( ' TODO: file writing in fetch backend? read-only for now' ) ;
71
119
} ,
72
120
73
121
// read/getSize fetch the data, then forward to the parent class.
74
122
read : async ( file , buffer , length , offset ) => {
123
+ if ( length == 0 ) {
124
+ return 0 ;
125
+ }
75
126
try {
76
- await getFile ( file ) ;
77
- } catch ( response ) {
78
- return response . status === 404 ? - { { { cDefs . ENOENT } } } : - { { { cDefs. EBADF } } } ;
127
+ await getFileRange ( file , offset || 0 , length ) ;
128
+ } catch ( failedResponse ) {
129
+ return failedResponse . status === 404 ? - { { { cDefs . ENOENT } } } : - { { { cDefs. EBADF } } } ;
79
130
}
80
- return jsFileOps . read ( file, buffer, length, offset) ;
131
+ var fileInfo = wasmFS$JSMemoryRanges [ file ] ;
132
+ var chunks = fileInfo . chunks ;
133
+ var chunkSize = fileInfo . chunkSize ;
134
+ var firstChunk = ( offset / chunkSize ) | 0 ;
135
+ // See comments in getFileRange.
136
+ var lastChunk = ( ( offset + length - 1 ) / chunkSize ) | 0 ;
137
+ var readLength = 0 ;
138
+ for ( var i = firstChunk ; i <= lastChunk ; i ++ ) {
139
+ var chunk = chunks [ i ] ;
140
+ var start = Math . max ( i * chunkSize , offset ) ;
141
+ var chunkStart = i * chunkSize ;
142
+ var end = Math . min ( chunkStart + chunkSize , offset + length ) ;
143
+ HEAPU8 . set ( chunk . subarray ( start - chunkStart , end - chunkStart ) , buffer + ( start - offset ) ) ;
144
+ readLength = end - offset ;
145
+ }
146
+ return readLength ;
81
147
} ,
82
148
getSize : async ( file ) => {
83
149
try {
84
- await getFile ( file ) ;
85
- } catch ( response ) { }
86
- return jsFileOps . getSize ( file ) ;
150
+ await getFileRange ( file , 0 , 0 ) ;
151
+ } catch ( failedResponse ) {
152
+ return 0 ;
153
+ }
154
+ return wasmFS$JSMemoryRanges [ file ] . size ;
87
155
} ,
88
156
} ;
89
157
} ,
0 commit comments