File: Files\HostedFileDownloadStream.cs
Project: ..\..\..\src\Libraries\Microsoft.Extensions.AI.Abstractions\Microsoft.Extensions.AI.Abstractions.csproj (Microsoft.Extensions.AI.Abstractions)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Shared.DiagnosticIds;
 
namespace Microsoft.Extensions.AI;
 
/// <summary>
/// Represents a stream for downloading file content from an AI service.
/// </summary>
/// <remarks>
/// <para>
/// This abstract class extends <see cref="Stream"/> to provide additional metadata
/// about the downloaded file, such as its media type and file name. Implementations
/// should override the abstract <see cref="Stream"/> members and optionally override
/// <see cref="MediaType"/> and <see cref="FileName"/> to provide file metadata.
/// </para>
/// <para>
/// The <see cref="ToDataContentAsync"/> method provides a convenient way to buffer
/// the entire stream content into a <see cref="DataContent"/> instance.
/// </para>
/// </remarks>
[Experimental(DiagnosticIds.Experiments.AIFiles, UrlFormat = DiagnosticIds.UrlFormat)]
public abstract class HostedFileDownloadStream : Stream
{
    /// <summary>
    /// Initializes a new instance of the <see cref="HostedFileDownloadStream"/> class.
    /// </summary>
    protected HostedFileDownloadStream()
    {
    }
 
    /// <summary>
    /// Gets the media type (MIME type) of the file content.
    /// </summary>
    /// <remarks>
    /// Returns <see langword="null"/> if the media type is not known.
    /// </remarks>
    public virtual string? MediaType => null;
 
    /// <summary>
    /// Gets the file name.
    /// </summary>
    /// <remarks>
    /// Returns <see langword="null"/> if the file name is not known.
    /// </remarks>
    public virtual string? FileName => null;
 
    /// <inheritdoc />
    public override bool CanWrite => false;
 
    /// <inheritdoc />
    public override void SetLength(long value) => throw new NotSupportedException();
 
    /// <inheritdoc />
    public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) =>
        throw new NotSupportedException();
 
    /// <inheritdoc />
    public override void EndWrite(IAsyncResult asyncResult) => throw new NotSupportedException();
 
    /// <inheritdoc />
    public override void WriteByte(byte value) => throw new NotSupportedException();
 
    /// <inheritdoc />
    public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
 
#if NET
    /// <inheritdoc />
    public override void Write(ReadOnlySpan<byte> buffer) => throw new NotSupportedException();
#endif
 
    /// <inheritdoc />
    public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
        throw new NotSupportedException();
 
#if NET
    /// <inheritdoc />
    public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default) =>
        throw new NotSupportedException();
#endif
 
    /// <summary>
    /// Reads the entire stream content from its current position and returns it as a <see cref="DataContent"/>.
    /// </summary>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
    /// <returns>A <see cref="DataContent"/> containing the buffered file content.</returns>
    /// <remarks>
    /// This method buffers the entire stream content into memory. For large files,
    /// consider streaming directly to the destination instead.
    /// </remarks>
    public virtual async Task<DataContent> ToDataContentAsync(CancellationToken cancellationToken = default)
    {
        MemoryStream memoryStream = new();
 
        await CopyToAsync(memoryStream,
#if !NET
            81920,
#endif
            cancellationToken).ConfigureAwait(false);
 
        return new DataContent(
            memoryStream.GetBuffer().AsMemory(0, (int)memoryStream.Length),
            MediaType ?? "application/octet-stream")
        {
            Name = FileName,
        };
    }
}