(Feat): Added a new archive type: RAR
This commit is contained in:
@@ -14,6 +14,8 @@ func Compress(config *models.CompressConfig) error {
|
||||
return createTar(config)
|
||||
case models.GZIP:
|
||||
return createGzip(config)
|
||||
case models.RAR:
|
||||
return createRar(config)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@@ -29,6 +31,8 @@ func Extract(config *models.ExtractConfig) error {
|
||||
return extractTar(config)
|
||||
case models.GZIP:
|
||||
return extractGzip(config)
|
||||
case models.RAR:
|
||||
return extractRar(config)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ func DetectArchiveType(path string) (models.ArchiveType, error) {
|
||||
return models.TAR, nil
|
||||
case ".tgz":
|
||||
return models.TARGZ, nil
|
||||
case ".rar":
|
||||
return models.RAR, nil
|
||||
}
|
||||
|
||||
// Try by magic bytes
|
||||
@@ -70,6 +72,11 @@ func DetectArchiveType(path string) (models.ArchiveType, error) {
|
||||
return models.TAR, nil
|
||||
}
|
||||
|
||||
// RAR magic: Rar! (0x52 0x61 0x72 0x21)
|
||||
if len(header) >= 4 && header[0] == 0x52 && header[1] == 0x61 && header[2] == 0x72 && header[3] == 0x21 {
|
||||
return models.RAR, nil
|
||||
}
|
||||
|
||||
return models.AUTO, nil
|
||||
}
|
||||
|
||||
@@ -86,6 +93,8 @@ func Analyze(path string) (*models.ArchiveInfo, error) {
|
||||
return analyzeTar(path, true)
|
||||
case models.TAR:
|
||||
return analyzeTar(path, false)
|
||||
case models.RAR:
|
||||
return analyzeRar(path)
|
||||
case models.GZIP:
|
||||
// For GZIP, provide basic file info
|
||||
file, err := os.Open(path)
|
||||
|
||||
136
internal/archiver/rar.go
Normal file
136
internal/archiver/rar.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package archiver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"zipprine/internal/models"
|
||||
|
||||
"github.com/nwaples/rardecode"
|
||||
)
|
||||
|
||||
// extractRar extracts a RAR archive
|
||||
func extractRar(config *models.ExtractConfig) error {
|
||||
file, err := os.Open(config.ArchivePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open RAR file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
reader, err := rardecode.NewReader(file, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create RAR reader: %w", err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(config.DestPath, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create destination directory: %w", err)
|
||||
}
|
||||
|
||||
for {
|
||||
header, err := reader.Next()
|
||||
if err != nil {
|
||||
if err.Error() == "EOF" {
|
||||
break
|
||||
}
|
||||
return fmt.Errorf("failed to read RAR entry: %w", err)
|
||||
}
|
||||
|
||||
if header.IsDir {
|
||||
targetPath := filepath.Join(config.DestPath, header.Name)
|
||||
if err := os.MkdirAll(targetPath, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
targetPath := filepath.Join(config.DestPath, header.Name)
|
||||
|
||||
if _, err := os.Stat(targetPath); err == nil && !config.OverwriteAll {
|
||||
fmt.Printf("Skipping existing file: %s\n", header.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
|
||||
return fmt.Errorf("failed to create parent directory: %w", err)
|
||||
}
|
||||
|
||||
outFile, err := os.Create(targetPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create file %s: %w", header.Name, err)
|
||||
}
|
||||
|
||||
if _, err := outFile.ReadFrom(reader); err != nil {
|
||||
outFile.Close()
|
||||
return fmt.Errorf("failed to write file %s: %w", header.Name, err)
|
||||
}
|
||||
outFile.Close()
|
||||
|
||||
// Set permissions if requested
|
||||
if config.PreservePerms {
|
||||
if err := os.Chmod(targetPath, header.Mode()); err != nil {
|
||||
fmt.Printf("Warning: failed to set permissions for %s: %v\n", header.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Extracted: %s\n", header.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// analyzeRar analyzes a RAR archive and returns information about it
|
||||
func analyzeRar(path string) (*models.ArchiveInfo, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open RAR file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
fileStat, _ := file.Stat()
|
||||
|
||||
reader, err := rardecode.NewReader(file, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create RAR reader: %w", err)
|
||||
}
|
||||
|
||||
info := &models.ArchiveInfo{
|
||||
Type: models.RAR,
|
||||
CompressedSize: fileStat.Size(),
|
||||
Files: []models.FileInfo{},
|
||||
}
|
||||
|
||||
for {
|
||||
header, err := reader.Next()
|
||||
if err != nil {
|
||||
if err.Error() == "EOF" {
|
||||
break
|
||||
}
|
||||
return nil, fmt.Errorf("failed to read RAR entry: %w", err)
|
||||
}
|
||||
|
||||
if !header.IsDir {
|
||||
info.FileCount++
|
||||
info.TotalSize += header.UnPackedSize
|
||||
info.Files = append(info.Files, models.FileInfo{
|
||||
Name: header.Name,
|
||||
Size: header.UnPackedSize,
|
||||
IsDir: header.IsDir,
|
||||
ModTime: header.ModificationTime.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if info.TotalSize > 0 {
|
||||
info.CompressionRatio = float64(info.CompressedSize) / float64(info.TotalSize)
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// Note: RAR compression is proprietary and requires a license.
|
||||
// This implementation only supports extraction using the rardecode library.
|
||||
// For compression, users should use WinRAR or other licensed tools.
|
||||
func createRar(config *models.CompressConfig) error {
|
||||
return fmt.Errorf("RAR compression is not supported due to proprietary format. Please use ZIP, TAR, or TAR.GZ for compression")
|
||||
}
|
||||
Reference in New Issue
Block a user