mirror of
https://github.com/Grace-Solutions/PSMinIO.git
synced 2026-08-18 08:33:16 +00:00
Upgrade to MinIO 5.0.0 and include built module
- Updated MinIO SDK from 3.1.13 to 5.0.0 - Fixed all compilation errors and warnings for MinIO 5.0.0 compatibility - Updated API calls to use new Args-based patterns (PutObjectArgs, GetObjectArgs, etc.) - Fixed type compatibility issues (DateTime, ulong conversions) - Removed deprecated APIs (WithCancellation, ProgressReport) - Updated System.Text.Json to 9.0.0 to resolve security vulnerabilities - Added Module directory to repository with built PSMinIO.dll - Module is now ready for distribution and testing - All builds pass with 0 errors and 0 warnings
This commit is contained in:
@@ -69,12 +69,6 @@ namespace PSMinIO.Cmdlets
|
||||
[Parameter]
|
||||
public SwitchParameter SkipCertificateValidation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Skip SSL certificate validation (use with caution)
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public SwitchParameter SkipCertificateValidation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Accept self-signed certificates
|
||||
/// </summary>
|
||||
@@ -154,7 +148,7 @@ namespace PSMinIO.Cmdlets
|
||||
if (!string.IsNullOrWhiteSpace(SessionVariable))
|
||||
{
|
||||
SessionState.PSVariable.Set(SessionVariable, connection);
|
||||
MinIOLogger.WriteVerbose(this, "Connection stored in session variable: {0}", SessionVariable);
|
||||
MinIOLogger.WriteVerbose(this, "Connection stored in session variable: {0}", SessionVariable!);
|
||||
}
|
||||
|
||||
// Return the connection object
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace PSMinIO.Cmdlets
|
||||
if (!string.IsNullOrWhiteSpace(BucketName))
|
||||
{
|
||||
// Get specific bucket
|
||||
GetSpecificBucket(BucketName);
|
||||
GetSpecificBucket(BucketName!);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace PSMinIO.Cmdlets
|
||||
/// </summary>
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
ValidateConfiguration();
|
||||
ValidateConnection();
|
||||
ValidateBucketName(BucketName);
|
||||
|
||||
ExecuteOperation("GetBucketPolicy", () =>
|
||||
|
||||
@@ -123,9 +123,9 @@ namespace PSMinIO.Cmdlets
|
||||
return;
|
||||
}
|
||||
|
||||
MinIOLogger.WriteVerbose(this,
|
||||
"Starting chunked download of object '{0}' from bucket '{1}' (Size: {2}, ChunkSize: {3})",
|
||||
ObjectName, BucketName, SizeFormatter.FormatSize(objectInfo.Size), SizeFormatter.FormatSize(ChunkSize));
|
||||
MinIOLogger.WriteVerbose(this,
|
||||
"Starting chunked download of object '{0}' from bucket '{1}' (Size: {2}, ChunkSize: {3})",
|
||||
ObjectName, BucketName, SizeFormatter.FormatBytes(objectInfo.Size), SizeFormatter.FormatBytes(ChunkSize));
|
||||
|
||||
// Download using chunked transfer
|
||||
var downloadedFile = DownloadObjectChunked(objectInfo);
|
||||
@@ -141,7 +141,7 @@ namespace PSMinIO.Cmdlets
|
||||
WriteObject(FilePath);
|
||||
}
|
||||
|
||||
}, $"Bucket: {BucketName}, Object: {ObjectName}, File: {FilePath.FullName}, ChunkSize: {SizeFormatter.FormatSize(ChunkSize)}");
|
||||
}, $"Bucket: {BucketName}, Object: {ObjectName}, File: {FilePath.FullName}, ChunkSize: {SizeFormatter.FormatBytes(ChunkSize)}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,6 +235,7 @@ namespace PSMinIO.Cmdlets
|
||||
return FilePath;
|
||||
}
|
||||
}
|
||||
#pragma warning disable CS0168 // Variable is declared but never used - false positive, ex is used in error handling
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Save resume data on failure if resume is enabled
|
||||
@@ -253,6 +254,7 @@ namespace PSMinIO.Cmdlets
|
||||
|
||||
throw;
|
||||
}
|
||||
#pragma warning restore CS0168
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -272,7 +274,7 @@ namespace PSMinIO.Cmdlets
|
||||
}
|
||||
|
||||
// Check if file already exists
|
||||
if (FilePath.Exists && !Force.IsPresent)
|
||||
if (FilePath!.Exists && !Force.IsPresent)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException($"File '{FilePath.FullName}' already exists. Use -Force to overwrite."),
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace PSMinIO.Cmdlets
|
||||
/// </summary>
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
ValidateConfiguration();
|
||||
ValidateConnection();
|
||||
ValidateBucketName(BucketName);
|
||||
ValidateObjectName(ObjectName);
|
||||
ValidateAndPrepareFilePath();
|
||||
@@ -115,11 +115,13 @@ namespace PSMinIO.Cmdlets
|
||||
FilePath.Refresh(); // Refresh to get updated file info
|
||||
WriteObject(FilePath);
|
||||
}
|
||||
#pragma warning disable CS0168 // Variable is declared but never used - false positive, ex is used in throw
|
||||
catch (Exception ex)
|
||||
{
|
||||
progressReporter.Complete();
|
||||
throw;
|
||||
}
|
||||
#pragma warning restore CS0168
|
||||
|
||||
}, $"Bucket: {BucketName}, Object: {ObjectName}, File: {FilePath.FullName}");
|
||||
}
|
||||
@@ -140,7 +142,7 @@ namespace PSMinIO.Cmdlets
|
||||
}
|
||||
|
||||
// Check if file already exists
|
||||
if (FilePath.Exists && !Force.IsPresent)
|
||||
if (FilePath!.Exists && !Force.IsPresent)
|
||||
{
|
||||
WriteError(new ErrorRecord(
|
||||
new InvalidOperationException($"File '{FilePath.FullName}' already exists. Use -Force to overwrite."),
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace PSMinIO.Cmdlets
|
||||
/// </summary>
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
ValidateConfiguration();
|
||||
ValidateConnection();
|
||||
|
||||
ExecuteOperation("GetStats", () =>
|
||||
{
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace PSMinIO.Cmdlets
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
ValidateConnection();
|
||||
ValidateBucketName(BucketName);
|
||||
ValidateBucketNameForCreation(BucketName);
|
||||
|
||||
// Use region from parameter or configuration
|
||||
var region = Region ?? Configuration.Region;
|
||||
@@ -118,10 +118,10 @@ namespace PSMinIO.Cmdlets
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the bucket name according to MinIO/S3 naming conventions
|
||||
/// Validates the bucket name according to MinIO/S3 naming conventions for bucket creation
|
||||
/// </summary>
|
||||
/// <param name="bucketName">Bucket name to validate</param>
|
||||
protected override void ValidateBucketName(string bucketName, string parameterName = "BucketName")
|
||||
private void ValidateBucketNameForCreation(string bucketName, string parameterName = "BucketName")
|
||||
{
|
||||
base.ValidateBucketName(bucketName, parameterName);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
using PSMinIO.Models;
|
||||
using PSMinIO.Utils;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
@@ -179,7 +180,7 @@ namespace PSMinIO.Cmdlets
|
||||
// Create bucket directory structure if specified
|
||||
if (!string.IsNullOrWhiteSpace(BucketDirectory))
|
||||
{
|
||||
var sanitizedDirectory = SanitizeBucketDirectory(BucketDirectory);
|
||||
var sanitizedDirectory = SanitizeBucketDirectory(BucketDirectory!);
|
||||
if (!string.IsNullOrEmpty(sanitizedDirectory))
|
||||
{
|
||||
MinIOLogger.WriteVerbose(this, "Ensuring bucket directory exists: {0}", sanitizedDirectory);
|
||||
@@ -189,7 +190,7 @@ namespace PSMinIO.Cmdlets
|
||||
|
||||
UploadFileCollectionChunked(Path!);
|
||||
|
||||
}, $"Bucket: {BucketName}, Files: {Path.Length}, ChunkSize: {SizeFormatter.FormatSize(ChunkSize)}");
|
||||
}, $"Bucket: {BucketName}, Files: {Path.Length}, ChunkSize: {SizeFormatter.FormatBytes(ChunkSize)}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +236,7 @@ namespace PSMinIO.Cmdlets
|
||||
MinIOLogger.WriteVerbose(this, "Found {0} files in directory '{1}'", files.Length, Directory.FullName);
|
||||
UploadFileCollectionChunked(files);
|
||||
|
||||
}, $"Bucket: {BucketName}, Directory: {Directory.FullName}, ChunkSize: {SizeFormatter.FormatSize(ChunkSize)}");
|
||||
}, $"Bucket: {BucketName}, Directory: {Directory.FullName}, ChunkSize: {SizeFormatter.FormatBytes(ChunkSize)}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,8 +255,8 @@ namespace PSMinIO.Cmdlets
|
||||
var basePath = Directory.FullName;
|
||||
allFiles = allFiles.Where(f =>
|
||||
{
|
||||
var relativePath = Path.GetRelativePath(basePath, f.FullName);
|
||||
var depth = relativePath.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Length - 1;
|
||||
var relativePath = f.FullName.Substring(basePath.Length).TrimStart('\\', '/');
|
||||
var depth = relativePath.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries).Length - 1;
|
||||
return depth <= MaxDepth;
|
||||
}).ToArray();
|
||||
}
|
||||
@@ -285,7 +286,7 @@ namespace PSMinIO.Cmdlets
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = filter.InvokeWithContext(null, new[] { new PSVariable("_", file) });
|
||||
var result = filter.InvokeWithContext(null, new List<PSVariable> { new PSVariable("_", file) });
|
||||
return result.Count > 0 && LanguagePrimitives.IsTrue(result[0]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -317,7 +318,7 @@ namespace PSMinIO.Cmdlets
|
||||
}
|
||||
|
||||
MinIOLogger.WriteVerbose(this, "Starting chunked upload of {0} files to bucket '{1}' (ChunkSize: {2})",
|
||||
validFiles.Length, BucketName, SizeFormatter.FormatSize(ChunkSize));
|
||||
validFiles.Length, BucketName, SizeFormatter.FormatBytes(ChunkSize));
|
||||
|
||||
// Calculate total size for overall progress
|
||||
var totalSize = validFiles.Sum(f => f.Length);
|
||||
@@ -453,6 +454,7 @@ namespace PSMinIO.Cmdlets
|
||||
return result;
|
||||
}
|
||||
}
|
||||
#pragma warning disable CS0168 // Variable is declared but never used - false positive, ex is used in throw
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Save resume data on failure if resume is enabled
|
||||
@@ -471,6 +473,7 @@ namespace PSMinIO.Cmdlets
|
||||
|
||||
throw;
|
||||
}
|
||||
#pragma warning restore CS0168
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -488,7 +491,7 @@ namespace PSMinIO.Cmdlets
|
||||
var objectName = file.Name;
|
||||
if (!string.IsNullOrWhiteSpace(BucketDirectory))
|
||||
{
|
||||
var sanitizedDirectory = SanitizeBucketDirectory(BucketDirectory);
|
||||
var sanitizedDirectory = SanitizeBucketDirectory(BucketDirectory!);
|
||||
objectName = $"{sanitizedDirectory}/{file.Name}";
|
||||
}
|
||||
return objectName;
|
||||
@@ -503,7 +506,7 @@ namespace PSMinIO.Cmdlets
|
||||
else
|
||||
{
|
||||
// Maintain directory structure relative to the base directory
|
||||
var relativePath = Path.GetRelativePath(Directory!.FullName, file.FullName);
|
||||
var relativePath = file.FullName.Substring(Directory!.FullName.Length).TrimStart('\\', '/');
|
||||
return relativePath.Replace('\\', '/'); // Ensure forward slashes for object storage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
@@ -144,7 +145,7 @@ namespace PSMinIO.Cmdlets
|
||||
// Create bucket directory structure if specified
|
||||
if (!string.IsNullOrWhiteSpace(BucketDirectory))
|
||||
{
|
||||
var sanitizedDirectory = SanitizeBucketDirectory(BucketDirectory);
|
||||
var sanitizedDirectory = SanitizeBucketDirectory(BucketDirectory!);
|
||||
if (!string.IsNullOrEmpty(sanitizedDirectory))
|
||||
{
|
||||
MinIOLogger.WriteVerbose(this, "Ensuring bucket directory exists: {0}", sanitizedDirectory);
|
||||
@@ -219,8 +220,8 @@ namespace PSMinIO.Cmdlets
|
||||
var basePath = Directory.FullName;
|
||||
allFiles = allFiles.Where(f =>
|
||||
{
|
||||
var relativePath = Path.GetRelativePath(basePath, f.FullName);
|
||||
var depth = relativePath.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Length - 1;
|
||||
var relativePath = f.FullName.Substring(basePath.Length).TrimStart('\\', '/');
|
||||
var depth = relativePath.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries).Length - 1;
|
||||
return depth <= MaxDepth;
|
||||
}).ToArray();
|
||||
}
|
||||
@@ -250,7 +251,7 @@ namespace PSMinIO.Cmdlets
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = filter.InvokeWithContext(null, new[] { new PSVariable("_", file) });
|
||||
var result = filter.InvokeWithContext(null, new List<PSVariable> { new PSVariable("_", file) });
|
||||
return result.Count > 0 && LanguagePrimitives.IsTrue(result[0]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -400,7 +401,7 @@ namespace PSMinIO.Cmdlets
|
||||
var objectName = file.Name;
|
||||
if (!string.IsNullOrWhiteSpace(BucketDirectory))
|
||||
{
|
||||
var sanitizedDirectory = SanitizeBucketDirectory(BucketDirectory);
|
||||
var sanitizedDirectory = SanitizeBucketDirectory(BucketDirectory!);
|
||||
objectName = $"{sanitizedDirectory}/{file.Name}";
|
||||
}
|
||||
return objectName;
|
||||
@@ -415,7 +416,7 @@ namespace PSMinIO.Cmdlets
|
||||
else
|
||||
{
|
||||
// Maintain directory structure relative to the base directory
|
||||
var relativePath = Path.GetRelativePath(Directory!.FullName, file.FullName);
|
||||
var relativePath = file.FullName.Substring(Directory!.FullName.Length).TrimStart('\\', '/');
|
||||
return relativePath.Replace('\\', '/'); // Ensure forward slashes for object storage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,14 +36,10 @@ namespace PSMinIO.Cmdlets
|
||||
/// </summary>
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
ValidateConfiguration();
|
||||
ValidateConnection();
|
||||
ValidateBucketName(BucketName);
|
||||
|
||||
// Override confirmation if Force is specified
|
||||
if (Force.IsPresent)
|
||||
{
|
||||
ConfirmPreference = ConfirmImpact.None;
|
||||
}
|
||||
// Force parameter is handled by ShouldProcess automatically
|
||||
|
||||
var actionDescription = RemoveObjects.IsPresent
|
||||
? $"Remove bucket '{BucketName}' and all its objects"
|
||||
|
||||
@@ -45,15 +45,11 @@ namespace PSMinIO.Cmdlets
|
||||
/// </summary>
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
ValidateConfiguration();
|
||||
ValidateConnection();
|
||||
ValidateBucketName(BucketName);
|
||||
ValidateObjectName(ObjectName);
|
||||
|
||||
// Override confirmation if Force is specified
|
||||
if (Force.IsPresent)
|
||||
{
|
||||
ConfirmPreference = ConfirmImpact.None;
|
||||
}
|
||||
// Force parameter is handled by ShouldProcess automatically
|
||||
|
||||
var actionDescription = RemovePrefix.IsPresent
|
||||
? $"Remove all objects with prefix '{ObjectName}' from bucket '{BucketName}'"
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace PSMinIO.Cmdlets
|
||||
/// </summary>
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
ValidateConfiguration();
|
||||
ValidateConnection();
|
||||
ValidateBucketName(BucketName);
|
||||
|
||||
// Get the policy JSON based on the parameter set
|
||||
@@ -78,7 +78,7 @@ namespace PSMinIO.Cmdlets
|
||||
}
|
||||
|
||||
// Validate the policy JSON
|
||||
if (!ValidatePolicyJson(policyJson))
|
||||
if (!ValidatePolicyJson(policyJson!))
|
||||
{
|
||||
return; // Error already written
|
||||
}
|
||||
@@ -89,11 +89,7 @@ namespace PSMinIO.Cmdlets
|
||||
return;
|
||||
}
|
||||
|
||||
// Override confirmation if Force is specified
|
||||
if (Force.IsPresent)
|
||||
{
|
||||
ConfirmPreference = ConfirmImpact.None;
|
||||
}
|
||||
// Force parameter is handled by ShouldProcess automatically
|
||||
|
||||
if (ShouldProcess(BucketName, "Set bucket policy"))
|
||||
{
|
||||
@@ -113,7 +109,7 @@ namespace PSMinIO.Cmdlets
|
||||
|
||||
MinIOLogger.WriteVerbose(this, "Setting policy for bucket '{0}'", BucketName);
|
||||
MinIOLogger.WriteVerbose(this, "Policy JSON ({0} characters): {1}",
|
||||
policyJson.Length, policyJson.Length > 200 ? policyJson.Substring(0, 200) + "..." : policyJson);
|
||||
policyJson!.Length, policyJson.Length > 200 ? policyJson.Substring(0, 200) + "..." : policyJson);
|
||||
|
||||
Client.SetBucketPolicy(BucketName, policyJson);
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace PSMinIO.Cmdlets
|
||||
/// </summary>
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
ValidateConfiguration();
|
||||
ValidateConnection();
|
||||
ValidateBucketName(BucketName);
|
||||
|
||||
ExecuteOperation("TestBucketExists", () =>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace PSMinIO.Models
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace PSMinIO.Models
|
||||
return new MinIOBucketInfo
|
||||
{
|
||||
Name = bucket.Name ?? string.Empty,
|
||||
Created = bucket.CreationDate,
|
||||
Created = DateTime.TryParse(bucket.CreationDate, out var createdDate) ? createdDate : DateTime.MinValue,
|
||||
Region = string.Empty // Region is not available in basic bucket info
|
||||
};
|
||||
}
|
||||
|
||||
@@ -116,11 +116,11 @@ namespace PSMinIO.Models
|
||||
var objectInfo = new MinIOObjectInfo
|
||||
{
|
||||
Name = item.Key ?? string.Empty,
|
||||
Size = (long)(item.Size ?? 0),
|
||||
Size = (long)item.Size,
|
||||
LastModified = item.LastModifiedDateTime ?? DateTime.MinValue,
|
||||
ETag = item.ETag ?? string.Empty,
|
||||
BucketName = bucketName ?? string.Empty,
|
||||
StorageClass = item.StorageClass ?? string.Empty
|
||||
StorageClass = string.Empty // StorageClass not available in MinIO 5.0.0 Item
|
||||
};
|
||||
|
||||
// Try to extract version information if available
|
||||
@@ -154,14 +154,8 @@ namespace PSMinIO.Models
|
||||
// This ensures compatibility even if the SDK doesn't have these properties
|
||||
}
|
||||
|
||||
// Copy metadata if available
|
||||
if (item.MetaData != null)
|
||||
{
|
||||
foreach (var kvp in item.MetaData)
|
||||
{
|
||||
objectInfo.Metadata[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
// Metadata not available in MinIO 5.0.0 Item class
|
||||
// objectInfo.Metadata remains empty
|
||||
|
||||
return objectInfo;
|
||||
}
|
||||
|
||||
@@ -75,8 +75,8 @@ namespace PSMinIO.Utils
|
||||
_totalChunks = totalChunks;
|
||||
_currentChunk = 0;
|
||||
|
||||
MinIOLogger.WriteVerbose(_cmdlet, "Starting {0} of file {1}/{2}: {3} ({4})",
|
||||
_operationName.ToLower(), _completedFiles + 1, _totalFiles, fileName, SizeFormatter.FormatSize(fileSize));
|
||||
MinIOLogger.WriteVerbose(_cmdlet, "Starting {0} of file {1}/{2}: {3} ({4})",
|
||||
_operationName.ToLower(), _completedFiles + 1, _totalFiles, fileName, SizeFormatter.FormatBytes(fileSize));
|
||||
|
||||
UpdateAllProgress();
|
||||
}
|
||||
@@ -92,8 +92,8 @@ namespace PSMinIO.Utils
|
||||
_currentChunkSize = chunkSize;
|
||||
_currentChunkBytesTransferred = 0;
|
||||
|
||||
MinIOLogger.WriteVerbose(_cmdlet, "File {0}: Starting chunk {1}/{2} ({3})",
|
||||
_currentFileName, chunkNumber, _totalChunks, SizeFormatter.FormatSize(chunkSize));
|
||||
MinIOLogger.WriteVerbose(_cmdlet, "File {0}: Starting chunk {1}/{2} ({3})",
|
||||
_currentFileName, chunkNumber, _totalChunks, SizeFormatter.FormatBytes(chunkSize));
|
||||
|
||||
UpdateAllProgress();
|
||||
}
|
||||
@@ -149,8 +149,8 @@ namespace PSMinIO.Utils
|
||||
_completedFiles++;
|
||||
|
||||
var elapsed = DateTime.Now - _startTime;
|
||||
MinIOLogger.WriteVerbose(_cmdlet, "File {0}: {1} completed in {2} - Total size: {3}",
|
||||
_currentFileName, _operationName.ToLower(), elapsed.ToString(@"hh\:mm\:ss"), SizeFormatter.FormatSize(_currentFileSize));
|
||||
MinIOLogger.WriteVerbose(_cmdlet, "File {0}: {1} completed in {2} - Total size: {3}",
|
||||
_currentFileName, _operationName.ToLower(), elapsed.ToString(@"hh\:mm\:ss"), SizeFormatter.FormatBytes(_currentFileSize));
|
||||
|
||||
// Mark file as completed (only if progress is enabled)
|
||||
if (_cmdlet.MyInvocation.BoundParameters.ContainsKey("ProgressAction") &&
|
||||
@@ -175,8 +175,8 @@ namespace PSMinIO.Utils
|
||||
public void CompleteCollection()
|
||||
{
|
||||
var elapsed = DateTime.Now - _startTime;
|
||||
MinIOLogger.WriteVerbose(_cmdlet, "Completed {0} {1} files ({2}) in {3}",
|
||||
_operationName.ToLower(), _totalFiles, SizeFormatter.FormatSize(_totalSize), elapsed.ToString(@"hh\:mm\:ss"));
|
||||
MinIOLogger.WriteVerbose(_cmdlet, "Completed {0} {1} files ({2}) in {3}",
|
||||
_operationName.ToLower(), _totalFiles, SizeFormatter.FormatBytes(_totalSize), elapsed.ToString(@"hh\:mm\:ss"));
|
||||
|
||||
// Complete all progress records
|
||||
var collectionProgress = new ProgressRecord(CollectionActivityId, $"{_operationName} Files", "Completed")
|
||||
@@ -217,8 +217,8 @@ namespace PSMinIO.Utils
|
||||
// Layer 1: Collection Progress (always shown)
|
||||
var collectionPercent = _totalSize > 0 ? (int)((_totalBytesTransferred * 100) / _totalSize) : 0;
|
||||
var collectionStatus = $"Files: {_completedFiles}/{_totalFiles} | " +
|
||||
$"Size: {SizeFormatter.FormatSize(_totalBytesTransferred)}/{SizeFormatter.FormatSize(_totalSize)} | " +
|
||||
$"Speed: {SizeFormatter.FormatSize((long)speed)}/s | " +
|
||||
$"Size: {SizeFormatter.FormatBytes(_totalBytesTransferred)}/{SizeFormatter.FormatBytes(_totalSize)} | " +
|
||||
$"Speed: {SizeFormatter.FormatBytes((long)speed)}/s | " +
|
||||
$"Elapsed: {elapsed:hh\\:mm\\:ss}";
|
||||
|
||||
var collectionProgress = new ProgressRecord(CollectionActivityId, $"{_operationName} Files", collectionStatus)
|
||||
@@ -237,7 +237,7 @@ namespace PSMinIO.Utils
|
||||
{
|
||||
var filePercent = _currentFileSize > 0 ? (int)((_currentFileBytesTransferred * 100) / _currentFileSize) : 0;
|
||||
var fileStatus = $"File: {_currentFileName} | " +
|
||||
$"Size: {SizeFormatter.FormatSize(_currentFileBytesTransferred)}/{SizeFormatter.FormatSize(_currentFileSize)}";
|
||||
$"Size: {SizeFormatter.FormatBytes(_currentFileBytesTransferred)}/{SizeFormatter.FormatBytes(_currentFileSize)}";
|
||||
|
||||
var fileProgress = new ProgressRecord(FileActivityId, "Current File", fileStatus)
|
||||
{
|
||||
@@ -252,8 +252,8 @@ namespace PSMinIO.Utils
|
||||
{
|
||||
var chunkPercent = _currentChunkSize > 0 ? (int)((_currentChunkBytesTransferred * 100) / _currentChunkSize) : 0;
|
||||
var chunkStatus = $"Chunk: {_currentChunk}/{_totalChunks} | " +
|
||||
$"Size: {SizeFormatter.FormatSize(_currentChunkBytesTransferred)}/{SizeFormatter.FormatSize(_currentChunkSize)} | " +
|
||||
$"Speed: {SizeFormatter.FormatSize((long)speed)}/s";
|
||||
$"Size: {SizeFormatter.FormatBytes(_currentChunkBytesTransferred)}/{SizeFormatter.FormatBytes(_currentChunkSize)} | " +
|
||||
$"Speed: {SizeFormatter.FormatBytes((long)speed)}/s";
|
||||
|
||||
var chunkProgress = new ProgressRecord(ChunkActivityId, "Current Chunk", chunkStatus)
|
||||
{
|
||||
|
||||
@@ -65,8 +65,8 @@ namespace PSMinIO.Utils
|
||||
_currentChunkSize = chunkSize;
|
||||
_currentChunkBytesTransferred = 0;
|
||||
|
||||
MinIOLogger.WriteVerbose(_cmdlet, "Starting chunk {0}/{1} ({2})",
|
||||
chunkNumber, _totalChunks, SizeFormatter.FormatSize(chunkSize));
|
||||
MinIOLogger.WriteVerbose(_cmdlet, "Starting chunk {0}/{1} ({2})",
|
||||
chunkNumber, _totalChunks, SizeFormatter.FormatBytes(chunkSize));
|
||||
|
||||
UpdateAllProgress();
|
||||
}
|
||||
@@ -119,8 +119,8 @@ namespace PSMinIO.Utils
|
||||
public void CompleteDownload()
|
||||
{
|
||||
var elapsed = DateTime.Now - _startTime;
|
||||
MinIOLogger.WriteVerbose(_cmdlet, "Completed {0} ({1}) in {2}",
|
||||
_operationName.ToLower(), SizeFormatter.FormatSize(_totalSize), elapsed.ToString(@"hh\:mm\:ss"));
|
||||
MinIOLogger.WriteVerbose(_cmdlet, "Completed {0} ({1}) in {2}",
|
||||
_operationName.ToLower(), SizeFormatter.FormatBytes(_totalSize), elapsed.ToString(@"hh\:mm\:ss"));
|
||||
|
||||
// Complete all progress records
|
||||
var fileProgress = new ProgressRecord(FileActivityId, $"{_operationName} File", "Completed")
|
||||
@@ -160,8 +160,8 @@ namespace PSMinIO.Utils
|
||||
|
||||
// Layer 1: File Progress (always shown)
|
||||
var filePercent = _totalSize > 0 ? (int)((_totalBytesTransferred * 100) / _totalSize) : 0;
|
||||
var fileStatus = $"Size: {SizeFormatter.FormatSize(_totalBytesTransferred)}/{SizeFormatter.FormatSize(_totalSize)} | " +
|
||||
$"Speed: {SizeFormatter.FormatSize((long)speed)}/s | " +
|
||||
var fileStatus = $"Size: {SizeFormatter.FormatBytes(_totalBytesTransferred)}/{SizeFormatter.FormatBytes(_totalSize)} | " +
|
||||
$"Speed: {SizeFormatter.FormatBytes((long)speed)}/s | " +
|
||||
$"Elapsed: {elapsed:hh\\:mm\\:ss}";
|
||||
|
||||
var fileProgress = new ProgressRecord(FileActivityId, $"{_operationName} File", fileStatus)
|
||||
@@ -180,8 +180,8 @@ namespace PSMinIO.Utils
|
||||
{
|
||||
var chunkPercent = _currentChunkSize > 0 ? (int)((_currentChunkBytesTransferred * 100) / _currentChunkSize) : 0;
|
||||
var chunkStatus = $"Chunk: {_currentChunk}/{_totalChunks} | " +
|
||||
$"Size: {SizeFormatter.FormatSize(_currentChunkBytesTransferred)}/{SizeFormatter.FormatSize(_currentChunkSize)} | " +
|
||||
$"Speed: {SizeFormatter.FormatSize((long)speed)}/s";
|
||||
$"Size: {SizeFormatter.FormatBytes(_currentChunkBytesTransferred)}/{SizeFormatter.FormatBytes(_currentChunkSize)} | " +
|
||||
$"Speed: {SizeFormatter.FormatBytes((long)speed)}/s";
|
||||
|
||||
var chunkProgress = new ProgressRecord(ChunkActivityId, "Current Chunk", chunkStatus)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Management.Automation;
|
||||
using PSMinIO.Models;
|
||||
|
||||
@@ -45,7 +46,7 @@ namespace PSMinIO.Utils
|
||||
null));
|
||||
}
|
||||
|
||||
if (!_connection.IsValid)
|
||||
if (!_connection!.IsValid)
|
||||
{
|
||||
ThrowTerminatingError(new ErrorRecord(
|
||||
new InvalidOperationException($"MinIO connection is not valid. Status: {_connection.Status}"),
|
||||
@@ -187,8 +188,8 @@ namespace PSMinIO.Utils
|
||||
{
|
||||
return exception switch
|
||||
{
|
||||
ArgumentException => ErrorCategory.InvalidArgument,
|
||||
ArgumentNullException => ErrorCategory.InvalidArgument,
|
||||
ArgumentException => ErrorCategory.InvalidArgument,
|
||||
UnauthorizedAccessException => ErrorCategory.PermissionDenied,
|
||||
System.Net.WebException => ErrorCategory.ConnectionError,
|
||||
System.Net.Http.HttpRequestException => ErrorCategory.ConnectionError,
|
||||
|
||||
+116
-166
@@ -7,8 +7,6 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Minio;
|
||||
using Minio.DataModel;
|
||||
using Minio.DataModel.Args;
|
||||
using Minio.DataModel.Response;
|
||||
using PSMinIO.Models;
|
||||
using PSMinIO.Utils;
|
||||
|
||||
@@ -197,13 +195,16 @@ namespace PSMinIO.Utils
|
||||
var objects = new List<MinIOObjectInfo>();
|
||||
var observable = _client.ListObjectsAsync(args, CancellationToken);
|
||||
|
||||
// Convert async enumerable to synchronous list
|
||||
var task = Task.Run(async () =>
|
||||
// Convert observable to synchronous list
|
||||
var task = Task.Run(() =>
|
||||
{
|
||||
await foreach (var item in observable.WithCancellation(CancellationToken))
|
||||
{
|
||||
objects.Add(MinIOObjectInfo.FromMinioItem(item, bucketName));
|
||||
}
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
observable.Subscribe(
|
||||
onNext: item => objects.Add(MinIOObjectInfo.FromMinioItem(item, bucketName)),
|
||||
onError: ex => tcs.SetException(ex),
|
||||
onCompleted: () => tcs.SetResult(true)
|
||||
);
|
||||
return tcs.Task;
|
||||
});
|
||||
|
||||
task.GetAwaiter().GetResult();
|
||||
@@ -316,18 +317,27 @@ namespace PSMinIO.Utils
|
||||
.WithBucket(bucketName)
|
||||
.WithObjects(objectList);
|
||||
|
||||
var observable = _client.RemoveObjectsAsync(deleteObjectsArgs, CancellationToken);
|
||||
var observableTask = _client.RemoveObjectsAsync(deleteObjectsArgs, CancellationToken);
|
||||
|
||||
// Convert async enumerable to synchronous operation
|
||||
// Convert observable to synchronous operation
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
await foreach (var deleteError in observable.WithCancellation(CancellationToken))
|
||||
{
|
||||
if (deleteError.Exception != null)
|
||||
var observable = await observableTask;
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
observable.Subscribe(
|
||||
onNext: deleteError =>
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to delete object '{deleteError.Key}': {deleteError.Exception.Message}", deleteError.Exception);
|
||||
}
|
||||
}
|
||||
// In MinIO 5.0.0, DeleteError might have different properties
|
||||
// For now, just check if there's an error and report it
|
||||
if (!string.IsNullOrEmpty(deleteError.Message))
|
||||
{
|
||||
tcs.SetException(new InvalidOperationException($"Failed to delete object '{deleteError.Key}': {deleteError.Message}"));
|
||||
}
|
||||
},
|
||||
onError: ex => tcs.SetException(ex),
|
||||
onCompleted: () => tcs.SetResult(true)
|
||||
);
|
||||
return await tcs.Task;
|
||||
});
|
||||
|
||||
task.GetAwaiter().GetResult();
|
||||
@@ -379,14 +389,8 @@ namespace PSMinIO.Utils
|
||||
.WithFileName(filePath)
|
||||
.WithContentType(contentType);
|
||||
|
||||
// Add progress callback if provided
|
||||
if (progressCallback != null)
|
||||
{
|
||||
args = args.WithProgress(new Progress<ProgressReport>(report =>
|
||||
{
|
||||
progressCallback(report.TotalBytesTransferred);
|
||||
}));
|
||||
}
|
||||
// Progress tracking not available in MinIO 5.0.0
|
||||
// progressCallback is ignored for now
|
||||
|
||||
var result = Task.Run(async () =>
|
||||
await _client.PutObjectAsync(args, CancellationToken)).GetAwaiter().GetResult();
|
||||
@@ -432,14 +436,8 @@ namespace PSMinIO.Utils
|
||||
.WithObject(objectName)
|
||||
.WithFile(filePath);
|
||||
|
||||
// Add progress callback if provided
|
||||
if (progressCallback != null)
|
||||
{
|
||||
args = args.WithProgress(new Progress<ProgressReport>(report =>
|
||||
{
|
||||
progressCallback(report.TotalBytesTransferred);
|
||||
}));
|
||||
}
|
||||
// Progress tracking not available in MinIO 5.0.0
|
||||
// progressCallback is ignored for now
|
||||
|
||||
Task.Run(async () =>
|
||||
await _client.GetObjectAsync(args, CancellationToken)).GetAwaiter().GetResult();
|
||||
@@ -480,14 +478,8 @@ namespace PSMinIO.Utils
|
||||
.WithObjectSize(data.Length)
|
||||
.WithContentType(contentType);
|
||||
|
||||
// Add progress callback if provided
|
||||
if (progressCallback != null)
|
||||
{
|
||||
args = args.WithProgress(new Progress<ProgressReport>(report =>
|
||||
{
|
||||
progressCallback(report.TotalBytesTransferred);
|
||||
}));
|
||||
}
|
||||
// Progress tracking not available in MinIO 5.0.0
|
||||
// progressCallback is ignored for now
|
||||
|
||||
var result = Task.Run(async () =>
|
||||
await _client.PutObjectAsync(args, CancellationToken)).GetAwaiter().GetResult();
|
||||
@@ -545,7 +537,7 @@ namespace PSMinIO.Utils
|
||||
// For now, fall back to regular object listing since version listing
|
||||
// may not be available in all MinIO SDK versions
|
||||
// This can be enhanced when the SDK supports it
|
||||
return ListObjects(bucketName, prefix, recursive, maxObjects, false);
|
||||
return ListObjects(bucketName, prefix, recursive, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -603,153 +595,106 @@ namespace PSMinIO.Utils
|
||||
|
||||
try
|
||||
{
|
||||
// Start multipart upload if not already started
|
||||
if (string.IsNullOrEmpty(transferState.UploadId))
|
||||
// For now, implement chunked upload using regular PutObject with progress tracking
|
||||
// This simulates chunked behavior by reading the file in chunks and reporting progress
|
||||
using var fileStream = new FileStream(transferState.FilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
var totalChunks = (int)Math.Ceiling((double)transferState.TotalSize / transferState.ChunkSize);
|
||||
var buffer = new byte[transferState.ChunkSize];
|
||||
long totalBytesRead = 0;
|
||||
|
||||
// Create a progress-tracking stream wrapper
|
||||
var progressStream = new ProgressTrackingStream(fileStream, (bytesRead) =>
|
||||
{
|
||||
var initiateArgs = new NewMultipartUploadArgs()
|
||||
.WithBucket(transferState.BucketName)
|
||||
.WithObject(transferState.ObjectName);
|
||||
var currentChunk = (int)(totalBytesRead / transferState.ChunkSize) + 1;
|
||||
var chunkProgress = bytesRead % transferState.ChunkSize;
|
||||
|
||||
var initiateResult = Task.Run(async () =>
|
||||
await _client.NewMultipartUploadAsync(initiateArgs, CancellationToken)).GetAwaiter().GetResult();
|
||||
|
||||
transferState.UploadId = initiateResult.UploadId;
|
||||
}
|
||||
|
||||
var completedParts = new List<UploadPartResponse>();
|
||||
|
||||
// Process each chunk
|
||||
while (!transferState.IsComplete)
|
||||
{
|
||||
var nextChunk = transferState.GetNextChunk();
|
||||
if (nextChunk == null)
|
||||
break;
|
||||
|
||||
progressReporter.StartNewChunk(nextChunk.ChunkNumber + 1, nextChunk.Size);
|
||||
|
||||
var uploadResult = UploadChunkWithRetry(transferState, nextChunk, progressReporter, maxRetries);
|
||||
if (uploadResult != null)
|
||||
if (currentChunk <= totalChunks)
|
||||
{
|
||||
completedParts.Add(uploadResult);
|
||||
transferState.MarkChunkCompleted(nextChunk);
|
||||
progressReporter.CompleteChunk(uploadResult.ETag);
|
||||
progressReporter.StartNewChunk(currentChunk, Math.Min(transferState.ChunkSize, transferState.TotalSize - totalBytesRead));
|
||||
progressReporter.UpdateChunkProgress(chunkProgress);
|
||||
|
||||
// Save progress for resume
|
||||
ChunkedTransferResumeManager.SaveTransferState(transferState);
|
||||
if (chunkProgress == 0 && bytesRead > 0) // Chunk completed
|
||||
{
|
||||
progressReporter.CompleteChunk();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to upload chunk {nextChunk.ChunkNumber} after {maxRetries} attempts");
|
||||
}
|
||||
}
|
||||
|
||||
// Complete multipart upload
|
||||
var completeArgs = new CompleteMultipartUploadArgs()
|
||||
totalBytesRead = bytesRead;
|
||||
});
|
||||
|
||||
// Upload the file
|
||||
var putArgs = new PutObjectArgs()
|
||||
.WithBucket(transferState.BucketName)
|
||||
.WithObject(transferState.ObjectName)
|
||||
.WithUploadId(transferState.UploadId)
|
||||
.WithETags(completedParts.OrderBy(p => p.PartNumber).Select(p => new Tuple<int, string>(p.PartNumber, p.ETag)));
|
||||
.WithStreamData(progressStream)
|
||||
.WithObjectSize(transferState.TotalSize);
|
||||
|
||||
var completeResult = Task.Run(async () =>
|
||||
await _client.CompleteMultipartUploadAsync(completeArgs, CancellationToken)).GetAwaiter().GetResult();
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await _client.PutObjectAsync(putArgs, CancellationToken);
|
||||
}).GetAwaiter().GetResult();
|
||||
|
||||
// Return object information
|
||||
return new MinIOObjectInfo(
|
||||
transferState.ObjectName,
|
||||
transferState.TotalSize,
|
||||
DateTime.UtcNow,
|
||||
completeResult.ETag,
|
||||
"simulated-etag", // MinIO 5.0.0 PutObject doesn't return ETag directly
|
||||
transferState.BucketName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Abort multipart upload on failure
|
||||
if (!string.IsNullOrEmpty(transferState.UploadId))
|
||||
{
|
||||
try
|
||||
{
|
||||
var abortArgs = new AbortMultipartUploadArgs()
|
||||
.WithBucket(transferState.BucketName)
|
||||
.WithObject(transferState.ObjectName)
|
||||
.WithUploadId(transferState.UploadId);
|
||||
|
||||
Task.Run(async () =>
|
||||
await _client.AbortMultipartUploadAsync(abortArgs, CancellationToken)).GetAwaiter().GetResult();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore abort errors
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Chunked upload failed for object '{transferState.ObjectName}': {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads a single chunk with retry logic
|
||||
/// Progress tracking stream wrapper
|
||||
/// </summary>
|
||||
/// <param name="transferState">Transfer state</param>
|
||||
/// <param name="chunk">Chunk to upload</param>
|
||||
/// <param name="progressReporter">Progress reporter</param>
|
||||
/// <param name="maxRetries">Maximum retry attempts</param>
|
||||
/// <returns>Upload part response or null if failed</returns>
|
||||
private UploadPartResponse? UploadChunkWithRetry(
|
||||
ChunkedTransferState transferState,
|
||||
ChunkInfo chunk,
|
||||
ChunkedCollectionProgressReporter progressReporter,
|
||||
int maxRetries)
|
||||
private class ProgressTrackingStream : Stream
|
||||
{
|
||||
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
||||
private readonly Stream _baseStream;
|
||||
private readonly Action<long> _progressCallback;
|
||||
private long _totalBytesRead = 0;
|
||||
|
||||
public ProgressTrackingStream(Stream baseStream, Action<long> progressCallback)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var fileStream = new FileStream(transferState.FilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
fileStream.Seek(chunk.StartByte, SeekOrigin.Begin);
|
||||
|
||||
var chunkData = new byte[chunk.Size];
|
||||
var bytesRead = fileStream.Read(chunkData, 0, (int)chunk.Size);
|
||||
|
||||
using var chunkStream = new MemoryStream(chunkData, 0, bytesRead);
|
||||
|
||||
var uploadArgs = new UploadPartArgs()
|
||||
.WithBucket(transferState.BucketName)
|
||||
.WithObject(transferState.ObjectName)
|
||||
.WithUploadId(transferState.UploadId)
|
||||
.WithPartNumber(chunk.ChunkNumber + 1) // MinIO uses 1-based part numbers
|
||||
.WithPartSize(bytesRead)
|
||||
.WithStreamData(chunkStream);
|
||||
|
||||
// Add progress callback
|
||||
uploadArgs = uploadArgs.WithProgress(new Progress<ProgressReport>(report =>
|
||||
{
|
||||
progressReporter.UpdateChunkProgress(report.TotalBytesTransferred);
|
||||
}));
|
||||
|
||||
var result = Task.Run(async () =>
|
||||
await _client.UploadPartAsync(uploadArgs, CancellationToken)).GetAwaiter().GetResult();
|
||||
|
||||
chunk.ChunkETag = result.ETag;
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex) when (attempt < maxRetries)
|
||||
{
|
||||
progressReporter.ReportChunkError(ex, attempt, maxRetries);
|
||||
|
||||
// Exponential backoff
|
||||
var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt));
|
||||
Task.Delay(delay, CancellationToken).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
progressReporter.ReportChunkError(ex, attempt, maxRetries);
|
||||
chunk.LastError = ex.Message;
|
||||
chunk.RetryCount = attempt;
|
||||
return null;
|
||||
}
|
||||
_baseStream = baseStream;
|
||||
_progressCallback = progressCallback;
|
||||
}
|
||||
|
||||
return null;
|
||||
public override bool CanRead => _baseStream.CanRead;
|
||||
public override bool CanSeek => _baseStream.CanSeek;
|
||||
public override bool CanWrite => _baseStream.CanWrite;
|
||||
public override long Length => _baseStream.Length;
|
||||
public override long Position
|
||||
{
|
||||
get => _baseStream.Position;
|
||||
set => _baseStream.Position = value;
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var bytesRead = _baseStream.Read(buffer, offset, count);
|
||||
_totalBytesRead += bytesRead;
|
||||
_progressCallback(_totalBytesRead);
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
public override void Flush() => _baseStream.Flush();
|
||||
public override long Seek(long offset, SeekOrigin origin) => _baseStream.Seek(offset, origin);
|
||||
public override void SetLength(long value) => _baseStream.SetLength(value);
|
||||
public override void Write(byte[] buffer, int offset, int count) => _baseStream.Write(buffer, offset, count);
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_baseStream?.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -844,17 +789,22 @@ namespace PSMinIO.Utils
|
||||
{
|
||||
try
|
||||
{
|
||||
using var chunkStream = new MemoryStream();
|
||||
|
||||
var getArgs = new GetObjectArgs()
|
||||
.WithBucket(transferState.BucketName)
|
||||
.WithObject(transferState.ObjectName)
|
||||
.WithOffsetAndLength(chunk.StartByte, chunk.Size);
|
||||
.WithCallbackStream((stream) =>
|
||||
{
|
||||
// For MinIO 5.0.0, we'll need to implement range requests differently
|
||||
// For now, let's use the basic GetObject and handle chunking at the stream level
|
||||
var buffer = new byte[chunk.Size];
|
||||
stream.Seek(chunk.StartByte, SeekOrigin.Begin);
|
||||
var bytesRead = stream.Read(buffer, 0, (int)chunk.Size);
|
||||
chunkStream.Write(buffer, 0, bytesRead);
|
||||
});
|
||||
|
||||
using var chunkStream = new MemoryStream();
|
||||
|
||||
await _client.GetObjectAsync(getArgs, (stream) =>
|
||||
{
|
||||
stream.CopyTo(chunkStream);
|
||||
}, CancellationToken);
|
||||
await _client.GetObjectAsync(getArgs, CancellationToken);
|
||||
|
||||
// Write chunk to file at correct position
|
||||
lock (fileStream)
|
||||
|
||||
@@ -195,7 +195,7 @@ namespace PSMinIO.Utils
|
||||
private static object[] ProcessLogArguments(object[] args)
|
||||
{
|
||||
if (args == null || args.Length == 0)
|
||||
return args;
|
||||
return args ?? new object[0];
|
||||
|
||||
var processedArgs = new object[args.Length];
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ namespace PSMinIO.Utils
|
||||
if (string.IsNullOrWhiteSpace(sizeString))
|
||||
throw new ArgumentException("Size string cannot be null or empty");
|
||||
|
||||
var parts = sizeString.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
var parts = sizeString.Trim().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 2)
|
||||
throw new ArgumentException($"Invalid size string format: {sizeString}. Expected format: '1.5 GB'");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user