diff --git a/internal/archiver/compare.go b/internal/archiver/compare.go new file mode 100644 index 0000000..c882a7c --- /dev/null +++ b/internal/archiver/compare.go @@ -0,0 +1,151 @@ +package archiver + +import ( + "fmt" + "sort" + + "zipprine/internal/models" +) + +// ComparisonResult holds the result of comparing two archives +type ComparisonResult struct { + OnlyInFirst []string + OnlyInSecond []string + InBoth []string + Different []DifferentFile + Summary string +} + +// DifferentFile represents a file that exists in both archives but differs +type DifferentFile struct { + Name string + Size1 int64 + Size2 int64 + ModTime1 string + ModTime2 string +} + +// CompareArchives compares two archives and returns differences +func CompareArchives(path1, path2 string, type1, type2 models.ArchiveType) (*ComparisonResult, error) { + // Analyze both archives + info1, err := AnalyzeArchive(path1, type1) + if err != nil { + return nil, fmt.Errorf("failed to analyze first archive: %w", err) + } + + info2, err := AnalyzeArchive(path2, type2) + if err != nil { + return nil, fmt.Errorf("failed to analyze second archive: %w", err) + } + + // Create maps for quick lookup + files1 := make(map[string]models.FileInfo) + files2 := make(map[string]models.FileInfo) + + for _, f := range info1.Files { + files1[f.Name] = f + } + + for _, f := range info2.Files { + files2[f.Name] = f + } + + result := &ComparisonResult{ + OnlyInFirst: []string{}, + OnlyInSecond: []string{}, + InBoth: []string{}, + Different: []DifferentFile{}, + } + + // Find files only in first archive + for name := range files1 { + if _, exists := files2[name]; !exists { + result.OnlyInFirst = append(result.OnlyInFirst, name) + } + } + + // Find files only in second archive and files in both + for name, file2 := range files2 { + if file1, exists := files1[name]; !exists { + result.OnlyInSecond = append(result.OnlyInSecond, name) + } else { + result.InBoth = append(result.InBoth, name) + + // Check if files are different + if file1.Size != file2.Size || file1.ModTime != file2.ModTime { + result.Different = append(result.Different, DifferentFile{ + Name: name, + Size1: file1.Size, + Size2: file2.Size, + ModTime1: file1.ModTime, + ModTime2: file2.ModTime, + }) + } + } + } + + // Sort results for consistent output + sort.Strings(result.OnlyInFirst) + sort.Strings(result.OnlyInSecond) + sort.Strings(result.InBoth) + + // Generate summary + result.Summary = fmt.Sprintf( + "Comparison Summary:\n"+ + " Files in both: %d\n"+ + " Only in first: %d\n"+ + " Only in second: %d\n"+ + " Different: %d", + len(result.InBoth), + len(result.OnlyInFirst), + len(result.OnlyInSecond), + len(result.Different), + ) + + return result, nil +} + +// AnalyzeArchive analyzes an archive and returns information about it +func AnalyzeArchive(path string, archiveType models.ArchiveType) (*models.ArchiveInfo, error) { + switch archiveType { + case models.ZIP: + return analyzeZip(path) + case models.TARGZ: + return analyzeTar(path, true) + case models.TAR: + return analyzeTar(path, false) + default: + return nil, fmt.Errorf("unsupported archive type: %s", archiveType) + } +} + +// GetArchiveStats returns quick statistics about an archive +func GetArchiveStats(path string, archiveType models.ArchiveType) (string, error) { + info, err := AnalyzeArchive(path, archiveType) + if err != nil { + return "", err + } + + compressionRatio := 0.0 + if info.TotalSize > 0 { + compressionRatio = (1.0 - float64(info.CompressedSize)/float64(info.TotalSize)) * 100 + } + + stats := fmt.Sprintf( + "Archive Statistics:\n"+ + " Type: %s\n"+ + " Files: %d\n"+ + " Total Size: %d bytes\n"+ + " Compressed Size: %d bytes\n"+ + " Compression Ratio: %.2f%%\n"+ + " Checksum: %s", + info.Type, + info.FileCount, + info.TotalSize, + info.CompressedSize, + compressionRatio, + info.Checksum, + ) + + return stats, nil +}