mirror of
https://github.com/Grace-Solutions/PSMinIO.git
synced 2026-07-26 14:58:13 +00:00
b237838ff1
- Removed MinIO SDK dependency completely - Built custom HTTP client with AWS S3 signature v4 support - Created synchronous operations optimized for PowerShell - Added real progress reporting during transfers - Implemented performance metrics in result objects - Fixed null pointer warnings for code quality - Removed Write-Host usage from test scripts - Cleaned up unnecessary DLL dependencies Available cmdlets: - Connect-MinIO: Enhanced connection with certificate options - Get-MinIOBucket: List buckets with sorting and filtering - New-MinIOBucket: Create buckets with region support - Test-MinIOBucketExists: Check bucket existence - Get-MinIOObject: List objects with advanced filtering - New-MinIOObject: Upload files with real progress - Get-MinIOObjectContent: Download files with progress This represents a complete architectural overhaul for better PowerShell compatibility and performance.
207 lines
8.6 KiB
C#
207 lines
8.6 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Management.Automation;
|
|
using PSMinIO.Core.Models;
|
|
using PSMinIO.Utils;
|
|
|
|
namespace PSMinIO.Cmdlets
|
|
{
|
|
/// <summary>
|
|
/// Downloads objects from MinIO
|
|
/// </summary>
|
|
[Cmdlet(VerbsCommon.Get, "MinIOObjectContent", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Medium)]
|
|
[OutputType(typeof(MinIODownloadResult))]
|
|
public class GetMinIOObjectContentCmdlet : MinIOBaseCmdlet
|
|
{
|
|
/// <summary>
|
|
/// Name of the bucket
|
|
/// </summary>
|
|
[Parameter(Position = 0, Mandatory = true)]
|
|
[ValidateNotNullOrEmpty]
|
|
public string BucketName { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Name of the object to download
|
|
/// </summary>
|
|
[Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
|
|
[ValidateNotNullOrEmpty]
|
|
[Alias("Key", "Name")]
|
|
public string ObjectName { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Local file path where the object should be saved
|
|
/// </summary>
|
|
[Parameter(Position = 2, Mandatory = true)]
|
|
[ValidateNotNullOrEmpty]
|
|
[Alias("Path", "FilePath")]
|
|
public string LocalPath { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Force overwrite if local file already exists
|
|
/// </summary>
|
|
[Parameter]
|
|
public SwitchParameter Force { get; set; }
|
|
|
|
/// <summary>
|
|
/// Create directory structure if it doesn't exist
|
|
/// </summary>
|
|
[Parameter]
|
|
public SwitchParameter CreateDirectories { get; set; }
|
|
|
|
/// <summary>
|
|
/// Return the download result information
|
|
/// </summary>
|
|
[Parameter]
|
|
public SwitchParameter PassThru { get; set; }
|
|
|
|
/// <summary>
|
|
/// Processes the cmdlet
|
|
/// </summary>
|
|
protected override void ProcessRecord()
|
|
{
|
|
ValidateBucketName(BucketName);
|
|
ValidateObjectName(ObjectName);
|
|
|
|
// Validate local path
|
|
if (string.IsNullOrWhiteSpace(LocalPath))
|
|
{
|
|
ThrowTerminatingError(new ErrorRecord(
|
|
new ArgumentException("Local path cannot be null or empty"),
|
|
"InvalidLocalPath",
|
|
ErrorCategory.InvalidArgument,
|
|
LocalPath));
|
|
}
|
|
|
|
// Check if file already exists
|
|
if (File.Exists(LocalPath) && !Force.IsPresent)
|
|
{
|
|
ThrowTerminatingError(new ErrorRecord(
|
|
new InvalidOperationException($"File already exists: {LocalPath}. Use -Force to overwrite."),
|
|
"FileAlreadyExists",
|
|
ErrorCategory.ResourceExists,
|
|
LocalPath));
|
|
}
|
|
|
|
if (ShouldProcess($"{BucketName}/{ObjectName}", "Download object"))
|
|
{
|
|
var result = ExecuteOperation("DownloadObject", () =>
|
|
{
|
|
WriteVerboseMessage("Downloading object '{0}' from bucket '{1}' to '{2}'",
|
|
ObjectName, BucketName, LocalPath);
|
|
|
|
// Create download result
|
|
var downloadResult = new MinIODownloadResult(BucketName, ObjectName, 0)
|
|
{
|
|
TargetFilePath = LocalPath
|
|
};
|
|
downloadResult.MarkStarted();
|
|
|
|
try
|
|
{
|
|
// Check if bucket exists
|
|
if (!S3Client.BucketExists(BucketName))
|
|
{
|
|
throw new InvalidOperationException($"Bucket '{BucketName}' does not exist");
|
|
}
|
|
|
|
// Create directory if needed
|
|
var directory = Path.GetDirectoryName(LocalPath);
|
|
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
|
{
|
|
if (CreateDirectories.IsPresent)
|
|
{
|
|
WriteVerboseMessage("Creating directory: {0}", directory);
|
|
Directory.CreateDirectory(directory);
|
|
}
|
|
else
|
|
{
|
|
throw new DirectoryNotFoundException($"Directory does not exist: {directory}. Use -CreateDirectories to create it.");
|
|
}
|
|
}
|
|
|
|
// Track progress
|
|
long lastReportedBytes = 0;
|
|
var startTime = DateTime.UtcNow;
|
|
|
|
// Download the object
|
|
using (var fileStream = new FileStream(LocalPath, FileMode.Create, FileAccess.Write))
|
|
{
|
|
var bytesDownloaded = S3Client.GetObject(
|
|
BucketName,
|
|
ObjectName,
|
|
fileStream,
|
|
bytesTransferred =>
|
|
{
|
|
downloadResult.BytesTransferred = bytesTransferred;
|
|
|
|
// Report progress every 1MB or at completion
|
|
if (bytesTransferred - lastReportedBytes >= 1024 * 1024 ||
|
|
(downloadResult.TotalSize > 0 && bytesTransferred >= downloadResult.TotalSize))
|
|
{
|
|
var elapsed = DateTime.UtcNow - startTime;
|
|
var speed = elapsed.TotalSeconds > 0 ? bytesTransferred / elapsed.TotalSeconds : 0;
|
|
var percentage = downloadResult.TotalSize > 0 ?
|
|
(double)bytesTransferred / downloadResult.TotalSize * 100 : 0;
|
|
|
|
WriteVerboseMessage("Download progress: {0:F1}% ({1}/{2}) at {3}",
|
|
percentage,
|
|
SizeFormatter.FormatBytes(bytesTransferred),
|
|
downloadResult.TotalSize > 0 ? SizeFormatter.FormatBytes(downloadResult.TotalSize) : "Unknown",
|
|
SizeFormatter.FormatSpeed(speed));
|
|
|
|
lastReportedBytes = bytesTransferred;
|
|
}
|
|
});
|
|
|
|
downloadResult.TotalSize = bytesDownloaded;
|
|
downloadResult.BytesTransferred = bytesDownloaded;
|
|
}
|
|
|
|
downloadResult.MarkCompleted();
|
|
|
|
var duration = downloadResult.Duration ?? TimeSpan.Zero;
|
|
var averageSpeed = downloadResult.AverageSpeed ?? 0;
|
|
|
|
WriteVerboseMessage("Download completed: {0} in {1} at average speed of {2}",
|
|
SizeFormatter.FormatBytes(downloadResult.BytesTransferred),
|
|
SizeFormatter.FormatDuration(duration),
|
|
SizeFormatter.FormatSpeed(averageSpeed));
|
|
|
|
// Set file timestamps if available
|
|
try
|
|
{
|
|
var fileInfo = new FileInfo(LocalPath);
|
|
if (downloadResult.LastModified.HasValue)
|
|
{
|
|
fileInfo.LastWriteTimeUtc = downloadResult.LastModified.Value;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
WriteVerboseMessage("Could not set file timestamps: {0}", ex.Message);
|
|
}
|
|
|
|
return downloadResult;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
downloadResult.MarkFailed(ex.Message);
|
|
throw;
|
|
}
|
|
}, $"Bucket: {BucketName}, Object: {ObjectName}, LocalPath: {LocalPath}");
|
|
|
|
// Return result if requested
|
|
if (PassThru.IsPresent)
|
|
{
|
|
WriteObject(result);
|
|
}
|
|
else
|
|
{
|
|
WriteVerboseMessage("Successfully downloaded object '{0}' from bucket '{1}' to '{2}'",
|
|
ObjectName, BucketName, LocalPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|