Skip to content

Commit

Permalink
Fixes #139
Browse files Browse the repository at this point in the history
  • Loading branch information
Tewr committed Jun 14, 2020
1 parent bd326ae commit 2722f10
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 107 deletions.
2 changes: 1 addition & 1 deletion src/Blazor.FileReader/FileReaderJsInterop.Base64Stream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Blazor.FileReader
{
internal partial class FileReaderJsInterop
public partial class FileReaderJsInterop
{
private class Base64Stream : IBase64Stream
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace Blazor.FileReader
{
internal partial class FileReaderJsInterop
public partial class FileReaderJsInterop
{
private class InteropFileStream : AsyncDisposableStream
{
Expand Down
43 changes: 39 additions & 4 deletions src/Blazor.FileReader/FileReaderJsInterop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@

namespace Blazor.FileReader
{
internal partial class FileReaderJsInterop
public partial class FileReaderJsInterop
{
private static readonly IReadOnlyDictionary<string, string> escapeScriptTextReplacements =
new Dictionary<string, string> { { @"\", @"\\" }, { "\r", @"\r" }, { "\n", @"\n" }, { "'", @"\'" }, { "\"", @"\""" } };
private readonly bool _needsInitialization = false;
private readonly IFileReaderServiceOptions _options;
private static long _readFileUnmarshalledCallIdSource;
private static readonly Dictionary<long, TaskCompletionSource<int>> _readFileUnmarshalledCalls
= new Dictionary<long, TaskCompletionSource<int>>();

internal IJSRuntime CurrentJSRuntime { get; }

Expand Down Expand Up @@ -130,19 +133,51 @@ private async Task<int> ReadFileUnmarshalledAsync(
int fileRef, byte[] buffer, long position, long bufferOffset, int count,
CancellationToken cancellationToken)
{

var bytesRead = await Task.Run(() => CurrentJSRuntime.InvokeUnmarshalled<ReadFileParams, int>(
var taskCompletionSource = new TaskCompletionSource<int>();
var id = ++_readFileUnmarshalledCallIdSource;
_readFileUnmarshalledCalls[id] = taskCompletionSource;

await Task.Run(() => CurrentJSRuntime.InvokeUnmarshalled<ReadFileParams, int>(
$"FileReaderComponent.ReadFileUnmarshalledAsync",
new ReadFileParams {
Buffer = buffer,
BufferOffset = bufferOffset,
Count = count,
FileRef = fileRef,
Position = position
Position = position,
TaskId = id
}));

var bytesRead = await taskCompletionSource.Task;
return bytesRead;
}

[JSInvokable(nameof(EndReadFileUnmarshalledAsyncResult))]
public static void EndReadFileUnmarshalledAsyncResult(long taskId, int bytesRead)
{
if (!_readFileUnmarshalledCalls.TryGetValue(taskId, out var taskCompletionSource)) {
Console.Error.WriteLine($"{nameof(EndReadFileUnmarshalledAsyncResult)}: Unknown {nameof(taskId)} '{taskId}'");
return;
}

_readFileUnmarshalledCalls.Remove(taskId);
taskCompletionSource.SetResult(bytesRead);
}


[JSInvokable(nameof(EndReadFileUnmarshalledAsyncError))]
public static void EndReadFileUnmarshalledAsyncError(long taskId, string error)
{
if (!_readFileUnmarshalledCalls.TryGetValue(taskId, out var taskCompletionSource))
{
Console.Error.WriteLine($"{nameof(EndReadFileUnmarshalledAsyncError)}: Unknown {nameof(taskId)} '{taskId}'");
return;
}

_readFileUnmarshalledCalls.Remove(taskId);
taskCompletionSource.SetException(new BrowserFileReaderException(error));
}

private async Task Initialize()
{
var isLoaded = await IsLoaded();
Expand Down
25 changes: 19 additions & 6 deletions src/Blazor.FileReader/ReadFileParams.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,30 @@ namespace Blazor.FileReader

[StructLayout(LayoutKind.Explicit)]
struct ReadFileParams
{
{
[FieldOffset(0)]
internal long BufferOffset;
public long TaskId;

[FieldOffset(8)]
internal int Count;
[FieldOffset(12)]
internal int FileRef;
public long BufferOffset;

[FieldOffset(16)]
public long Position;
public int Count;

[FieldOffset(20)]
public int FileRef;

[FieldOffset(24)]
public long Position;

[FieldOffset(32)]
public byte[] Buffer;
}

public struct ReadFileResult
{
public long TaskId;
public int BytesRead;
public string Error;
}
}
124 changes: 74 additions & 50 deletions src/Blazor.FileReader/script/FileReaderComponent.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
declare const Blazor: IBlazor;
declare const DotNet: IDotNet;

interface IDotNet {
invokeMethodAsync<T>(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise<T>;
}
interface IBlazor {
platform: IBlazorPlatform;
}
Expand All @@ -21,13 +25,19 @@ interface IBlazorPlatform {
}

interface IReadFileParams {
taskId: number;
buffer: System_Array<any>;
bufferOffset: number;
count: number;
fileRef: number;
position: number;
};

interface ReadFileSliceResult {
file: File;
result: string | ArrayBuffer;
}

interface IFileInfo {
name: string;
nonStandardProperties: any;
Expand All @@ -43,7 +53,7 @@ interface IDotNetBuffer {
class FileReaderComponent {

private newFileStreamReference = 0;
private readonly fileStreams: { [reference: number]: { file: File, arrayBuffer: ArrayBuffer } } = {};
private readonly fileStreams: { [reference: number]: File } = {};
private readonly dragElements: Map<HTMLElement, EventListenerOrEventListenerObject> = new Map();
private readonly elementDataTransfers: Map<HTMLElement, FileList> = new Map();

Expand Down Expand Up @@ -124,7 +134,7 @@ class FileReaderComponent {
return 0;
};

public GetFileInfoFromElement = (element: HTMLElement, index: number, property: string): IFileInfo => {
public GetFileInfoFromElement = (element: HTMLElement, index: number): IFileInfo => {
this.LogIfNull(element);
const files = this.GetFiles(element);
if (!files) {
Expand All @@ -144,15 +154,15 @@ class FileReaderComponent {
}

public GetFileInfoFromFile(file: File): IFileInfo {
var result = {
const result = {
lastModified: file.lastModified,
name: file.name,
nonStandardProperties: null,
size: file.size,
type: file.type
};
var properties: any = new Object();
for (let property in file) {
const properties: { [propertyName: string]: object } = {};
for (const property in file) {
if (Object.getPrototypeOf(file).hasOwnProperty(property) && !(property in result)) {
properties[property] = file[property];
}
Expand All @@ -161,74 +171,88 @@ class FileReaderComponent {
return result;
}

public OpenRead = (element: HTMLElement, fileIndex: number): Promise<number> => {
public OpenRead = (element: HTMLElement, fileIndex: number): number => {
this.LogIfNull(element);
return new Promise<number>((resolve, reject) => {
const files = this.GetFiles(element);
if (!files) {
throw 'No FileList available.';
}
const file = files.item(fileIndex);
if (!file) {
throw `No file with index ${fileIndex} available.`;
}

const fileRef: number = this.newFileStreamReference++;
const reader = new FileReader();
reader.onload = ((r) => {
return () => {
try {
const arrayBuffer: ArrayBuffer = r.result as ArrayBuffer;
this.fileStreams[fileRef] = { file, arrayBuffer };

resolve(fileRef);
} catch (e) {
reject(e);
}
}
})(reader);
reader.readAsArrayBuffer(file);

const files = this.GetFiles(element);
if (!files) {
throw 'No FileList available.';
}
const file = files.item(fileIndex);
if (!file) {
throw `No file with index ${fileIndex} available.`;
}

return fileRef;
});
const fileRef: number = this.newFileStreamReference++;
this.fileStreams[fileRef] = file;
return fileRef;

}
public ReadFileParamsPointer = (readFileParamsPointer: Pointer): IReadFileParams => {
return {
bufferOffset: Blazor.platform.readUint64Field(readFileParamsPointer, 0),
count: Blazor.platform.readInt32Field(readFileParamsPointer, 8),
fileRef: Blazor.platform.readInt32Field(readFileParamsPointer, 12),
position: Blazor.platform.readUint64Field(readFileParamsPointer, 16),
buffer: Blazor.platform.readInt32Field(readFileParamsPointer, 24) as unknown as System_Array<any>
taskId: Blazor.platform.readUint64Field(readFileParamsPointer, 0),
bufferOffset: Blazor.platform.readUint64Field(readFileParamsPointer, 8),
count: Blazor.platform.readInt32Field(readFileParamsPointer, 16),
fileRef: Blazor.platform.readInt32Field(readFileParamsPointer, 20),
position: Blazor.platform.readUint64Field(readFileParamsPointer, 24),
buffer: Blazor.platform.readInt32Field(readFileParamsPointer, 32) as unknown as System_Array<any>
};
}

public ReadFileUnmarshalledAsync = (readFileParamsPointer: Pointer) => {
const readFileParams = this.ReadFileParamsPointer(readFileParamsPointer);
const fileStream = this.fileStreams[readFileParams.fileRef];
const dotNetBuffer = readFileParams.buffer;
const dotNetBufferView: Uint8Array = Blazor.platform.toUint8Array(dotNetBuffer);
const byteCount = Math.min(fileStream.arrayBuffer.byteLength - readFileParams.position, readFileParams.count);
dotNetBufferView.set(new Uint8Array(fileStream.arrayBuffer, readFileParams.position, byteCount), readFileParams.bufferOffset);
return byteCount;

const asyncCall = new Promise<number>((resolve, reject) => {
return this.ReadFileSlice(readFileParams, (r,b) => r.readAsArrayBuffer(b))
.then(r => {
try {
const dotNetBufferView = Blazor.platform.toUint8Array(readFileParams.buffer);
const arrayBuffer = r.result as ArrayBuffer;
dotNetBufferView.set(new Uint8Array(arrayBuffer), readFileParams.bufferOffset);

const byteCount = Math.min(arrayBuffer.byteLength, readFileParams.count);
resolve(byteCount);
} catch (e) {
reject(e);
}
}, e => reject(e));
});

asyncCall.then(
byteCount => DotNet.invokeMethodAsync("Blazor.FileReader", "EndReadFileUnmarshalledAsyncResult", readFileParams.taskId, byteCount),
error => {
console.error("ReadFileUnmarshalledAsync error", error);
DotNet.invokeMethodAsync("Blazor.FileReader", "EndReadFileUnmarshalledAsyncError", readFileParams.taskId, error.toString());
});
}

public ReadFileMarshalledAsync = (readFileParams: IReadFileParams): Promise<string> => {

return new Promise<string>((resolve, reject) => {
const file: File = this.fileStreams[readFileParams.fileRef].file;
return this.ReadFileSlice(readFileParams, (r,b) => r.readAsDataURL(b))
.then(r => {
const contents = r.result as string;
const data = contents ? contents.split(";base64,")[1] : null;
resolve(data);
}, e => reject(e));
});
}


private ReadFileSlice = (readFileParams: IReadFileParams, method: (reader: FileReader, blob: Blob) => void): Promise<ReadFileSliceResult> => {
return new Promise<ReadFileSliceResult>((resolve, reject) => {
const file: File = this.fileStreams[readFileParams.fileRef];
try {
const reader = new FileReader();
reader.onload = ((r) => {
return () => {
try {
const contents = r.result as string;
const data = contents ? contents.split(";base64,")[1] : null;
resolve(data);
resolve({result: r.result, file: file });
} catch (e) {
reject(e);
}
}
})(reader);
reader.readAsDataURL(file.slice(readFileParams.position, readFileParams.position + readFileParams.count));
method(reader, file.slice(readFileParams.position, readFileParams.position + readFileParams.count));
} catch (e) {
reject(e);
}
Expand Down
Loading

0 comments on commit 2722f10

Please sign in to comment.