diff --git a/internal/archiver/batch.go b/internal/archiver/batch.go new file mode 100644 index 0000000..ee16349 --- /dev/null +++ b/internal/archiver/batch.go @@ -0,0 +1,191 @@ +package archiver + +import ( + "fmt" + "sync" + + "zipprine/internal/models" +) + +// BatchCompressConfig holds configuration for batch compression operations +type BatchCompressConfig struct { + Configs []*models.CompressConfig + Parallel bool + MaxWorkers int + OnProgress func(index int, total int, filename string) + OnError func(index int, filename string, err error) + OnComplete func(index int, filename string) +} + +// BatchCompress compresses multiple sources in batch +func BatchCompress(batchConfig *BatchCompressConfig) []error { + if batchConfig.MaxWorkers <= 0 { + batchConfig.MaxWorkers = 4 + } + + errors := make([]error, len(batchConfig.Configs)) + + if !batchConfig.Parallel { + // Sequential processing + for i, config := range batchConfig.Configs { + if batchConfig.OnProgress != nil { + batchConfig.OnProgress(i+1, len(batchConfig.Configs), config.OutputPath) + } + + err := Compress(config) + errors[i] = err + + if err != nil && batchConfig.OnError != nil { + batchConfig.OnError(i, config.OutputPath, err) + } else if batchConfig.OnComplete != nil { + batchConfig.OnComplete(i, config.OutputPath) + } + } + return errors + } + + // Parallel processing with worker pool + var wg sync.WaitGroup + jobs := make(chan int, len(batchConfig.Configs)) + + // Start workers + for w := 0; w < batchConfig.MaxWorkers; w++ { + wg.Add(1) + go func() { + defer wg.Done() + for i := range jobs { + config := batchConfig.Configs[i] + + if batchConfig.OnProgress != nil { + batchConfig.OnProgress(i+1, len(batchConfig.Configs), config.OutputPath) + } + + err := Compress(config) + errors[i] = err + + if err != nil && batchConfig.OnError != nil { + batchConfig.OnError(i, config.OutputPath, err) + } else if batchConfig.OnComplete != nil { + batchConfig.OnComplete(i, config.OutputPath) + } + } + }() + } + + // Send jobs + for i := range batchConfig.Configs { + jobs <- i + } + close(jobs) + + wg.Wait() + return errors +} + +// BatchExtractConfig holds configuration for batch extraction operations +type BatchExtractConfig struct { + Configs []*models.ExtractConfig + Parallel bool + MaxWorkers int + OnProgress func(index int, total int, filename string) + OnError func(index int, filename string, err error) + OnComplete func(index int, filename string) +} + +// BatchExtract extracts multiple archives in batch +func BatchExtract(batchConfig *BatchExtractConfig) []error { + if batchConfig.MaxWorkers <= 0 { + batchConfig.MaxWorkers = 4 + } + + errors := make([]error, len(batchConfig.Configs)) + + if !batchConfig.Parallel { + // Sequential processing + for i, config := range batchConfig.Configs { + if batchConfig.OnProgress != nil { + batchConfig.OnProgress(i+1, len(batchConfig.Configs), config.ArchivePath) + } + + err := Extract(config) + errors[i] = err + + if err != nil && batchConfig.OnError != nil { + batchConfig.OnError(i, config.ArchivePath, err) + } else if batchConfig.OnComplete != nil { + batchConfig.OnComplete(i, config.ArchivePath) + } + } + return errors + } + + // Parallel processing with worker pool + var wg sync.WaitGroup + jobs := make(chan int, len(batchConfig.Configs)) + + // Start workers + for w := 0; w < batchConfig.MaxWorkers; w++ { + wg.Add(1) + go func() { + defer wg.Done() + for i := range jobs { + config := batchConfig.Configs[i] + + if batchConfig.OnProgress != nil { + batchConfig.OnProgress(i+1, len(batchConfig.Configs), config.ArchivePath) + } + + err := Extract(config) + errors[i] = err + + if err != nil && batchConfig.OnError != nil { + batchConfig.OnError(i, config.ArchivePath, err) + } else if batchConfig.OnComplete != nil { + batchConfig.OnComplete(i, config.ArchivePath) + } + } + }() + } + + // Send jobs + for i := range batchConfig.Configs { + jobs <- i + } + close(jobs) + + wg.Wait() + return errors +} + +// ConvertArchive converts an archive from one format to another +func ConvertArchive(sourcePath, destPath string, sourceType, destType models.ArchiveType) error { + // Create temporary directory for extraction + tmpDir := destPath + ".tmp" + + // Extract source archive + extractConfig := &models.ExtractConfig{ + ArchivePath: sourcePath, + DestPath: tmpDir, + ArchiveType: sourceType, + OverwriteAll: true, + PreservePerms: true, + } + + if err := Extract(extractConfig); err != nil { + return fmt.Errorf("failed to extract source archive: %w", err) + } + + // Compress to destination format + compressConfig := &models.CompressConfig{ + SourcePath: tmpDir, + OutputPath: destPath, + ArchiveType: destType, + CompressionLevel: 5, + } + + if err := Compress(compressConfig); err != nil { + return fmt.Errorf("failed to create destination archive: %w", err) + } + + return nil +}