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 }