214 lines
5.8 KiB
Go
214 lines
5.8 KiB
Go
package spm
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gopkg.in/ini.v1"
|
|
)
|
|
|
|
// getInstalledPackagesPath determines the path for the installed.ini file.
|
|
func getInstalledPackagesPath(installDir string) string {
|
|
spmDir := filepath.Join(installDir, "spm")
|
|
_ = os.MkdirAll(spmDir, 0755)
|
|
return filepath.Join(spmDir, "installed.ini")
|
|
}
|
|
|
|
// loadInstalledPackages reads the installed.ini file and parses it.
|
|
func loadInstalledPackages(installDir string) ([]AppIndexEntry, error) {
|
|
installedFile := getInstalledPackagesPath(installDir)
|
|
|
|
cfg, err := ini.LoadSources(ini.LoadOptions{AllowNonUniqueSections: true}, installedFile)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return []AppIndexEntry{}, nil // Return empty slice if file doesn't exist
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
var installed []AppIndexEntry
|
|
for _, section := range cfg.Sections() {
|
|
if section.Name() == "DEFAULT" {
|
|
continue
|
|
}
|
|
|
|
// Read fields
|
|
name := section.Key("P").String()
|
|
version := section.Key("V").String()
|
|
release := section.Key("R").String()
|
|
typeVal := section.Key("o").String()
|
|
arch := section.Key("A").MustString(runtime.GOARCH) // Default to system arch
|
|
osName := section.Key("p").MustString(runtime.GOOS) // Default to system OS
|
|
|
|
// Append to slice
|
|
installed = append(installed, AppIndexEntry{
|
|
Name: name,
|
|
Version: version,
|
|
Release: release,
|
|
Type: typeVal,
|
|
Arch: arch,
|
|
OS: osName,
|
|
})
|
|
}
|
|
|
|
return installed, nil
|
|
}
|
|
|
|
// saveInstalledPackages writes the installed packages into installed.ini.
|
|
func saveInstalledPackages(installDir string, pkgs []AppIndexEntry) error {
|
|
installedFile := getInstalledPackagesPath(installDir)
|
|
|
|
cfg := ini.Empty()
|
|
for _, pkg := range pkgs {
|
|
section, err := cfg.NewSection(pkg.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
section.Key("P").SetValue(pkg.Name)
|
|
section.Key("V").SetValue(pkg.Version)
|
|
section.Key("R").SetValue(pkg.Release)
|
|
section.Key("o").SetValue(pkg.Type)
|
|
|
|
// Save arch if different from current runtime architecture
|
|
if pkg.Arch != runtime.GOARCH {
|
|
section.Key("A").SetValue(pkg.Arch)
|
|
}
|
|
|
|
// Save OS if different from current runtime OS
|
|
if pkg.OS != runtime.GOOS {
|
|
section.Key("p").SetValue(pkg.OS)
|
|
}
|
|
}
|
|
|
|
return cfg.SaveTo(installedFile)
|
|
}
|
|
|
|
// isNewerVersion compares two version strings and determines if `newVer` is newer than `oldVer`.
|
|
func isNewerVersion(oldVer, newVer string) bool {
|
|
// Handle date-based versions (e.g., nightly: YYYY.MM.DD)
|
|
if isDateVersion(oldVer) && isDateVersion(newVer) {
|
|
return strings.Compare(newVer, oldVer) > 0
|
|
}
|
|
|
|
// Handle semantic versions (e.g., stable: v1.0.1)
|
|
if isSemVer(oldVer) && isSemVer(newVer) {
|
|
return compareSemVer(oldVer, newVer) > 0
|
|
}
|
|
|
|
// Fallback to lexicographical comparison for unknown formats
|
|
return strings.Compare(newVer, oldVer) > 0
|
|
}
|
|
|
|
// isDateVersion checks if a version string is in the format YYYY.MM.DD.
|
|
func isDateVersion(version string) bool {
|
|
parts := strings.Split(version, ".")
|
|
if len(parts) != 3 {
|
|
return false
|
|
}
|
|
for _, part := range parts {
|
|
if _, err := strconv.Atoi(part); err != nil {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// isSemVer checks if a version string is in the format vMAJOR.MINOR.PATCH.
|
|
func isSemVer(version string) bool {
|
|
if !strings.HasPrefix(version, "v") {
|
|
return false
|
|
}
|
|
parts := strings.Split(strings.TrimPrefix(version, "v"), ".")
|
|
if len(parts) != 3 {
|
|
return false
|
|
}
|
|
for _, part := range parts {
|
|
if _, err := strconv.Atoi(part); err != nil {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// compareSemVer compares two semantic version strings (vMAJOR.MINOR.PATCH).
|
|
// Returns:
|
|
// - 1 if `v2` is newer than `v1`
|
|
// - 0 if `v1` and `v2` are equal
|
|
// - -1 if `v1` is newer than `v2`
|
|
func compareSemVer(v1, v2 string) int {
|
|
parts1 := strings.Split(strings.TrimPrefix(v1, "v"), ".")
|
|
parts2 := strings.Split(strings.TrimPrefix(v2, "v"), ".")
|
|
|
|
for i := 0; i < len(parts1); i++ {
|
|
num1, _ := strconv.Atoi(parts1[i])
|
|
num2, _ := strconv.Atoi(parts2[i])
|
|
if num1 > num2 {
|
|
return 1
|
|
}
|
|
if num1 < num2 {
|
|
return -1
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// IsUpdateNeeded checks if the given package version is newer than what's installed.
|
|
func IsUpdateNeeded(installDir, name, release, newVersion, arch, osName string) (bool, error) {
|
|
installed, err := loadInstalledPackages(installDir)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
for _, pkg := range installed {
|
|
if pkg.Name == name && pkg.Release == release && pkg.Arch == arch && pkg.OS == osName {
|
|
fmt.Printf("Found installed package: %v\n", pkg)
|
|
if isNewerVersion(pkg.Version, newVersion) {
|
|
fmt.Println("Update is needed")
|
|
return true, nil
|
|
}
|
|
fmt.Println("No update needed")
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
fmt.Println("Package not installed, update needed")
|
|
return true, nil
|
|
}
|
|
|
|
// UpdateInstalledPackage writes/updates the new package version in installed.ini.
|
|
func UpdateInstalledPackage(installDir string, pkg AppIndexEntry) error {
|
|
installed, err := loadInstalledPackages(installDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
updated := false
|
|
for i, p := range installed {
|
|
if p.Name == pkg.Name && p.Release == pkg.Release && p.Arch == pkg.Arch && p.OS == pkg.OS {
|
|
installed[i].Version = pkg.Version
|
|
updated = true
|
|
break
|
|
}
|
|
}
|
|
if !updated {
|
|
installed = append(installed, pkg)
|
|
}
|
|
|
|
return saveInstalledPackages(installDir, installed)
|
|
}
|
|
|
|
// // DebugInstalled prints installed packages for debugging.
|
|
// func DebugInstalled(installDir string) {
|
|
// pkgs, err := loadInstalledPackages(installDir)
|
|
// if err != nil {
|
|
// fmt.Println("DebugInstalled error:", err)
|
|
// return
|
|
// }
|
|
// for _, p := range pkgs {
|
|
// fmt.Printf("Installed: %s v%s [%s] (arch=%s, os=%s)\n", p.Name, p.Version, p.Release, p.Arch, p.OS)
|
|
// }
|
|
// }
|