KristofferStrube / Blazor.FileSystemAccess

A Blazor wrapper for the File System Access browser API.
https://kristofferstrube.github.io/Blazor.FileSystemAccess/
MIT License
327 stars 39 forks source link

Feature: Support non-text file reading #19

Closed fixnil closed 2 years ago

fixnil commented 2 years ago

Hi @KristofferStrube, I've recently had some issues reading non-text files into the browser.

Expect an API like this:

var handle = await directory.GetFileHandleAsync("sample.db");

var file = await handle.GetFileAsync();

var buffer = new byte[file.Size];

file.Read(buffer, 0, buffer.Length);

or like this:

var handle = await directory.GetFileHandleAsync("sample.db");

var file = await handle.GetFileAsync();

var reader = new FileReader(file);

var buffer = new byte[reader.Size];

reader.Read(buffer, 0, buffer.Length);

Thank you for your work!

KristofferStrube commented 2 years ago

Hi @fixnil, 😊 Thanks for the issue.

This is related to issue #10 which is closed; you can read more about it there. As mentioned in the first response to that issue the File System Access API doesn't give a way to read a file as a Uint8Array. A Blob can be read as an ArrayBuffer but that is not directly convertible to a byte array. So to do this you would need to call a JS method through JSInterop and go through a couple of steps as described in the last comment of that thread.

In release 1.1.0 I added the possibility to stream to a FileSystemWritableFileStream by having it extend Stream that has writing enabled. I would like it to be possible to do the same for Blob in order to be able to stream from it by again extending Stream with reading enabled.

I am unsure whether this is inside this library's intended scope as it is a lot of work with the JS Blob which is a part of the File API. A full wrapper of the File API should probably be its own library so that it can be used by other libraries/projects that would use that functionality without depending on this library.

I am leaving this issue open so that I can track interest in solving this. I haven't decided whether or not this should be a part of this library or potentially spun out to its own library (Blazor.File) that this library (Blazor.FileSystemAccess) could reference.

I have noted a couple of libraries that also work with JSInterop and Files which might be a help for future work.

fixnil commented 2 years ago

Sorry, I forgot to check those closed issues.

Your solution works perfectly, my code is as follows:

file.js

export async function arrayBuffer(blob) {

  var buffer = await blob.arrayBuffer();

  var bytes = new Uint8Array(buffer);

  return bytes;
}

Index.razor

var file = await _handle.GetFileAsync();

var module = await this.JSRuntime.InvokeAsync<IJSObjectReference>("import", "./file.js");

var bytes = await module.InvokeAsync<byte[]>("arrayBuffer", file.JSReference);

await System.IO.File.WriteAllBytesAsync(Filename, bytes);

I don't know if I need to submit a PR as an alternative, before the official File API goes live? The code looks like this:

Blazor.FileSystemAccess/src/KristofferStrube.Blazor.FileSystemAccess/Blob/Blob.cs

// ...
public class Blob
{
    // ...

    public async Task<byte[]> ArrayBufferAsync()
    {
        return await helper.InvokeAsync<byte[]>("arrayBuffer", JSReference);
    }
}
fixnil commented 2 years ago

By the way, I'll mention a small problem I found while using it.

As you can see the code above, I had to use the fully qualified name because of the conflict.

await System.IO.File.WriteAllBytesAsync(Filename, bytes);

Is there a better solution here? Or is there another issue that needs to be created to discuss this.

KristofferStrube commented 2 years ago

Hey @fixnil,

Sounds good. You can create a PR with the solution if you want. I think it is pretty nice. If you add it to the existing helper js file and add the method to the Blob class as you described then that would be awesome.

If you submit a PR. Then I would like you to also create an example use case and add that to the demo on a new page. I imagine that use cases are related to loading binary files that have some specific encoding. If you can't figure out a demo then I can also help with that once you have a PR out.

Regarding your problem with having to use the full namespace then I can recommend to either use using static System.IO.File or adding an alias for it using SIOF = System.IO.File.

KristofferStrube commented 2 years ago

And once we have a PR for this issue we can close it and I can create a new issue specifically for having Blob extend Stream and consider whether we want that in that issue.

fixnil commented 2 years ago

Hi @KristofferStrube,

I've added the method to the blob and added a simple case to prove it works, please review the code if you have time.

Thanks.