zsvo/pkg/fetcher/fetcher.go
itexpert228 e351ee62d1
кал
2026-03-09 22:40:05 +03:00

211 lines
5.2 KiB
Go

package fetcher
import (
"crypto/sha256"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"github.com/mholt/archiver/v3"
)
// Fetcher handles downloading and extracting sources
type Fetcher struct {
cacheDir string
}
// NewFetcher creates a new fetcher
func NewFetcher(cacheDir string) *Fetcher {
return &Fetcher{
cacheDir: cacheDir,
}
}
// Download downloads a file from URL and verifies checksum
func (f *Fetcher) Download(url, expectedHash string) (string, error) {
// Validate input parameters
if url == "" {
return "", fmt.Errorf("URL cannot be empty")
}
if expectedHash == "" {
return "", fmt.Errorf("expected hash cannot be empty")
}
filename := filepath.Base(url)
if filename == "" {
return "", fmt.Errorf("invalid URL: no filename found")
}
cachePath := filepath.Join(f.cacheDir, filename)
// Check if already downloaded and valid
if f.isValidCache(cachePath, expectedHash) {
return cachePath, nil
}
// Create cache directory if it doesn't exist
if err := os.MkdirAll(f.cacheDir, 0755); err != nil {
return "", fmt.Errorf("failed to create cache directory: %w", err)
}
// Download file
resp, err := http.Get(url)
if err != nil {
return "", fmt.Errorf("failed to download %s: %w", url, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("failed to download %s: HTTP %d", url, resp.StatusCode)
}
// Create temporary file
tmpFile, err := os.CreateTemp(f.cacheDir, "download-*")
if err != nil {
return "", fmt.Errorf("failed to create temp file: %w", err)
}
defer tmpFile.Close()
// Download to temp file
hasher := sha256.New()
if _, err := io.Copy(tmpFile, resp.Body); err != nil {
return "", fmt.Errorf("failed to download file: %w", err)
}
// Verify checksum
calculatedHash := fmt.Sprintf("%x", hasher.Sum(nil))
if calculatedHash != expectedHash {
return "", fmt.Errorf("checksum mismatch: expected %s, got %s", expectedHash, calculatedHash)
}
// Move temp file to final location
if err := os.Rename(tmpFile.Name(), cachePath); err != nil {
return "", fmt.Errorf("failed to move downloaded file: %w", err)
}
return cachePath, nil
}
// Extract extracts an archive to destination
func (f *Fetcher) Extract(archivePath, destDir string) error {
// Create destination directory
if err := os.MkdirAll(destDir, 0755); err != nil {
return fmt.Errorf("failed to create destination directory: %w", err)
}
// Extract archive using archiver.Unarchive
return archiver.Unarchive(archivePath, destDir)
}
// isValidCache checks if cached file exists and has correct checksum
func (f *Fetcher) isValidCache(path, expectedHash string) bool {
file, err := os.Open(path)
if err != nil {
return false
}
defer file.Close()
hasher := sha256.New()
if _, err := io.Copy(hasher, file); err != nil {
return false
}
calculatedHash := fmt.Sprintf("%x", hasher.Sum(nil))
return calculatedHash == expectedHash
}
// ApplyPatches applies patch files to source directory
func (f *Fetcher) ApplyPatches(sourceDir string, patchFiles []string) error {
for _, patchFile := range patchFiles {
if err := f.applyPatch(sourceDir, patchFile); err != nil {
return fmt.Errorf("failed to apply patch %s: %w", patchFile, err)
}
}
return nil
}
// applyPatch applies a single patch file
func (f *Fetcher) applyPatch(sourceDir, patchFile string) error {
// This is a simplified patch application
// In a real implementation, you'd want to use the 'patch' command
// For now, we'll just copy the patch file to the source directory
patchDest := filepath.Join(sourceDir, filepath.Base(patchFile))
if err := copyFile(patchFile, patchDest); err != nil {
return fmt.Errorf("failed to copy patch file: %w", err)
}
return nil
}
// copyFile copies a file from src to dst
func copyFile(src, dst string) error {
sourceFile, err := os.Open(src)
if err != nil {
return err
}
defer sourceFile.Close()
destFile, err := os.Create(dst)
if err != nil {
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, sourceFile)
return err
}
// DownloadAndExtract downloads and extracts source
func (f *Fetcher) DownloadAndExtract(url, expectedHash, destDir string) error {
archivePath, err := f.Download(url, expectedHash)
if err != nil {
return err
}
return f.Extract(archivePath, destDir)
}
// DownloadMultiple downloads multiple files concurrently
func (f *Fetcher) DownloadMultiple(urls []string, hashes []string) ([]string, error) {
if len(urls) != len(hashes) {
return nil, fmt.Errorf("urls and hashes length mismatch")
}
paths := make([]string, len(urls))
for i, url := range urls {
path, err := f.Download(url, hashes[i])
if err != nil {
return nil, err
}
paths[i] = path
}
return paths, nil
}
// GetCacheDir returns the cache directory
func (f *Fetcher) GetCacheDir() string {
return f.cacheDir
}
// CleanCache removes old cached files
func (f *Fetcher) CleanCache() error {
return os.RemoveAll(f.cacheDir)
}
// ListCachedFiles lists all cached files
func (f *Fetcher) ListCachedFiles() ([]string, error) {
files, err := os.ReadDir(f.cacheDir)
if err != nil {
return nil, err
}
var result []string
for _, file := range files {
if !file.IsDir() {
result = append(result, file.Name())
}
}
return result, nil
}