I am trying to port some part of a desktop application to be able to run in the browser (client-side). I need a sort of virtual file system, in which I can read and write files (binary data). From what I gather, one of the only options that works broadly across browsers is IndexedDB. However, I'm kind of alienated trying to find examples that read or write larger files. It seems the API only supports passing/obtaining an entire file contents to/from the database (a blob or byte array).
What I'm trying to find, is something in which I can continuously "stream" so to speak the data to/from the virtual file system, analoguous to how you do it on any other non-browser application. E.g. (pseudo code)
val in = new FileInputStream(someURLorPath)
val chunkSize = 4096
val buf = new Array[Byte](chunkSize)
while (in.hasRemaining) {
val sz = min(chunkSize, in.remaining)
in.read(buf, 0, sz)
processSome(buf, 0, sz)
...
)
in.close()
I understand synchronous API is a problem for browsers; it would also be ok, if read
was an asynchronous method instead. But I want to go through the file - which can be huge, e.g. several 100 MB - block by block. The block size doesn't matter. This goes both for reading and for writing.
Random-access (being able to seek to a position within the virtual file) would be a plus, but not mandatory.
One idea I have is that one store = one virtual file, and then the keys are chunk indices? A bit like the cursor example on MDN, but each record is a blob or array of a fixed size. Does that make sense? Is there a better API or approach?
It seems that Streams would conceptually be the API I'm looking for, but I don't know how to "stream to/from" a virtual file system such as IndexedDB.
Assuming you want the ability to transparently work with initially remote resources which are cached (and consistent) locally, you can abstract over
fetch
(withRange:
requests) andIndexedDB
.BTW, you'll really want to use TypeScript for this, because working with
Promise<T>
in pure JavaScript is a PITA.Something like this..
I cobbled this together from MDN's docs - I haven't tested it, but I hope it put you in the right direction:
Part 1 -
LocalFileStore
These classes allow you to store arbitrary binary data in in chunks of 4096 bytes, where each chunk is represented by an
ArrayBuffer
.The IndexedDB API is confusing at first, as it doesn't use native ECMAScript
Promise<T>
s but instead its ownIDBRequest
-API and with oddly named properties - but the gist of it is:'files'
holds all of the files cached locally.IDBObjectStore
instance.IDBObjectStore
, where thekey
is the4096
-aligned offset into the file.IDBTransaction
context, hence whyclass LocalFile
wraps aIDBTransaction
object rather than anIDBObjectStore
object.Part 2:
AbstractFile
LocalFileStore
andLocalFile
classes above and also usesfetch
.LocalFileStore
; if it has the necessary chunks then it will retrieve them.fetch
with aRange:
header, and cache those chunks locally.Part 3 - Streams?
As the classes above use
ArrayBuffer
, you can make-use of existingArrayBuffer
functionality to create a Stream-compatible or Stream-like representation - it will have to be asynchronous of course, butasync
+await
make that easy. You could write a generator-function (aka iterator) that simply yields each chunk asynchronously.