MVP
This commit is contained in:
parent
61725b4d07
commit
5bf51d108c
5 changed files with 214 additions and 28 deletions
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MeowStash.Services;
|
||||||
using Microsoft.AspNetCore.Http.HttpResults;
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
@ -11,8 +12,15 @@ namespace MeowStash.Controllers;
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class ResourceController : ControllerBase
|
public class ResourceController : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpPost("add")]
|
public ResourceController(FileIOService io)
|
||||||
public async Task<IActionResult> AddFiles([FromForm] IFormFileCollection files)
|
{
|
||||||
|
_io = io;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly FileIOService _io;
|
||||||
|
|
||||||
|
[HttpPost("upload")]
|
||||||
|
public async Task<IActionResult> UploadFiles([FromForm] IFormFileCollection files)
|
||||||
{
|
{
|
||||||
foreach (var file in files)
|
foreach (var file in files)
|
||||||
{
|
{
|
||||||
|
@ -23,4 +31,12 @@ public class ResourceController : ControllerBase
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("upload/fragment/{id}")]
|
||||||
|
public async Task<IActionResult> UploadFileFragment([FromRoute] int uploadId, [FromBody] byte[] dataPacket)
|
||||||
|
{
|
||||||
|
using var buffer = new MemoryStream(dataPacket);
|
||||||
|
await _io.WritePartialFile(uploadId, buffer);
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,36 +7,108 @@
|
||||||
|
|
||||||
|
|
||||||
<InputFile OnChange="@SendFiles" multiple /><br />
|
<InputFile OnChange="@SendFiles" multiple /><br />
|
||||||
<span class="@_umsg_class">@_umsg</span>
|
|
||||||
|
@if (_uploadData.Any())
|
||||||
|
{
|
||||||
|
<div class="uploads">
|
||||||
|
@foreach (var item in _uploadData)
|
||||||
|
{
|
||||||
|
<div class="upload-item">
|
||||||
|
<div class="upload-item-data">
|
||||||
|
<span class="upload-item-name">@item.Key.Name</span>
|
||||||
|
</div>
|
||||||
|
<div class="upload-progress">
|
||||||
|
<span class="upload-progress-value @GetUploadProgressClassesFor(item.Value.Item1)">@item.Value.Item1%</span>
|
||||||
|
<span class="upload-progress-bytes">@item.Value.Item2</span>
|
||||||
|
<div class="upload-progress-bar-container">
|
||||||
|
<div class="upload-progress-bar @GetUploadProgressClassesFor(item.Value.Item1)"
|
||||||
|
style="width: @($"{item.Value.Item1}%");"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _umsg = "", _umsg_class = "text-hidden";
|
|
||||||
|
private string GetUploadProgressClassesFor(float percent)
|
||||||
|
{
|
||||||
|
return percent switch
|
||||||
|
{
|
||||||
|
(< 5f) => "upload-progress-low",
|
||||||
|
(> 5f) and (< 50f) => "upload-progress-submedial",
|
||||||
|
(> 50f) and (< 95f) => "upload-progress-supermedial",
|
||||||
|
(> 95f) and (< 100f) => "upload-progress-nearly-done",
|
||||||
|
100f or (> 100f) => "upload-progress-done",
|
||||||
|
_ => "upload-progress-invalid"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private System.Collections.Concurrent.ConcurrentDictionary<IBrowserFile, (float, long)> _uploadData = new();
|
||||||
|
private CancellationTokenSource _cancel = null!;
|
||||||
|
|
||||||
private async Task SendFiles(InputFileChangeEventArgs e)
|
private async Task SendFiles(InputFileChangeEventArgs e)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Files: {e.FileCount}");
|
if (_cancel != null)
|
||||||
|
{
|
||||||
|
_cancel.Cancel();
|
||||||
|
_cancel.Dispose();
|
||||||
|
}
|
||||||
|
_cancel = new();
|
||||||
|
|
||||||
var files = e.GetMultipleFiles(e.FileCount);
|
var files = e.GetMultipleFiles(e.FileCount);
|
||||||
|
|
||||||
_umsg_class = "text-green";
|
_uploadData = new(files.Count, files.ToDictionary(f => f, _ => (0f, 0L)), null);
|
||||||
|
|
||||||
int counter = 1;
|
if (!System.IO.Directory.Exists("/tmp/uploads"))
|
||||||
foreach (var file in files)
|
|
||||||
{
|
{
|
||||||
_umsg = $"Uploading {counter}/{e.FileCount} files...";
|
System.IO.Directory.CreateDirectory("/tmp/uploads");
|
||||||
StateHasChanged();
|
|
||||||
Console.WriteLine($"Fos: /tmp/uploads/{file.Name}");
|
|
||||||
if (!System.IO.Directory.Exists("/tmp/uploads"))
|
|
||||||
{
|
|
||||||
System.IO.Directory.CreateDirectory("/tmp/uploads");
|
|
||||||
}
|
|
||||||
Console.WriteLine($"fosse");
|
|
||||||
await using var fos = System.IO.File.OpenWrite($"/tmp/uploads/{file.Name}");
|
|
||||||
Console.WriteLine("Fos opened");
|
|
||||||
await file.OpenReadStream(1024 * 1024 * 10).CopyToAsync(fos);
|
|
||||||
Console.WriteLine($"File: /tmp/uploads/{file.Name} written...");
|
|
||||||
++counter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_umsg = "Done!";
|
var tasks = new List<Task>();
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
tasks.Add(Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await using var fos = System.IO.File.OpenWrite($"/tmp/uploads/{file.Name}");
|
||||||
|
|
||||||
|
var stream = file.OpenReadStream(1024 * 1024 * 1024, _cancel.Token);
|
||||||
|
int read;
|
||||||
|
|
||||||
|
byte[] buffer = new byte[16 * 1024];
|
||||||
|
|
||||||
|
while ((read = await stream.ReadAsync(buffer, 0, buffer.Length, _cancel.Token)) > 0)
|
||||||
|
{
|
||||||
|
if (_cancel.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
stream.Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fos.WriteAsync(buffer, 0, read);
|
||||||
|
|
||||||
|
long totalRead = _uploadData[file].Item2;
|
||||||
|
totalRead += read;
|
||||||
|
|
||||||
|
float percent = float.Round(((float)totalRead / (float)file.Size * 100f), 2);
|
||||||
|
_uploadData[file] = (percent, totalRead);
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
await Task.Delay(500, _cancel.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.Close();
|
||||||
|
|
||||||
|
Console.WriteLine($"File: /tmp/uploads/{file.Name} written...");
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,13 @@
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Add services to the container.
|
|
||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
builder.Services.AddServerSideBlazor();
|
builder.Services.AddServerSideBlazor();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
|
||||||
if (!app.Environment.IsDevelopment())
|
if (!app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseExceptionHandler("/Error");
|
app.UseExceptionHandler("/Error");
|
||||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
|
||||||
app.UseHsts();
|
app.UseHsts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
23
Services/FileIOService.cs
Normal file
23
Services/FileIOService.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
namespace MeowStash.Services;
|
||||||
|
|
||||||
|
public class FileIOService
|
||||||
|
{
|
||||||
|
public async Task WriteFullFile(string location, byte[] data)
|
||||||
|
{
|
||||||
|
await File.WriteAllBytesAsync(location, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteFullFile(string location, Stream data)
|
||||||
|
{
|
||||||
|
using var stream = File.OpenWrite(location);
|
||||||
|
await data.CopyToAsync(stream);
|
||||||
|
stream.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WritePartialFile(int id, Stream data)
|
||||||
|
{
|
||||||
|
using var stream = File.Open($"/tmp/{id}", FileMode.Append);
|
||||||
|
await data.CopyToAsync(stream);
|
||||||
|
stream.Close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,3 +86,84 @@ a,
|
||||||
.blazor-error-boundary::after {
|
.blazor-error-boundary::after {
|
||||||
content: "An error has occurred.";
|
content: "An error has occurred.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.upload-item {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-item-name {
|
||||||
|
color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-value {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-bytes {
|
||||||
|
color: lightgray;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-bar-container {
|
||||||
|
width: 80%;
|
||||||
|
height: 30px;
|
||||||
|
background-color: gray;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.upload-progress-low {
|
||||||
|
color: lightcoral;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.upload-progress-submedial {
|
||||||
|
color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.upload-progress-supermedial {
|
||||||
|
color: greenyellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.upload-progress-nearly-done {
|
||||||
|
color: limegreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.upload-progress-done {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.upload-progress-invalid {
|
||||||
|
color: darkgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-bar.upload-progress-low {
|
||||||
|
background-color: lightcoral;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-bar.upload-progress-submedial {
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-bar.upload-progress-supermedial {
|
||||||
|
background-color: greenyellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-bar.upload-progress-nearly-done {
|
||||||
|
background-color: limegreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-bar.upload-progress-done {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress-bar.upload-progress-invalid {
|
||||||
|
background-color: darkgray;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue