211 lines
5.2 KiB
Go
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
|
|
}
|