package spm import ( "fmt" "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 // DownloadUpdates 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 InstallUpdates(). func DownloadUpdates() error { // 1) Download the APPINDEX file to a temporary location err := UpdateIndex() if err != nil { return fmt.Errorf("[ERROR] Failed to download APPINDEX: %w", err) } fmt.Println("[INFO] APPINDEX downloaded successfully") // 2) Parse the APPINDEX file entries, err := GetIndex() 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(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 InstallUpdates 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] DownloadUpdates completed successfully") return nil } // InstallUpdates installs any packages that were downloaded and decompressed by DownloadUpdates. // It moves files from their temp directories to the final location and updates installed.ini. func InstallUpdates() 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 DownloadSpecified(specs []AppIndexEntry) error { // 1) Download the APPINDEX file to a temporary location if err := UpdateIndex(); err != nil { return fmt.Errorf("[ERROR] Failed to download APPINDEX: %w", err) } fmt.Println("[INFO] APPINDEX downloaded successfully") // 2) Parse the APPINDEX file entries, err := GetIndex() 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) 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( 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) // Add to pendingUpdates for InstallUpdates fmt.Printf("[INFO] Adding '%s' to pending updates\n", matchingEntry.Name) pendingUpdates = append(pendingUpdates, *matchingEntry) } fmt.Println("[INFO] AutoDownloadSpecifiedPackages completed successfully") return nil }