-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- The stream returned by HttpFile.OpenStreamAsync() now includes the value of the Content-Length header as the stream Length. - The stream returned by HttpFile.OpenStreamAsync() is now wrapped in a LazySeekStream, allowing for seeking and only advancing the underlying stream when strictly needed. - Fixed an issue where ZipArchiveFolder.DeleteAsync wasn't correctly identifying the zip entry given the storage id. - Fixed a redundant Id check in OwlCore.Storage.Tests.Archive.ZipArchive.CreateNewFolderAsyncTest_FolderWithNestedItems, substituting with a name check instead.
- Loading branch information
1 parent
0bbc8c0
commit 8baab6c
Showing
7 changed files
with
300 additions
and
18 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
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
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,188 @@ | ||
using System; | ||
using System.IO; | ||
|
||
namespace OwlCore.Storage; | ||
|
||
/// <summary> | ||
/// Wraps around a non-seekable stream to enable seeking functionality with lazy loading of the source. | ||
/// </summary> | ||
internal class LazySeekStream : Stream | ||
{ | ||
private Stream _originalStream; | ||
private MemoryStream _memoryStream; | ||
|
||
/// <summary> | ||
/// Creates a new instance of <see cref="LazySeekStream"/>. | ||
/// </summary> | ||
/// <param name="stream"></param> | ||
public LazySeekStream(Stream stream) | ||
{ | ||
_originalStream = stream; | ||
|
||
_memoryStream = new MemoryStream() | ||
{ | ||
Capacity = (int)Length, | ||
}; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override bool CanRead => _memoryStream.CanRead; | ||
|
||
/// <inheritdoc /> | ||
public override bool CanSeek => _memoryStream.CanSeek; | ||
|
||
/// <inheritdoc /> | ||
public override bool CanWrite => false; | ||
|
||
/// <inheritdoc /> | ||
public override long Length => _originalStream.Length; | ||
|
||
/// <inheritdoc /> | ||
public override long Position | ||
{ | ||
get => _memoryStream.Position; | ||
set | ||
{ | ||
if (value < 0) | ||
throw new IOException("An attempt was made to move the position before the beginning of the stream."); | ||
|
||
// Check if the requested position is beyond the current length of the memory stream | ||
if (value > _memoryStream.Length) | ||
{ | ||
long additionalBytesNeeded = value - _memoryStream.Length; | ||
var buffer = new byte[additionalBytesNeeded]; | ||
long totalBytesRead = 0; | ||
|
||
while (totalBytesRead < additionalBytesNeeded) | ||
{ | ||
int bytesRead = _originalStream.Read(buffer, (int)totalBytesRead, (int)(additionalBytesNeeded - totalBytesRead)); | ||
if (bytesRead == 0) | ||
break; // End of the original stream reached | ||
|
||
totalBytesRead += bytesRead; | ||
} | ||
|
||
// Write the newly read bytes to the end of the memory stream | ||
_memoryStream.Seek(0, SeekOrigin.End); | ||
_memoryStream.Write(buffer, 0, (int)totalBytesRead); | ||
} | ||
|
||
// Set the new position of the memory stream | ||
_memoryStream.Position = value; | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override void Flush() => _memoryStream.Flush(); | ||
|
||
/// <inheritdoc /> | ||
public override int Read(byte[] buffer, int offset, int count) | ||
{ | ||
int totalBytesRead = 0; | ||
|
||
// Read from memory stream first | ||
if (_memoryStream.Position < _memoryStream.Length) | ||
{ | ||
totalBytesRead = _memoryStream.Read(buffer, offset, count); | ||
if (totalBytesRead == count) | ||
{ | ||
return totalBytesRead; // Complete read from memory stream | ||
} | ||
|
||
// Prepare to read the remaining data from the original stream | ||
offset += totalBytesRead; | ||
count -= totalBytesRead; | ||
} | ||
|
||
// Read the remaining data directly into the provided buffer | ||
while (count > 0) | ||
{ | ||
int bytesReadFromOriginalStream = _originalStream.Read(buffer, offset, count); | ||
if (bytesReadFromOriginalStream == 0) | ||
{ | ||
break; // End of the original stream reached | ||
} | ||
|
||
// Write the new data from the original stream into the memory stream | ||
_memoryStream.Seek(0, SeekOrigin.End); | ||
_memoryStream.Write(buffer, offset, bytesReadFromOriginalStream); | ||
|
||
totalBytesRead += bytesReadFromOriginalStream; | ||
offset += bytesReadFromOriginalStream; | ||
count -= bytesReadFromOriginalStream; | ||
} | ||
|
||
return totalBytesRead; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override long Seek(long offset, SeekOrigin origin) | ||
{ | ||
switch (origin) | ||
{ | ||
case SeekOrigin.Begin: | ||
Position = offset; | ||
break; | ||
case SeekOrigin.Current: | ||
Position = _memoryStream.Position + offset; | ||
break; | ||
case SeekOrigin.End: | ||
Position = _originalStream.Length + offset; | ||
break; | ||
default: | ||
throw new ArgumentOutOfRangeException(nameof(origin), "Invalid seek origin."); | ||
} | ||
|
||
return Position; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override void SetLength(long value) | ||
{ | ||
if (value < 0) | ||
throw new ArgumentOutOfRangeException(nameof(value), "Length must be non-negative."); | ||
|
||
if (value < _memoryStream.Length) | ||
{ | ||
// Truncate the memory stream | ||
_memoryStream.SetLength(value); | ||
} | ||
else if (value > _memoryStream.Length) | ||
{ | ||
long additionalBytesNeeded = value - _memoryStream.Length; | ||
|
||
// Extend the memory stream with zeros or additional data from the original stream | ||
if (_originalStream.CanRead && additionalBytesNeeded > 0) | ||
{ | ||
var buffer = new byte[additionalBytesNeeded]; | ||
int bytesRead = _originalStream.Read(buffer, 0, buffer.Length); | ||
|
||
_memoryStream.Seek(0, SeekOrigin.End); | ||
_memoryStream.Write(buffer, 0, bytesRead); | ||
|
||
if (bytesRead < additionalBytesNeeded) | ||
{ | ||
// Fill the rest with zeros if the original stream didn't have enough data | ||
var zeroFill = new byte[additionalBytesNeeded - bytesRead]; | ||
_memoryStream.Write(zeroFill, 0, zeroFill.Length); | ||
} | ||
} | ||
else | ||
{ | ||
// Fill with zeros if the original stream can't be read or no additional bytes are needed | ||
_memoryStream.SetLength(value); | ||
} | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException($"Writing not supported by {nameof(LazySeekStream)}"); | ||
|
||
/// <inheritdoc /> | ||
protected override void Dispose(bool disposing) | ||
{ | ||
base.Dispose(disposing); | ||
_memoryStream.Dispose(); | ||
_originalStream.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,71 @@ | ||
using System; | ||
using System.IO; | ||
|
||
namespace OwlCore.Storage; | ||
|
||
/// <summary> | ||
/// A stream wrapper that allows overriding the Length property. | ||
/// </summary> | ||
internal class LengthOverrideStream : Stream | ||
{ | ||
private readonly long _overriddenLength; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="LengthOverrideStream"/> class. | ||
/// </summary> | ||
/// <param name="sourceStream">The underlying stream to wrap.</param> | ||
/// <param name="overriddenLength">The length value to be returned by the Length property.</param> | ||
public LengthOverrideStream(Stream sourceStream, long overriddenLength) | ||
{ | ||
SourceStream = sourceStream ?? throw new ArgumentNullException(nameof(sourceStream)); | ||
_overriddenLength = overriddenLength; | ||
} | ||
|
||
/// <summary> | ||
/// The underlying source stream being wrapped around. | ||
/// </summary> | ||
public Stream SourceStream { get; } | ||
|
||
/// <inheritdoc /> | ||
public override bool CanRead => SourceStream.CanRead; | ||
|
||
/// <inheritdoc /> | ||
public override bool CanSeek => SourceStream.CanSeek; | ||
|
||
/// <inheritdoc /> | ||
public override bool CanWrite => SourceStream.CanWrite; | ||
|
||
/// <inheritdoc /> | ||
public override long Length => _overriddenLength; | ||
|
||
/// <inheritdoc /> | ||
public override long Position | ||
{ | ||
get => SourceStream.Position; | ||
set => SourceStream.Position = value; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override void Flush() => SourceStream.Flush(); | ||
|
||
/// <inheritdoc /> | ||
public override int Read(byte[] buffer, int offset, int count) => SourceStream.Read(buffer, offset, count); | ||
|
||
/// <inheritdoc /> | ||
public override long Seek(long offset, SeekOrigin origin) => SourceStream.Seek(offset, origin); | ||
|
||
/// <inheritdoc /> | ||
public override void SetLength(long value) => SourceStream.SetLength(value); | ||
|
||
/// <inheritdoc /> | ||
public override void Write(byte[] buffer, int offset, int count) => SourceStream.Write(buffer, offset, count); | ||
|
||
/// <inheritdoc /> | ||
protected override void Dispose(bool disposing) | ||
{ | ||
if (disposing) | ||
SourceStream.Dispose(); | ||
|
||
base.Dispose(disposing); | ||
} | ||
} |
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
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
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