261 lines
9.4 KiB
Go
261 lines
9.4 KiB
Go
package spm
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// pendingUpdates holds info about packages that have been downloaded/decompressed
|
|
// but not yet moved to the final install location, cuz Windows has this stupid file locking mechanism
|
|
var pendingUpdates []AppIndexEntry
|
|
|
|
// AutoDownloadUpdates downloads the APPINDEX file, parses it, compares against
|
|
// currently installed packages, and if it finds a newer version, downloads
|
|
// and decompresses it into a temporary folder. The result is stored in pendingUpdates, so it can be used by AutoInstallUpdates().
|
|
func AutoDownloadUpdates() error {
|
|
// 1) Download the APPINDEX file to a temporary location
|
|
appIndexPath := filepath.Join(os.TempDir(), "APPINDEX")
|
|
fmt.Println("[INFO] Starting APPINDEX download to:", appIndexPath)
|
|
err := DownloadAppIndex(appIndexPath)
|
|
if err != nil {
|
|
return fmt.Errorf("[ERROR] Failed to download APPINDEX: %w", err)
|
|
}
|
|
fmt.Println("[INFO] APPINDEX downloaded successfully")
|
|
|
|
// 2) Parse the APPINDEX file
|
|
fmt.Println("[INFO] Parsing APPINDEX file:", appIndexPath)
|
|
entries, err := ParseAppIndex(appIndexPath)
|
|
if err != nil {
|
|
return fmt.Errorf("[ERROR] Failed to parse APPINDEX: %w", err)
|
|
}
|
|
fmt.Printf("[INFO] Parsed APPINDEX successfully, found %d entries\n", len(entries))
|
|
|
|
// 3) Load installed packages
|
|
fmt.Println("[INFO] Loading installed packages")
|
|
installDir, err := GetInstallDir()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println("[INFO] Install directory:", installDir)
|
|
|
|
installedPkgs, err := loadInstalledPackages(installDir)
|
|
if err != nil {
|
|
return fmt.Errorf("[ERROR] Failed to load installed packages: %w", err)
|
|
}
|
|
fmt.Printf("[INFO] Loaded %d installed packages\n", len(installedPkgs))
|
|
|
|
// 4) Process entries for installed packages only
|
|
for _, installed := range installedPkgs {
|
|
fmt.Printf("[INFO] Checking updates for installed package: %+v\n", installed)
|
|
|
|
// Filter APPINDEX entries that match the installed package's attributes
|
|
var matchingEntry *AppIndexEntry
|
|
for _, entry := range entries {
|
|
if entry.Name == installed.Name &&
|
|
entry.Release == installed.Release &&
|
|
entry.Type == installed.Type &&
|
|
entry.OS == installed.OS &&
|
|
entry.Arch == installed.Arch {
|
|
matchingEntry = &entry
|
|
break
|
|
}
|
|
}
|
|
|
|
if matchingEntry == nil {
|
|
fmt.Printf("[WARN] No matching APPINDEX entry found for installed package: %s (%s)\n", installed.Name, installed.Release)
|
|
continue
|
|
}
|
|
|
|
fmt.Printf("[INFO] Found matching APPINDEX entry: %+v\n", *matchingEntry)
|
|
|
|
// Determine if an update is needed
|
|
updateNeeded, err := IsUpdateNeeded(installDir, matchingEntry.Name, matchingEntry.Release, matchingEntry.Version, matchingEntry.Arch, matchingEntry.OS)
|
|
if err != nil {
|
|
return fmt.Errorf("[ERROR] Failed to check if update is needed for %s: %w", matchingEntry.Name, err)
|
|
}
|
|
|
|
if !updateNeeded {
|
|
fmt.Printf("[INFO] No update needed for package '%s'\n", matchingEntry.Name)
|
|
continue
|
|
}
|
|
|
|
// 5) Download the package into a temporary download folder
|
|
downloadDir := GetTempDir()
|
|
fmt.Printf("[INFO] Downloading package '%s' to temporary folder: %s\n", matchingEntry.Name, downloadDir)
|
|
|
|
err = DownloadPackageFromAppIndex(appIndexPath, matchingEntry.Name, matchingEntry.Release, matchingEntry.Type, downloadDir)
|
|
if err != nil {
|
|
return fmt.Errorf("[ERROR] Failed to download package '%s': %w", matchingEntry.Name, err)
|
|
}
|
|
|
|
fmt.Printf("[INFO] Package '%s' downloaded successfully to: %s\n", matchingEntry.Name, downloadDir)
|
|
|
|
// 6) Decompress the package into another temp folder
|
|
fmt.Printf("[INFO] Decompressing package '%s'\n", matchingEntry.Name)
|
|
tempDir, err := DecompressPackage(downloadDir, matchingEntry.Name, matchingEntry.Arch, matchingEntry.OS, matchingEntry.Type, matchingEntry.Release, matchingEntry.Version)
|
|
if err != nil {
|
|
return fmt.Errorf("[ERROR] Failed to decompress package '%s': %w", matchingEntry.Name, err)
|
|
}
|
|
fmt.Printf("[INFO] Package '%s' decompressed successfully to: %s\n", matchingEntry.Name, tempDir)
|
|
|
|
// 7) Store in pendingUpdates so that AutoInstallUpdates can finish the job
|
|
fmt.Printf("[INFO] Adding '%s' to pending updates\n", matchingEntry.Name)
|
|
pendingUpdates = append(pendingUpdates, AppIndexEntry{
|
|
Name: matchingEntry.Name,
|
|
Version: matchingEntry.Version,
|
|
Release: matchingEntry.Release,
|
|
Arch: matchingEntry.Arch,
|
|
OS: matchingEntry.OS,
|
|
Type: matchingEntry.Type,
|
|
})
|
|
}
|
|
|
|
fmt.Println("[INFO] AutoDownloadUpdates completed successfully")
|
|
return nil
|
|
}
|
|
|
|
// AutoInstallUpdates installs any packages that were downloaded and decompressed by AutoDownloadUpdates.
|
|
// It moves files from their temp directories to the final location and updates installed.ini.
|
|
func AutoInstallUpdates() error {
|
|
installDir, err := GetInstallDir()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, entry := range pendingUpdates {
|
|
// 1) Construct the same .tar.gz name we used when decompressing
|
|
fileName := fmt.Sprintf("%s@%s@%s@%s@%s@%s",
|
|
entry.Name, // no 'packageName'
|
|
entry.Arch, // matches 'arch'
|
|
entry.OS, // matches 'os'
|
|
entry.Type, // matches 'type'
|
|
entry.Release, // matches 'release'
|
|
entry.Version, // matches 'version'
|
|
)
|
|
|
|
// 3) Combine with your global temp dir
|
|
tempBase := GetTempDir() // e.g. C:\Users\YourUser\AppData\Local\Temp\spm_temp_164326
|
|
decompressedDir := filepath.Join(tempBase, fileName)
|
|
|
|
// 4) Move files from that decompressedDir
|
|
fmt.Printf("[INFO] Installing %s from %s\n", entry.Name, decompressedDir)
|
|
err := MoveFilesToInstallDir(decompressedDir, installDir, entry.Type)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to move files for %s: %w", entry.Name, err)
|
|
}
|
|
|
|
// 5) Finalize
|
|
err = FinalizeInstall(entry.Name, entry.Release, entry.Version, entry.Arch, entry.OS)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to finalize install for %s: %w", entry.Name, err)
|
|
}
|
|
}
|
|
|
|
pendingUpdates = nil
|
|
return nil
|
|
}
|
|
|
|
func AutoDownloadSpecified(specs []AppIndexEntry) error {
|
|
// 1) Download the APPINDEX file to a temporary location
|
|
appIndexPath := filepath.Join(os.TempDir(), "APPINDEX")
|
|
fmt.Println("[INFO] Starting APPINDEX download to:", appIndexPath)
|
|
if err := DownloadAppIndex(appIndexPath); err != nil {
|
|
return fmt.Errorf("[ERROR] Failed to download APPINDEX: %w", err)
|
|
}
|
|
fmt.Println("[INFO] APPINDEX downloaded successfully")
|
|
|
|
// 2) Parse the APPINDEX file
|
|
fmt.Println("[INFO] Parsing APPINDEX file:", appIndexPath)
|
|
entries, err := ParseAppIndex(appIndexPath)
|
|
if err != nil {
|
|
return fmt.Errorf("[ERROR] Failed to parse APPINDEX: %w", err)
|
|
}
|
|
fmt.Printf("[INFO] Parsed APPINDEX successfully, found %d entries\n", len(entries))
|
|
|
|
// 3) Get install directory to check for updates
|
|
installDir, err := GetInstallDir()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println("[INFO] Install directory:", installDir)
|
|
|
|
// 4) For each item in the passed specs, attempt to download if update is needed
|
|
for _, spec := range specs {
|
|
fmt.Printf("[INFO] Checking requested package: %+v\n", spec)
|
|
|
|
// Find matching entry from the parsed APPINDEX
|
|
var matchingEntry *AppIndexEntry
|
|
for _, e := range entries {
|
|
if e.Name == spec.Name &&
|
|
e.Release == spec.Release &&
|
|
e.Type == spec.Type &&
|
|
e.OS == spec.OS &&
|
|
e.Arch == spec.Arch {
|
|
matchingEntry = &e
|
|
break
|
|
}
|
|
}
|
|
|
|
if matchingEntry == nil {
|
|
fmt.Printf("[WARN] No matching APPINDEX entry found for package: %s (%s)\n", spec.Name, spec.Release)
|
|
continue
|
|
}
|
|
fmt.Printf("[INFO] Found matching APPINDEX entry: %+v\n", *matchingEntry)
|
|
|
|
// // Check if an update is needed
|
|
// updateNeeded, err := IsUpdateNeeded(
|
|
// installDir,
|
|
// matchingEntry.Name,
|
|
// matchingEntry.Release,
|
|
// matchingEntry.Version,
|
|
// matchingEntry.Arch,
|
|
// matchingEntry.OS,
|
|
// )
|
|
// if err != nil {
|
|
// return fmt.Errorf("[ERROR] Failed to check if update is needed for %s: %w", matchingEntry.Name, err)
|
|
// }
|
|
|
|
// if !updateNeeded {
|
|
// fmt.Printf("[INFO] No update needed for package '%s'\n", matchingEntry.Name)
|
|
// continue
|
|
// }
|
|
|
|
// 5) Download the package
|
|
downloadDir := GetTempDir()
|
|
fmt.Printf("[INFO] Downloading package '%s' to temporary folder: %s\n", matchingEntry.Name, downloadDir)
|
|
if err := DownloadPackageFromAppIndex(
|
|
appIndexPath,
|
|
matchingEntry.Name,
|
|
matchingEntry.Release,
|
|
matchingEntry.Type,
|
|
downloadDir,
|
|
); err != nil {
|
|
return fmt.Errorf("[ERROR] Failed to download package '%s': %w", matchingEntry.Name, err)
|
|
}
|
|
fmt.Printf("[INFO] Package '%s' downloaded successfully to: %s\n", matchingEntry.Name, downloadDir)
|
|
|
|
// 6) Decompress the package
|
|
fmt.Printf("[INFO] Decompressing package '%s'\n", matchingEntry.Name)
|
|
tempDir, err := DecompressPackage(
|
|
downloadDir,
|
|
matchingEntry.Name,
|
|
matchingEntry.Arch,
|
|
matchingEntry.OS,
|
|
matchingEntry.Type,
|
|
matchingEntry.Release,
|
|
matchingEntry.Version,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("[ERROR] Failed to decompress package '%s': %w", matchingEntry.Name, err)
|
|
}
|
|
fmt.Printf("[INFO] Package '%s' decompressed successfully to: %s\n", matchingEntry.Name, tempDir)
|
|
|
|
// 7) Store in pendingUpdates for AutoInstallUpdates
|
|
fmt.Printf("[INFO] Adding '%s' to pending updates\n", matchingEntry.Name)
|
|
pendingUpdates = append(pendingUpdates, *matchingEntry)
|
|
}
|
|
|
|
fmt.Println("[INFO] AutoDownloadSpecifiedPackages completed successfully")
|
|
return nil
|
|
}
|