(Feat): New feature: Ability to compare different archives

This commit is contained in:
2025-11-21 08:30:09 +00:00
parent 834faf936a
commit a435a6cb79

View File

@@ -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
}