forked from SteveSandersonMS/BlazorInputFile
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 69c82ba
Showing
35 changed files
with
1,322 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.vs/ | ||
bin/ | ||
obj/ | ||
*.user | ||
samples/Sample.WebAssembly/wwwroot/_content/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 16 | ||
VisualStudioVersion = 16.0.29230.61 | ||
MinimumVisualStudioVersion = 15.0.26124.0 | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorInputFile", "BlazorInputFile\BlazorInputFile.csproj", "{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}" | ||
EndProject | ||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{EEEC40BE-3F2B-4B6E-9040-71FF3DC274AA}" | ||
EndProject | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Server", "samples\Sample.Server\Sample.Server.csproj", "{BE1CED5B-9590-4170-BB54-822555E920CF}" | ||
EndProject | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.WebAssembly", "samples\Sample.WebAssembly\Sample.WebAssembly.csproj", "{28B157AE-2F2D-4438-8493-3FE9148E442B}" | ||
EndProject | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Core", "samples\Sample.Core\Sample.Core.csproj", "{5D825DE4-4A0D-439F-A3BB-F16B33461F16}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Debug|x64 = Debug|x64 | ||
Debug|x86 = Debug|x86 | ||
Release|Any CPU = Release|Any CPU | ||
Release|x64 = Release|x64 | ||
Release|x86 = Release|x86 | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Debug|x64.Build.0 = Debug|Any CPU | ||
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Debug|x86.Build.0 = Debug|Any CPU | ||
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Release|x64.ActiveCfg = Release|Any CPU | ||
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Release|x64.Build.0 = Release|Any CPU | ||
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Release|x86.ActiveCfg = Release|Any CPU | ||
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Release|x86.Build.0 = Release|Any CPU | ||
{BE1CED5B-9590-4170-BB54-822555E920CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{BE1CED5B-9590-4170-BB54-822555E920CF}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{BE1CED5B-9590-4170-BB54-822555E920CF}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
{BE1CED5B-9590-4170-BB54-822555E920CF}.Debug|x64.Build.0 = Debug|Any CPU | ||
{BE1CED5B-9590-4170-BB54-822555E920CF}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
{BE1CED5B-9590-4170-BB54-822555E920CF}.Debug|x86.Build.0 = Debug|Any CPU | ||
{BE1CED5B-9590-4170-BB54-822555E920CF}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{BE1CED5B-9590-4170-BB54-822555E920CF}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{BE1CED5B-9590-4170-BB54-822555E920CF}.Release|x64.ActiveCfg = Release|Any CPU | ||
{BE1CED5B-9590-4170-BB54-822555E920CF}.Release|x64.Build.0 = Release|Any CPU | ||
{BE1CED5B-9590-4170-BB54-822555E920CF}.Release|x86.ActiveCfg = Release|Any CPU | ||
{BE1CED5B-9590-4170-BB54-822555E920CF}.Release|x86.Build.0 = Release|Any CPU | ||
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Debug|x64.Build.0 = Debug|Any CPU | ||
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Debug|x86.Build.0 = Debug|Any CPU | ||
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Release|x64.ActiveCfg = Release|Any CPU | ||
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Release|x64.Build.0 = Release|Any CPU | ||
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Release|x86.ActiveCfg = Release|Any CPU | ||
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Release|x86.Build.0 = Release|Any CPU | ||
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Debug|x64.Build.0 = Debug|Any CPU | ||
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Debug|x86.Build.0 = Debug|Any CPU | ||
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Release|x64.ActiveCfg = Release|Any CPU | ||
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Release|x64.Build.0 = Release|Any CPU | ||
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Release|x86.ActiveCfg = Release|Any CPU | ||
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Release|x86.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(NestedProjects) = preSolution | ||
{BE1CED5B-9590-4170-BB54-822555E920CF} = {EEEC40BE-3F2B-4B6E-9040-71FF3DC274AA} | ||
{28B157AE-2F2D-4438-8493-3FE9148E442B} = {EEEC40BE-3F2B-4B6E-9040-71FF3DC274AA} | ||
{5D825DE4-4A0D-439F-A3BB-F16B33461F16} = {EEEC40BE-3F2B-4B6E-9040-71FF3DC274AA} | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {843608D9-10E0-41C6-9955-83621659FFE5} | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<Project Sdk="Microsoft.NET.Sdk.Razor"> | ||
|
||
<PropertyGroup> | ||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks> | ||
<RazorLangVersion>3.0</RazorLangVersion> | ||
<LangVersion>preview</LangVersion> | ||
<PackageVersion>0.1.0-preview</PackageVersion> | ||
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.AspNetCore.Components" Version="$(ServerPackagesVersion)" /> | ||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="$(ServerPackagesVersion)" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
using System; | ||
using System.IO; | ||
|
||
namespace BlazorInputFile | ||
{ | ||
// This is public only because it's used in a JSInterop method signature, | ||
// but otherwise is intended as internal | ||
public class FileListEntryImpl : IFileListEntry | ||
{ | ||
internal InputFile Owner { get; set; } | ||
|
||
private Stream _stream; | ||
|
||
public event EventHandler OnDataRead; | ||
|
||
public int Id { get; set; } | ||
|
||
public DateTime LastModified { get; set; } | ||
|
||
public string Name { get; set; } | ||
|
||
public long Size { get; set; } | ||
|
||
public string Type { get; set; } | ||
|
||
public Stream Data | ||
{ | ||
get | ||
{ | ||
_stream ??= Owner.OpenFileStream(this); | ||
return _stream; | ||
} | ||
} | ||
|
||
internal void RaiseOnDataRead() | ||
{ | ||
OnDataRead?.Invoke(this, null); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
using Microsoft.AspNetCore.Components; | ||
using Microsoft.JSInterop; | ||
using System; | ||
using System.IO; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace BlazorInputFile | ||
{ | ||
// TODO: When ReadAsync is called, don't just fetch the segment of data that's being requested. | ||
// That will be very slow, as you may be doing a separate round-trip for each 1KB or so of data. | ||
// Instead, have a larger buffer whose size == SignalR.MaxMessageSize and populate that. Then | ||
// many of the ReadAsync calls can return immediately with already-loaded data. | ||
// | ||
// This is still not as fast as allowing the client to send as much data as it wants, and using | ||
// TCP to apply backpressure. In the future we could achieve something closer to that by having | ||
// an even larger buffer, and telling the client to send N messages in parallel. The ReadAsync | ||
// calls would return whenever their portion of the buffer was populated. This is much more | ||
// complicated to implement. | ||
|
||
public abstract class FileListEntryStream : Stream | ||
{ | ||
protected readonly IJSRuntime _jsRuntime; | ||
protected readonly ElementReference _inputFileElement; | ||
protected readonly FileListEntryImpl _file; | ||
private long _position; | ||
|
||
public FileListEntryStream(IJSRuntime jsRuntime, ElementReference inputFileElement, FileListEntryImpl file) | ||
{ | ||
_jsRuntime = jsRuntime; | ||
_inputFileElement = inputFileElement; | ||
_file = file; | ||
} | ||
|
||
public override bool CanRead => true; | ||
|
||
public override bool CanSeek => false; | ||
|
||
public override bool CanWrite => false; | ||
|
||
public override long Length => _file.Size; | ||
|
||
public override long Position | ||
{ | ||
get => _position; | ||
set => throw new NotSupportedException(); | ||
} | ||
|
||
public override void Flush() | ||
=> throw new NotSupportedException(); | ||
|
||
public override int Read(byte[] buffer, int offset, int count) | ||
=> throw new NotSupportedException("Synchronous reads are not supported"); | ||
|
||
public override long Seek(long offset, SeekOrigin origin) | ||
=> throw new NotSupportedException(); | ||
|
||
public override void SetLength(long value) | ||
=> throw new NotSupportedException(); | ||
|
||
public override void Write(byte[] buffer, int offset, int count) | ||
=> throw new NotSupportedException(); | ||
|
||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) | ||
{ | ||
var maxBytesToRead = (int)Math.Min(count, Length - Position); | ||
if (maxBytesToRead == 0) | ||
{ | ||
return 0; | ||
} | ||
|
||
var actualBytesRead = await CopyFileDataIntoBuffer(_position, buffer, offset, maxBytesToRead, cancellationToken); | ||
_position += actualBytesRead; | ||
_file.RaiseOnDataRead(); | ||
return actualBytesRead; | ||
} | ||
|
||
protected abstract Task<int> CopyFileDataIntoBuffer(long sourceOffset, byte[] destination, int destinationOffset, int maxBytes, CancellationToken cancellationToken); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
using System; | ||
using System.IO; | ||
|
||
namespace BlazorInputFile | ||
{ | ||
public interface IFileListEntry | ||
{ | ||
DateTime LastModified { get; } | ||
|
||
string Name { get; } | ||
|
||
long Size { get; } | ||
|
||
string Type { get; } | ||
|
||
Stream Data { get; } | ||
|
||
event EventHandler OnDataRead; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
@implements IDisposable | ||
@inject IJSRuntime JSRuntime | ||
|
||
<input type="file" @ref="inputFileElement" @attributes="UnmatchedParameters" /> | ||
|
||
@code { | ||
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> UnmatchedParameters { get; set; } | ||
[Parameter] public EventCallback<IFileListEntry[]> OnChange { get; set; } | ||
[Parameter] public int MaxMessageSize { get; set; } = 20 * 1024; // TODO: Use SignalR default | ||
[Parameter] public int MaxBufferSize { get; set; } = 1024 * 1024; | ||
|
||
ElementReference inputFileElement; | ||
IDisposable thisReference; | ||
|
||
[JSInvokable] | ||
public Task NotifyChange(FileListEntryImpl[] files) | ||
{ | ||
foreach (var file in files) | ||
{ | ||
// So that method invocations on the file can be dispatched back here | ||
file.Owner = (InputFile)(object)this; | ||
} | ||
|
||
return OnChange.InvokeAsync(files); | ||
} | ||
|
||
protected override async Task OnAfterRenderAsync(bool firstRender) | ||
{ | ||
if (firstRender) | ||
{ | ||
thisReference = DotNetObjectReference.Create(this); | ||
await JSRuntime.InvokeAsync<object>("BlazorInputFile.init", inputFileElement, thisReference); | ||
} | ||
} | ||
|
||
internal Stream OpenFileStream(FileListEntryImpl file) | ||
{ | ||
return SharedMemoryFileListEntryStream.IsSupported(JSRuntime) | ||
? (Stream)new SharedMemoryFileListEntryStream(JSRuntime, inputFileElement, file) | ||
: new RemoteFileListEntryStream(JSRuntime, inputFileElement, file, MaxMessageSize, MaxBufferSize); | ||
} | ||
|
||
void IDisposable.Dispose() | ||
{ | ||
thisReference?.Dispose(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
|
||
namespace BlazorInputFile | ||
{ | ||
internal class PreFetchingSequence<T> | ||
{ | ||
private readonly Func<long, CancellationToken, T> _fetchCallback; | ||
private readonly int _maxBufferCapacity; | ||
private readonly long _totalFetchableItems; | ||
private readonly Queue<T> _buffer; | ||
private long _maxFetchedIndex; | ||
|
||
public PreFetchingSequence(Func<long, CancellationToken, T> fetchCallback, long totalFetchableItems, int maxBufferCapacity) | ||
{ | ||
_fetchCallback = fetchCallback; | ||
_buffer = new Queue<T>(); | ||
_maxBufferCapacity = maxBufferCapacity; | ||
_totalFetchableItems = totalFetchableItems; | ||
} | ||
|
||
public T ReadNext(CancellationToken cancellationToken) | ||
{ | ||
EnqueueFetches(cancellationToken); | ||
if (_buffer.Count == 0) | ||
{ | ||
throw new InvalidOperationException("There are no more entries to read"); | ||
} | ||
|
||
var next = _buffer.Dequeue(); | ||
EnqueueFetches(cancellationToken); | ||
return next; | ||
} | ||
|
||
public bool TryPeekNext(out T result) | ||
{ | ||
if (_buffer.Count > 0) | ||
{ | ||
result = _buffer.Peek(); | ||
return true; | ||
} | ||
else | ||
{ | ||
result = default; | ||
return false; | ||
} | ||
} | ||
|
||
private void EnqueueFetches(CancellationToken cancellationToken) | ||
{ | ||
while (_buffer.Count < _maxBufferCapacity && _maxFetchedIndex < _totalFetchableItems) | ||
{ | ||
_buffer.Enqueue(_fetchCallback(_maxFetchedIndex++, cancellationToken)); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.