blob: d035fa369ad0e2c2e674408c9aea6c05f2baf88a [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.IO;
using System.Threading.Tasks;
using Apache.Hive.Service.Rpc.Thrift;
namespace Apache.Arrow.Adbc.Drivers.Databricks.Reader.CloudFetch
{
/// <summary>
/// Represents a downloaded result file with its associated metadata.
/// </summary>
internal sealed class DownloadResult : IDownloadResult
{
private readonly TaskCompletionSource<bool> _downloadCompletionSource;
private readonly ICloudFetchMemoryBufferManager _memoryManager;
private Stream? _dataStream;
private bool _isDisposed;
private long _size;
/// <summary>
/// Initializes a new instance of the <see cref="DownloadResult"/> class.
/// </summary>
/// <param name="link">The link information for this result.</param>
/// <param name="memoryManager">The memory buffer manager.</param>
public DownloadResult(TSparkArrowResultLink link, ICloudFetchMemoryBufferManager memoryManager)
{
Link = link ?? throw new ArgumentNullException(nameof(link));
_memoryManager = memoryManager ?? throw new ArgumentNullException(nameof(memoryManager));
_downloadCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
_size = link.BytesNum;
}
/// <inheritdoc />
public TSparkArrowResultLink Link { get; private set; }
/// <inheritdoc />
public Stream DataStream
{
get
{
ThrowIfDisposed();
if (!IsCompleted)
{
throw new InvalidOperationException("Download has not completed yet.");
}
return _dataStream!;
}
}
/// <inheritdoc />
public long Size => _size;
/// <inheritdoc />
public Task DownloadCompletedTask => _downloadCompletionSource.Task;
/// <inheritdoc />
public bool IsCompleted => _downloadCompletionSource.Task.IsCompleted && !_downloadCompletionSource.Task.IsFaulted;
/// <summary>
/// Gets the number of URL refresh attempts for this download.
/// </summary>
public int RefreshAttempts { get; private set; } = 0;
/// <summary>
/// Checks if the URL is expired or about to expire.
/// </summary>
/// <param name="expirationBufferSeconds">Buffer time in seconds before expiration to consider a URL as expiring soon.</param>
/// <returns>True if the URL is expired or about to expire, false otherwise.</returns>
public bool IsExpiredOrExpiringSoon(int expirationBufferSeconds = 60)
{
// Convert expiry time to DateTime
var expiryTime = DateTimeOffset.FromUnixTimeMilliseconds(Link.ExpiryTime).UtcDateTime;
// Check if the URL is already expired or will expire soon
return DateTime.UtcNow.AddSeconds(expirationBufferSeconds) >= expiryTime;
}
/// <summary>
/// Updates this download result with a refreshed link.
/// </summary>
/// <param name="refreshedLink">The refreshed link information.</param>
public void UpdateWithRefreshedLink(TSparkArrowResultLink refreshedLink)
{
ThrowIfDisposed();
Link = refreshedLink ?? throw new ArgumentNullException(nameof(refreshedLink));
RefreshAttempts++;
}
/// <inheritdoc />
public void SetCompleted(Stream dataStream, long size)
{
ThrowIfDisposed();
_dataStream = dataStream ?? throw new ArgumentNullException(nameof(dataStream));
_downloadCompletionSource.TrySetResult(true);
_size = size;
}
/// <inheritdoc />
public void SetFailed(Exception exception)
{
ThrowIfDisposed();
_downloadCompletionSource.TrySetException(exception ?? throw new ArgumentNullException(nameof(exception)));
}
/// <inheritdoc />
public void Dispose()
{
if (_isDisposed)
{
return;
}
if (_dataStream != null)
{
_dataStream.Dispose();
_dataStream = null;
// Release memory back to the manager
if (_size > 0)
{
_memoryManager.ReleaseMemory(_size);
}
}
// Ensure any waiting tasks are completed if not already
if (!_downloadCompletionSource.Task.IsCompleted)
{
_downloadCompletionSource.TrySetCanceled();
}
_isDisposed = true;
}
private void ThrowIfDisposed()
{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(DownloadResult));
}
}
}
}