blob: ac6c781403f481311b8120af3b30e606cc5c5137 [file] [log] [blame]
/**
* @license
* Copyright 2022 The Emscripten Authors
* SPDX-License-Identifier: MIT
*/
addToLibrary({
$wasmFS$JSMemoryRanges: {},
// Fetch backend: On first access of the file (either a read or a getSize), it
// will fetch() the data from the network asynchronously. Otherwise, after
// that fetch it behaves just like JSFile (and it reuses the code from there).
_wasmfs_create_fetch_backend_js__deps: [
'$wasmFS$backends',
'$wasmFS$JSMemoryRanges',
'_wasmfs_fetch_get_file_url',
'_wasmfs_fetch_get_chunk_size',
],
_wasmfs_create_fetch_backend_js: async function(backend) {
// Get a promise that fetches the data and stores it in JS memory (if it has
// not already been fetched).
async function getFileRange(file, offset, len) {
var url = '';
var fileUrl_p = __wasmfs_fetch_get_file_url(file);
var fileUrl = UTF8ToString(fileUrl_p);
var isAbs = fileUrl.indexOf('://') !== -1;
if (isAbs) {
url = fileUrl;
} else {
try {
var u = new URL(fileUrl, self.location.origin);
url = u.toString();
} catch (_e) {
throw {status: 404};
}
}
var chunkSize = __wasmfs_fetch_get_chunk_size(file);
offset ??= 0;
len ??= chunkSize;
// In which chunk does the seeked range start? E.g., 5-14 with chunksize 8 will start in chunk 0.
if (!(file in wasmFS$JSMemoryRanges)) {
var fileInfo = await fetch(url, {method:'HEAD', headers:{'Range': 'bytes=0-'}});
if (fileInfo.ok &&
fileInfo.headers.has('Content-Length') &&
fileInfo.headers.get('Accept-Ranges') == 'bytes' &&
(parseInt(fileInfo.headers.get('Content-Length'), 10) > chunkSize*2)) {
var size = parseInt(fileInfo.headers.get('Content-Length'), 10);
wasmFS$JSMemoryRanges[file] = {
size,
chunks: [],
chunkSize: chunkSize
};
len = Math.min(len, size-offset);
} else {
// may as well/forced to download the whole file
var wholeFileReq = await fetch(url);
if (!wholeFileReq.ok) {
throw wholeFileReq;
}
var wholeFileData = new Uint8Array(await wholeFileReq.arrayBuffer());
var text = new TextDecoder().decode(wholeFileData);
wasmFS$JSMemoryRanges[file] = {
size: wholeFileData.byteLength,
chunks: [wholeFileData],
chunkSize: wholeFileData.byteLength
};
return;
}
}
var firstChunk = (offset / chunkSize) | 0;
// 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).
// This will always give us a chunk >= firstChunk since len > 0.
var lastChunk = ((offset+len-1) / chunkSize) | 0;
var allPresent = true;
var i;
// Do we have all the chunks already? If so, we don't need to do any fetches.
for (i = firstChunk; i <= lastChunk; i++) {
if (!wasmFS$JSMemoryRanges[file].chunks[i]) {
allPresent = false;
break;
}
}
if (allPresent) {
// The data is already here, so nothing to do before we continue on to
// the actual read.
return;
}
// This is the first time we want the chunks' data. We'll make
// one request for all the chunks we need, rather than one
// request per chunk.
var start = firstChunk * chunkSize;
// We must fetch *up to* the last byte of the last chunk.
var end = (lastChunk+1) * chunkSize;
var response = await fetch(url, {headers:{'Range': `bytes=${start}-${end-1}`}});
if (!response.ok) {
throw response;
}
var bytes = await response['bytes']();
for (i = firstChunk; i <= lastChunk; i++) {
wasmFS$JSMemoryRanges[file].chunks[i] = bytes.slice(i*chunkSize-start,(i+1)*chunkSize-start);
}
}
wasmFS$backends[backend] = {
// alloc/free operations are not actually async. Just forward to the
// parent class, but we must return a Promise as the caller expects.
allocFile: async (file) => { /* nop */ },
freeFile: async (file) => {
// free memory
wasmFS$JSMemoryRanges[file] = undefined;
},
write: async (file, buffer, length, offset) => {
console.error('TODO: file writing in fetch backend? read-only for now');
},
// read/getSize fetch the data, then forward to the parent class.
read: async (file, buffer, length, offset) => {
// This function assumes that offset is non-negative and length is positive.
// C read() doesn't take an offset and so doesn't have to deal with the former situation,
// and if the length is 0 or the offset is negative there's no reasonable read we can make.
if (offset < 0 || length <= 0) {
return 0;
}
try {
await getFileRange(file, offset || 0, length);
} catch (failedResponse) {
return failedResponse.status === 404 ? -{{{ cDefs.ENOENT }}} : -{{{ cDefs.EBADF }}};
}
var fileInfo = wasmFS$JSMemoryRanges[file];
length = Math.min(length, fileInfo.size-offset);
// As above, we check the length just in case offset was beyond size and length is now negative.
if (length <= 0) {
return 0;
}
var chunks = fileInfo.chunks;
var chunkSize = fileInfo.chunkSize;
var firstChunk = (offset / chunkSize) | 0;
// See comments in getFileRange.
var lastChunk = ((offset+length-1) / chunkSize) | 0;
var readLength = 0;
for (var i = firstChunk; i <= lastChunk; i++) {
var chunk = chunks[i];
var start = Math.max(i*chunkSize, offset);
var chunkStart = i*chunkSize;
var end = Math.min(chunkStart+chunkSize, offset+length);
HEAPU8.set(chunk.subarray(start-chunkStart, end-chunkStart), buffer+(start-offset));
readLength = end - offset;
}
return readLength;
},
getSize: async (file) => {
try {
await getFileRange(file, 0, 0);
} catch (failedResponse) {
return 0;
}
return wasmFS$JSMemoryRanges[file].size;
},
};
},
});