220 lines
7.1 KiB
Go
220 lines
7.1 KiB
Go
package spm
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"time"
|
|
)
|
|
|
|
// func DownloadPackage(pkg Package, destDir string, version, release, pkgType string) error {
|
|
// client := &http.Client{}
|
|
|
|
// var resp *http.Response
|
|
// var err error
|
|
// for i := 0; i < 3; i++ { // Retry up to 3 times
|
|
// fmt.Printf("[INFO] Attempting to download package from URL: %s (Attempt %d)\n", pkg.DownloadURL, i+1)
|
|
// resp, err = client.Get(pkg.DownloadURL)
|
|
// if err == nil && resp.StatusCode == http.StatusOK {
|
|
// break
|
|
// }
|
|
// if err != nil {
|
|
// fmt.Printf("[ERROR] Attempt %d failed: %v\n", i+1, err)
|
|
// }
|
|
// if resp != nil && resp.StatusCode != http.StatusOK {
|
|
// fmt.Printf("[ERROR] Server returned status: %d\n", resp.StatusCode)
|
|
// }
|
|
// if i < 2 {
|
|
// time.Sleep(2 * time.Second) // Delay between retries
|
|
// }
|
|
// }
|
|
// if err != nil {
|
|
// return fmt.Errorf("[ERROR] Failed to download %s after 3 retries: %w", pkg.Name, err)
|
|
// }
|
|
// defer resp.Body.Close()
|
|
|
|
// // Check content type
|
|
// contentType := resp.Header.Get("Content-Type")
|
|
// if contentType != "application/gzip" && contentType != "application/x-tar" {
|
|
// return fmt.Errorf("[ERROR] Invalid content type: %s. Expected a .tar.gz file.", contentType)
|
|
// }
|
|
|
|
// // Generate the filename using the desired format
|
|
// filename := fmt.Sprintf("%s@%s@%s@%s@%s@%s.tar.gz",
|
|
// pkg.Name, // Name of the package
|
|
// pkg.Arch, // Architecture (e.g., amd64)
|
|
// pkg.OS, // Operating System (e.g., windows, linux)
|
|
// pkgType, // Type of the package (e.g., nightly, stable)
|
|
// release, // Release (e.g., nightly, stable)
|
|
// version, // Version of the package
|
|
// )
|
|
|
|
// // Construct the full file path
|
|
// filePath := filepath.Join(destDir, filename)
|
|
// fmt.Printf("[INFO] Saving package to: %s\n", filePath)
|
|
|
|
// // Create the destination directory if it doesn't exist
|
|
// err = os.MkdirAll(destDir, 0755)
|
|
// if err != nil {
|
|
// return fmt.Errorf("[ERROR] Failed to create destination directory %s: %w", destDir, err)
|
|
// }
|
|
|
|
// // Create the file to save the download
|
|
// out, err := os.Create(filePath)
|
|
// if err != nil {
|
|
// return fmt.Errorf("[ERROR] Failed to create file %s: %w", filePath, err)
|
|
// }
|
|
// defer out.Close()
|
|
|
|
// // Track download progress
|
|
// totalSize := resp.ContentLength
|
|
// var downloaded int64
|
|
// buf := make([]byte, 1024)
|
|
// for {
|
|
// n, err := resp.Body.Read(buf)
|
|
// if n > 0 {
|
|
// downloaded += int64(n)
|
|
// percentage := int(float64(downloaded) / float64(totalSize) * 100)
|
|
// UpdateProgress(percentage, fmt.Sprintf("Downloading %s", pkg.Name))
|
|
// if _, err := out.Write(buf[:n]); err != nil {
|
|
// return fmt.Errorf("[ERROR] Failed to write to file %s: %w", filePath, err)
|
|
// }
|
|
// }
|
|
// if err == io.EOF {
|
|
// break
|
|
// }
|
|
// if err != nil {
|
|
// return fmt.Errorf("[ERROR] Error reading response body: %w", err)
|
|
// }
|
|
// }
|
|
|
|
// UpdateProgress(100, fmt.Sprintf("%s downloaded", pkg.Name))
|
|
// fmt.Printf("[INFO] Package %s downloaded successfully to: %s\n", pkg.Name, filePath)
|
|
|
|
// // Validate that the file is a valid gzip or tar file
|
|
// if _, err := os.Stat(filePath); err != nil {
|
|
// return fmt.Errorf("[ERROR] Downloaded file does not exist: %w", err)
|
|
// }
|
|
|
|
// return nil
|
|
// }
|
|
|
|
// DownloadPackageFromAppIndex selects and downloads the correct package from the APPINDEX.
|
|
func DownloadPackageFromAppIndex(appIndexPath string, packageName string, release string, pkgType string, destDir string) error {
|
|
// Parse the APPINDEX
|
|
entries, err := ParseAppIndex(appIndexPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse APPINDEX: %w", err)
|
|
}
|
|
|
|
// Find the right entry
|
|
var selected *AppIndexEntry
|
|
for _, e := range entries {
|
|
if e.Name == packageName &&
|
|
e.Release == release &&
|
|
e.Type == pkgType &&
|
|
e.OS == runtime.GOOS &&
|
|
e.Arch == runtime.GOARCH {
|
|
selected = &e
|
|
break
|
|
}
|
|
}
|
|
|
|
// Handle no matching entry
|
|
if selected == nil {
|
|
return fmt.Errorf("package not found in APPINDEX: %s (release: %s, type: %s, os: %s, arch: %s)", packageName, release, pkgType, runtime.GOOS, runtime.GOARCH)
|
|
}
|
|
|
|
// Check if the package is already installed and up-to-date
|
|
installDir, err := GetDefaultInstallDir()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get install directory: %w", err)
|
|
}
|
|
needsUpdate, err := IsUpdateNeeded(installDir, packageName, release, selected.Version, selected.Arch, selected.OS)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check update status: %w", err)
|
|
}
|
|
if !needsUpdate {
|
|
UpdateProgress(0, "Already up-to-date, skipping download.")
|
|
return nil // Skip download
|
|
}
|
|
|
|
// Download the package
|
|
UpdateProgress(0, fmt.Sprintf("Downloading %s %s (%s)...", packageName, selected.Version, selected.Type))
|
|
resp, err := http.Get(selected.DownloadURL)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to download package: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Save the downloaded file
|
|
downloadedFileName := filepath.Base(selected.DownloadURL)
|
|
downloadedFilePath := filepath.Join(destDir, downloadedFileName)
|
|
|
|
out, err := os.OpenFile(downloadedFilePath, os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create output file: %w", err)
|
|
}
|
|
defer out.Close()
|
|
|
|
totalSize := resp.ContentLength
|
|
var downloaded int64
|
|
buf := make([]byte, 32*1024) // Use a larger buffer for efficiency
|
|
|
|
for {
|
|
n, errRead := resp.Body.Read(buf)
|
|
if n > 0 {
|
|
downloaded += int64(n)
|
|
percentage := int(float64(downloaded) / float64(totalSize) * 100)
|
|
UpdateProgress(percentage, fmt.Sprintf("Downloading %s %s (%s)...", packageName, selected.Version, selected.Type))
|
|
if _, errWrite := out.Write(buf[:n]); errWrite != nil {
|
|
return fmt.Errorf("failed to write to output file: %w", errWrite)
|
|
}
|
|
}
|
|
if errRead == io.EOF {
|
|
break
|
|
}
|
|
if errRead != nil {
|
|
return fmt.Errorf("error while reading response: %w", errRead)
|
|
}
|
|
}
|
|
|
|
// Ensure the file handle is closed before renaming
|
|
out.Close()
|
|
|
|
// Construct the expected filename
|
|
expectedFileName := fmt.Sprintf("%s@%s@%s@%s@%s@%s.tar.gz",
|
|
packageName, selected.Arch, selected.OS, selected.Type, selected.Release, selected.Version)
|
|
|
|
expectedFilePath := filepath.Join(destDir, expectedFileName)
|
|
|
|
// I dont know why is this happening, I dont want to know but sometimes some process is helding up the donwloaded files so thats why it retries here
|
|
maxRetries := 5
|
|
for i := 0; i < maxRetries; i++ {
|
|
err = os.Rename(downloadedFilePath, expectedFilePath)
|
|
if err == nil {
|
|
break
|
|
}
|
|
|
|
// Check if file is in use
|
|
f, checkErr := os.Open(downloadedFilePath)
|
|
if checkErr != nil {
|
|
return fmt.Errorf("file is locked by another process: %w", checkErr)
|
|
}
|
|
f.Close()
|
|
|
|
if i < maxRetries-1 {
|
|
time.Sleep(500 * time.Millisecond) // Wait before retrying
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to rename downloaded file after retries: %w", err)
|
|
}
|
|
|
|
UpdateProgress(100, fmt.Sprintf("Downloaded %s %s (%s).", packageName, selected.Version, selected.Type))
|
|
return nil
|
|
}
|