A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

124 lines
4.1 KiB

using System.Buffers;
using System.Text.Json.Serialization;
using Microsoft.JSInterop;
namespace Avalonia.Web.Blazor.Interop.Storage
{
// Loose wrapper implementaion of a stream on top of FileAPI FileSystemWritableFileStream
internal sealed class JSWriteableStream : Stream
{
private IJSInProcessObjectReference? _jSReference;
// Unfortunatelly we can't read current length/position, so we need to keep it C#-side only.
private long _length, _position;
internal JSWriteableStream(IJSInProcessObjectReference jSReference, long initialLength)
{
_jSReference = jSReference;
_length = initialLength;
}
private IJSInProcessObjectReference JSReference => _jSReference ?? throw new ObjectDisposedException(nameof(JSWriteableStream));
public override bool CanRead => false;
public override bool CanSeek => true;
public override bool CanWrite => true;
public override long Length => _length;
public override long Position
{
get => _position;
set => Seek(_position, SeekOrigin.Begin);
}
public override void Flush()
{
// no-op
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
var position = origin switch
{
SeekOrigin.Current => _position + offset,
SeekOrigin.End => _length + offset,
_ => offset
};
JSReference.InvokeVoid("seek", position);
return position;
}
public override void SetLength(long value)
{
_length = value;
// See https://docs.w3cub.com/dom/filesystemwritablefilestream/truncate
// If the offset is smaller than the size, it remains unchanged. If the offset is larger than size, the offset is set to that size
if (_position > _length)
{
_position = _length;
}
JSReference.InvokeVoid("truncate", value);
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException("Synchronous writes are not supported.");
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (offset != 0 || count != buffer.Length)
{
// TODO, we need to pass prepared buffer to the JS
// Can't use ArrayPool as it can return bigger array than requested
// Can't use Span/Memory, as it's not supported by JS interop yet.
// Alternatively we can pass original buffer and offset+count, so it can be trimmed on the JS side (but is it more efficient tho?)
buffer = buffer.AsMemory(offset, count).ToArray();
}
return WriteAsyncInternal(buffer, cancellationToken).AsTask();
}
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
return WriteAsyncInternal(buffer.ToArray(), cancellationToken);
}
private ValueTask WriteAsyncInternal(byte[] buffer, CancellationToken _)
{
_position += buffer.Length;
return JSReference.InvokeVoidAsync("write", buffer);
}
protected override void Dispose(bool disposing)
{
if (_jSReference is { } jsReference)
{
_jSReference = null;
jsReference.InvokeVoid("close");
jsReference.Dispose();
}
}
public override async ValueTask DisposeAsync()
{
if (_jSReference is { } jsReference)
{
_jSReference = null;
await jsReference.InvokeVoidAsync("close");
await jsReference.DisposeAsync();
}
}
}
}