using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Management.Automation; namespace PSMinIO.Utils { /// /// Zip builder with integrated progress tracking and PowerShell compatibility /// Provides seamless integration with PSMinIO's progress reporting system /// public class ZipBuilder : IDisposable { private readonly PSCmdlet _cmdlet; private readonly ZipArchiveBuilder _zipBuilder; private readonly ThreadSafeProgressCollector _progressCollector; private bool _disposed = false; private int _totalFiles = 0; // Activity IDs for progress tracking private const int ZipActivityId = 10; private const int FileActivityId = 11; /// /// Gets the underlying zip archive builder metrics /// public ZipArchiveBuilder ArchiveBuilder => _zipBuilder; /// /// Creates a new zip builder /// /// PowerShell cmdlet for progress reporting /// Stream to write zip to /// Zip archive mode /// Whether to leave output stream open public ZipBuilder(PSCmdlet cmdlet, Stream outputStream, ZipArchiveMode mode = ZipArchiveMode.Create, bool leaveOpen = false) { _cmdlet = cmdlet ?? throw new ArgumentNullException(nameof(cmdlet)); _zipBuilder = new ZipArchiveBuilder(outputStream, mode, leaveOpen); _progressCollector = new ThreadSafeProgressCollector(cmdlet); // Subscribe to zip builder events _zipBuilder.ProgressChanged += OnProgressChanged; _zipBuilder.FileAdded += OnFileAdded; _zipBuilder.Completed += OnCompleted; } /// /// Creates a zip builder for a file /// /// PowerShell cmdlet for progress reporting /// Path to zip file to create /// Zip archive mode public static ZipBuilder CreateFile(PSCmdlet cmdlet, string zipFilePath, ZipArchiveMode mode = ZipArchiveMode.Create) { var fileStream = new FileStream(zipFilePath, FileMode.Create, FileAccess.Write); return new ZipBuilder(cmdlet, fileStream, mode, false); } /// /// Adds a single file with PowerShell progress reporting /// /// File to add /// Entry name in zip (optional) /// Compression level public void AddFile(FileInfo fileInfo, string? entryName = null, CompressionLevel compressionLevel = CompressionLevel.Optimal) { if (fileInfo == null) throw new ArgumentNullException(nameof(fileInfo)); MinIOLogger.WriteVerbose(_cmdlet, "Adding file to zip: {0} ({1})", fileInfo.Name, SizeFormatter.FormatBytes(fileInfo.Length)); _zipBuilder.AddFile(fileInfo, entryName, compressionLevel); } /// /// Adds multiple files with comprehensive progress tracking /// /// Files to add /// Base path for entry names (optional) /// Compression level public void AddFiles(IEnumerable files, string? basePath = null, CompressionLevel compressionLevel = CompressionLevel.Optimal) { if (files == null) throw new ArgumentNullException(nameof(files)); var fileList = files.ToList(); _totalFiles = fileList.Count; var totalSize = fileList.OfType().Sum(f => f.Length); MinIOLogger.WriteVerbose(_cmdlet, "Starting zip compression: {0} files, {1} total size", _totalFiles, SizeFormatter.FormatBytes(totalSize)); // Initialize overall progress _progressCollector.QueueProgressUpdate(ZipActivityId, "Creating Zip Archive", $"Preparing to compress {_totalFiles} files", 0); _zipBuilder.AddFiles(files, basePath, compressionLevel); } /// /// Adds a directory with progress tracking /// /// Directory to add /// Include base directory in entry names /// Compression level public void AddDirectory(DirectoryInfo directoryInfo, bool includeBaseDirectory = true, CompressionLevel compressionLevel = CompressionLevel.Optimal) { if (directoryInfo == null) throw new ArgumentNullException(nameof(directoryInfo)); var files = directoryInfo.GetFiles("*", SearchOption.AllDirectories); _totalFiles = files.Length; var totalSize = files.Sum(f => f.Length); MinIOLogger.WriteVerbose(_cmdlet, "Adding directory to zip: {0} ({1} files, {2})", directoryInfo.Name, _totalFiles, SizeFormatter.FormatBytes(totalSize)); _zipBuilder.AddDirectory(directoryInfo, includeBaseDirectory, compressionLevel); } /// /// Completes the zip and processes final progress updates /// public void Complete() { _zipBuilder.Complete(); _progressCollector.Complete(); MinIOLogger.WriteVerbose(_cmdlet, "Zip compression completed: {0} files, {1} -> {2} ({3:F1}% compression)", _zipBuilder.FileCount, SizeFormatter.FormatBytes(_zipBuilder.TotalUncompressedSize), SizeFormatter.FormatBytes(_zipBuilder.TotalCompressedSize), (1 - _zipBuilder.CompressionRatio) * 100); } /// /// Handles progress updates from the zip builder /// private void OnProgressChanged(object? sender, ZipProgressEventArgs e) { // Update file-level progress _progressCollector.QueueProgressUpdate(FileActivityId, "Compressing File", $"Processing: {e.CurrentFileName} ({SizeFormatter.FormatBytes(e.CurrentFileBytesProcessed)}/{SizeFormatter.FormatBytes(e.CurrentFileSize)})", (int)e.CurrentFileProgress, ZipActivityId); // Update overall progress with file count var currentFile = e.TotalFilesProcessed + 1; // +1 because we're currently processing this file var overallStatus = _totalFiles > 0 ? $"Processing file {currentFile} of {_totalFiles}" : $"Compressed {e.TotalFilesProcessed} files"; if (e.ElapsedTime.TotalSeconds > 0) { var speed = e.TotalBytesProcessed / e.ElapsedTime.TotalSeconds; overallStatus += $" at {SizeFormatter.FormatSpeed(speed)}"; } // Calculate overall progress percentage var overallProgress = _totalFiles > 0 ? (int)((double)e.TotalFilesProcessed / _totalFiles * 100) : -1; _progressCollector.QueueProgressUpdate(ZipActivityId, "Creating Zip Archive", overallStatus, overallProgress); // Process queued updates _progressCollector.ProcessQueuedUpdates(); } /// /// Handles file added events from the zip builder /// private void OnFileAdded(object? sender, ZipFileEventArgs e) { _progressCollector.QueueVerboseMessage("Compressed: {0} -> {1} ({2:F1}% reduction, {3})", e.FileName, SizeFormatter.FormatBytes(e.CompressedSize), e.CompressionEfficiency, SizeFormatter.FormatDuration(e.ProcessingTime)); // Complete file progress _progressCollector.QueueProgressCompletion(FileActivityId, "Compressing File", ZipActivityId); } /// /// Handles completion events from the zip builder /// private void OnCompleted(object? sender, ZipCompletedEventArgs e) { _progressCollector.QueueVerboseMessage("Zip compression summary:"); _progressCollector.QueueVerboseMessage(" Files: {0}", e.TotalFiles); _progressCollector.QueueVerboseMessage(" Original size: {0}", SizeFormatter.FormatBytes(e.TotalUncompressedSize)); _progressCollector.QueueVerboseMessage(" Compressed size: {0}", SizeFormatter.FormatBytes(e.TotalCompressedSize)); _progressCollector.QueueVerboseMessage(" Space saved: {0} ({1:F1}%)", SizeFormatter.FormatBytes(e.SpaceSaved), e.CompressionEfficiency); _progressCollector.QueueVerboseMessage(" Duration: {0}", SizeFormatter.FormatDuration(e.Duration)); _progressCollector.QueueVerboseMessage(" Average speed: {0}", SizeFormatter.FormatSpeed(e.AverageCompressionSpeed)); // Complete overall progress _progressCollector.QueueProgressCompletion(ZipActivityId, "Creating Zip Archive"); } /// /// Creates a zip result object with comprehensive metrics /// /// Path to the created zip file /// Zip creation result public ZipCreationResult CreateResult(string zipFilePath) { return new ZipCreationResult { ZipFilePath = zipFilePath, StartTime = _zipBuilder.StartTime, EndTime = _zipBuilder.EndTime ?? DateTime.UtcNow, Duration = _zipBuilder.Duration ?? TimeSpan.Zero, FileCount = _zipBuilder.FileCount, TotalUncompressedSize = _zipBuilder.TotalUncompressedSize, TotalCompressedSize = _zipBuilder.TotalCompressedSize, CompressionRatio = _zipBuilder.CompressionRatio, SpaceSaved = _zipBuilder.TotalUncompressedSize - _zipBuilder.TotalCompressedSize }; } /// /// Disposes the zip builder and resources /// public void Dispose() { if (!_disposed) { Complete(); _zipBuilder?.Dispose(); _disposed = true; } } } /// /// Result object for zip creation operations /// public class ZipCreationResult { public string ZipFilePath { get; set; } = string.Empty; public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public TimeSpan Duration { get; set; } public int FileCount { get; set; } public long TotalUncompressedSize { get; set; } public long TotalCompressedSize { get; set; } public double CompressionRatio { get; set; } public long SpaceSaved { get; set; } public double CompressionEfficiency => (1 - CompressionRatio) * 100; public double AverageSpeed => Duration.TotalSeconds > 0 ? TotalUncompressedSize / Duration.TotalSeconds : 0; } }