Compare commits
No commits in common. "main" and "v0.0.3" have entirely different histories.
12 changed files with 103 additions and 363 deletions
13
README.md
13
README.md
|
@ -47,9 +47,9 @@ func main() {
|
|||
}
|
||||
|
||||
// -- Download --
|
||||
// spm.DownloadSpecified(specs) downloads specified packages to temp dir and decompresses them, making them ready for install by running "spm.InstallUpdates()".
|
||||
// spm.AutoDownloadSpecified(specs) downloads specified packages to temp dir and decompresses them, making them ready for install by running "spm.AutoInstallUpdates()".
|
||||
fmt.Println("Starting download and decompression...")
|
||||
if err := spm.DownloadSpecified(specs); err != nil {
|
||||
if err := spm.AutoDownloadSpecified(specs); err != nil {
|
||||
fmt.Println("Error downloading packages:", err)
|
||||
return
|
||||
}
|
||||
|
@ -57,15 +57,15 @@ func main() {
|
|||
fmt.Println("Download complete. Proceeding with installation...")
|
||||
|
||||
// -- Install --
|
||||
// Install and Download are separate as you cannot replace running binaries on Windows. So the final move to the correct folder is done by "spm.InstallUpdates()".
|
||||
if err := spm.InstallUpdates(); err != nil {
|
||||
// Install and Download are separate as you cannot replace running binaries on Windows. So the final move to the correct folder is done by "spm.AutoInstallUpdates()".
|
||||
if err := spm.AutoInstallUpdates(); err != nil {
|
||||
fmt.Println("Error during installation:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// -- Register --
|
||||
// spm.RegisterApp() is primarily used to modify the Windows registry so it recognizes Spitfire Browser as an installed program.
|
||||
// You shouldn’t need to run it more than once during installation. Also this function requires administrative privileges on Windows to work correctly.
|
||||
// spm.RegisterApp() is primarily used to modify the Windows registry so it recognizes Spitfire Browser as an installed program.
|
||||
// You shouldn’t need to run it more than once during installation. Also this function requires administrative privileges on Windows to work correctly.
|
||||
if err := spm.RegisterApp(); err != nil {
|
||||
fmt.Println("Error registering app:", err)
|
||||
return
|
||||
|
@ -78,6 +78,7 @@ func main() {
|
|||
spm.Run()
|
||||
}
|
||||
```
|
||||
*Functions, and specifically their names, are subject to change as I really don't like "AutoDownloadSpecified," but I don't want to make it "DownloadSpecified" yet, as there are still many functions in SPM used for manual downloads, installs, etc.*
|
||||
|
||||
<p align="center" style="font-size: 32px;">
|
||||
<strong>License</strong>
|
||||
|
|
277
appindex.go
277
appindex.go
|
@ -6,46 +6,14 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// AppIndexEntry represents a single entry in an app index.
|
||||
type AppIndexEntry struct {
|
||||
Name string
|
||||
Version string
|
||||
Release string // "nightly" / "stable" / etc.
|
||||
Arch string // e.g. "amd64", "386"
|
||||
OS string // e.g. "windows", "linux"
|
||||
Type string // "browser", "addon", "theme", etc.
|
||||
DownloadURL string
|
||||
}
|
||||
const appIndexURL = "https://downloads.sourceforge.net/project/spitfire-browser/APPINDEX"
|
||||
|
||||
// RemoteIndex represents a remote APPINDEX repository.
|
||||
type RemoteIndex struct {
|
||||
Name string `json:"name"`
|
||||
Link string `json:"link"`
|
||||
}
|
||||
|
||||
var (
|
||||
// defaultRemoteIndexes holds the default remote index.
|
||||
defaultRemoteIndexes = []RemoteIndex{
|
||||
{
|
||||
Name: "default",
|
||||
Link: "https://downloads.sourceforge.net/project/spitfire-browser/APPINDEX",
|
||||
},
|
||||
}
|
||||
// remoteIndexes holds the current remote indexes in use.
|
||||
remoteIndexes = defaultRemoteIndexes
|
||||
)
|
||||
|
||||
// downloadAppIndex downloads an APPINDEX from the given URL and writes it to dest.
|
||||
func downloadAppIndex(url, dest string) error {
|
||||
func DownloadAppIndex(dest string) error {
|
||||
UpdateProgress(0, "Downloading APPINDEX")
|
||||
resp, err := http.Get(url)
|
||||
resp, err := http.Get(appIndexURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -84,10 +52,25 @@ func downloadAppIndex(url, dest string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// parseAppIndexFromReader parses an APPINDEX from any io.Reader.
|
||||
func parseAppIndexFromReader(r io.Reader) ([]AppIndexEntry, error) {
|
||||
type AppIndexEntry struct {
|
||||
Name string
|
||||
Version string
|
||||
Release string // "nightly" / "stable" / etc.
|
||||
Arch string // e.g. "amd64", "386"
|
||||
OS string // e.g. "windows", "linux"
|
||||
Type string // "browser", "addon", "theme", etc.
|
||||
DownloadURL string
|
||||
}
|
||||
|
||||
func ParseAppIndex(filePath string) ([]AppIndexEntry, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var entries []AppIndexEntry
|
||||
scanner := bufio.NewScanner(r)
|
||||
scanner := bufio.NewScanner(file)
|
||||
entry := AppIndexEntry{}
|
||||
|
||||
for scanner.Scan() {
|
||||
|
@ -129,222 +112,12 @@ func parseAppIndexFromReader(r io.Reader) ([]AppIndexEntry, error) {
|
|||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
return entries, scanner.Err()
|
||||
}
|
||||
|
||||
// parseAppIndex reads the APPINDEX file at filePath and parses its contents.
|
||||
func parseAppIndex(filePath string) ([]AppIndexEntry, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
entries, err := parseAppIndexFromReader(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Printf("[INFO] Total parsed entries from %s: %d\n", filePath, len(entries))
|
||||
// Log all parsed entries
|
||||
fmt.Printf("[INFO] Total parsed entries: %d\n", len(entries))
|
||||
for _, e := range entries {
|
||||
fmt.Printf(" - Name: %s, Release: %s, Type: %s, OS: %s, Arch: %s, Version: %s, URL: %s\n",
|
||||
e.Name, e.Release, e.Type, e.OS, e.Arch, e.Version, e.DownloadURL)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// saveIndex saves the current list of remote indexes to an INI file located in
|
||||
// the spm directory under installDir, but only if it's different from the default.
|
||||
func saveIndex() error {
|
||||
// Only save if remoteIndexes differs from defaultRemoteIndexes.
|
||||
if len(remoteIndexes) == len(defaultRemoteIndexes) {
|
||||
same := true
|
||||
for i, ri := range remoteIndexes {
|
||||
if ri != defaultRemoteIndexes[i] {
|
||||
same = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if same {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
installDir, err := GetInstallDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
spmDir := filepath.Join(installDir, "spm")
|
||||
if err := os.MkdirAll(spmDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
filePath := filepath.Join(spmDir, "sources.ini")
|
||||
|
||||
cfg := ini.Empty()
|
||||
sec, err := cfg.NewSection("RemoteIndexes")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Save each remote index as a key/value pair.
|
||||
for _, ri := range remoteIndexes {
|
||||
if _, err := sec.NewKey(ri.Name, ri.Link); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return cfg.SaveTo(filePath)
|
||||
}
|
||||
|
||||
// loadIndex loads the list of remote indexes from an INI file located in
|
||||
// the spm directory under installDir. If the file is missing, it sets remoteIndexes to the default.
|
||||
func loadIndex() error {
|
||||
installDir, err := GetInstallDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
spmDir := filepath.Join(installDir, "spm")
|
||||
filePath := filepath.Join(spmDir, "sources.ini")
|
||||
cfg, err := ini.Load(filePath)
|
||||
if err != nil {
|
||||
// If file is missing or can't be loaded, use the default.
|
||||
remoteIndexes = defaultRemoteIndexes
|
||||
return nil
|
||||
}
|
||||
sec := cfg.Section("RemoteIndexes")
|
||||
var loaded []RemoteIndex
|
||||
for _, key := range sec.Keys() {
|
||||
loaded = append(loaded, RemoteIndex{
|
||||
Name: key.Name(),
|
||||
Link: key.Value(),
|
||||
})
|
||||
}
|
||||
remoteIndexes = loaded
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateIndex downloads fresh APPINDEX files from all remote sources and saves them
|
||||
// into the temp directory. If the app is registered, it loads the remote indexes
|
||||
// from the INI file (or uses the default if not available) and saves them after updating.
|
||||
func UpdateIndex() error {
|
||||
tempDir := GetTempDir()
|
||||
var sources []RemoteIndex
|
||||
if IsRegistered() {
|
||||
// Try to load persisted remote indexes.
|
||||
if err := loadIndex(); err != nil {
|
||||
// If loading fails, fall back to the default remote indexes.
|
||||
sources = defaultRemoteIndexes
|
||||
} else {
|
||||
sources = remoteIndexes
|
||||
}
|
||||
} else {
|
||||
// Not registered: use default remote indexes.
|
||||
sources = defaultRemoteIndexes
|
||||
}
|
||||
|
||||
// Download each APPINDEX file.
|
||||
for _, ri := range sources {
|
||||
localPath := filepath.Join(tempDir, fmt.Sprintf("appindex_%s.txt", ri.Name))
|
||||
if err := downloadAppIndex(ri.Link, localPath); err != nil {
|
||||
return fmt.Errorf("[WARN] AppIndex: failed downloading %s: %v", ri.Link, err)
|
||||
}
|
||||
}
|
||||
|
||||
// If registered, save the current remote indexes.
|
||||
if IsRegistered() {
|
||||
if err := saveIndex(); err != nil {
|
||||
return fmt.Errorf("[WARN] AppIndex: failed saving indexes: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetIndex parses APPINDEX data from local files in the temp directory.
|
||||
// If a file is missing, it downloads the corresponding APPINDEX first.
|
||||
// If the app is registered, it loads remote indexes from the INI file.
|
||||
// Otherwise, it uses the default remote index.
|
||||
func GetIndex() ([]AppIndexEntry, error) {
|
||||
var allEntries []AppIndexEntry
|
||||
tempDir := GetTempDir()
|
||||
var sources []RemoteIndex
|
||||
if IsRegistered() {
|
||||
if err := loadIndex(); err != nil {
|
||||
sources = defaultRemoteIndexes
|
||||
} else {
|
||||
sources = remoteIndexes
|
||||
}
|
||||
} else {
|
||||
sources = defaultRemoteIndexes
|
||||
}
|
||||
|
||||
// For each remote source, ensure the APPINDEX file exists (downloading if needed),
|
||||
// then parse its contents.
|
||||
for _, ri := range sources {
|
||||
localPath := filepath.Join(tempDir, fmt.Sprintf("appindex_%s.txt", ri.Name))
|
||||
if _, err := os.Stat(localPath); os.IsNotExist(err) {
|
||||
if err := downloadAppIndex(ri.Link, localPath); err != nil {
|
||||
return nil, fmt.Errorf("[WARN] AppIndex: failed downloading %s: %v", ri.Link, err)
|
||||
}
|
||||
}
|
||||
entries, err := parseAppIndex(localPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[WARN] AppIndex: failed parsing %s: %v", localPath, err)
|
||||
}
|
||||
allEntries = append(allEntries, entries...)
|
||||
}
|
||||
return allEntries, nil
|
||||
}
|
||||
|
||||
// AddIndex adds a new remote index (name and link) into the list,
|
||||
// sorts the list by name, and if the app is registered, saves the updated list.
|
||||
func AddIndex(name, link string) error {
|
||||
// If registered, load current indexes first.
|
||||
if IsRegistered() {
|
||||
if err := loadIndex(); err != nil {
|
||||
return fmt.Errorf("[WARN] AppIndex: failed loading indexes: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
ri := RemoteIndex{
|
||||
Name: name,
|
||||
Link: link,
|
||||
}
|
||||
remoteIndexes = append(remoteIndexes, ri)
|
||||
sort.Slice(remoteIndexes, func(i, j int) bool {
|
||||
return remoteIndexes[i].Name < remoteIndexes[j].Name
|
||||
})
|
||||
|
||||
// If registered, persist the changes.
|
||||
if IsRegistered() {
|
||||
if err := saveIndex(); err != nil {
|
||||
return fmt.Errorf("[WARN] AppIndex: failed saving indexes: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveIndex removes any remote index with the given name from the list,
|
||||
// and if the app is registered, saves the updated list.
|
||||
func RemoveIndex(name string) error {
|
||||
// If registered, load current indexes first.
|
||||
if IsRegistered() {
|
||||
if err := loadIndex(); err != nil {
|
||||
return fmt.Errorf("[WARN] AppIndex: failed loading indexes: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var updated []RemoteIndex
|
||||
for _, ri := range remoteIndexes {
|
||||
if ri.Name != name {
|
||||
updated = append(updated, ri)
|
||||
}
|
||||
}
|
||||
remoteIndexes = updated
|
||||
|
||||
// If registered, persist the changes.
|
||||
if IsRegistered() {
|
||||
if err := saveIndex(); err != nil {
|
||||
return fmt.Errorf("[WARN] AppIndex: failed saving indexes: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
return entries, scanner.Err()
|
||||
}
|
||||
|
|
69
auto.go
69
auto.go
|
@ -2,6 +2,7 @@ package spm
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
|
@ -9,19 +10,22 @@ import (
|
|||
// 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
|
||||
// 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 InstallUpdates().
|
||||
func DownloadUpdates() error {
|
||||
// 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
|
||||
err := UpdateIndex()
|
||||
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
|
||||
entries, err := GetIndex()
|
||||
fmt.Println("[INFO] Parsing APPINDEX file:", appIndexPath)
|
||||
entries, err := ParseAppIndex(appIndexPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[ERROR] Failed to parse APPINDEX: %w", err)
|
||||
}
|
||||
|
@ -80,7 +84,7 @@ func DownloadUpdates() error {
|
|||
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)
|
||||
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)
|
||||
}
|
||||
|
@ -95,7 +99,7 @@ func DownloadUpdates() error {
|
|||
}
|
||||
fmt.Printf("[INFO] Package '%s' decompressed successfully to: %s\n", matchingEntry.Name, tempDir)
|
||||
|
||||
// 7) Store in pendingUpdates so that InstallUpdates can finish the job
|
||||
// 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,
|
||||
|
@ -107,13 +111,13 @@ func DownloadUpdates() error {
|
|||
})
|
||||
}
|
||||
|
||||
fmt.Println("[INFO] DownloadUpdates completed successfully")
|
||||
fmt.Println("[INFO] AutoDownloadUpdates completed successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// InstallUpdates installs any packages that were downloaded and decompressed by DownloadUpdates.
|
||||
// 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 InstallUpdates() error {
|
||||
func AutoInstallUpdates() error {
|
||||
installDir, err := GetInstallDir()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -142,7 +146,7 @@ func InstallUpdates() error {
|
|||
}
|
||||
|
||||
// 5) Finalize
|
||||
err = finalizeInstall(entry.Name, entry.Release, entry.Version, entry.Arch, entry.OS)
|
||||
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)
|
||||
}
|
||||
|
@ -152,15 +156,18 @@ func InstallUpdates() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func DownloadSpecified(specs []AppIndexEntry) error {
|
||||
func AutoDownloadSpecified(specs []AppIndexEntry) error {
|
||||
// 1) Download the APPINDEX file to a temporary location
|
||||
if err := UpdateIndex(); err != nil {
|
||||
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
|
||||
entries, err := GetIndex()
|
||||
fmt.Println("[INFO] Parsing APPINDEX file:", appIndexPath)
|
||||
entries, err := ParseAppIndex(appIndexPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[ERROR] Failed to parse APPINDEX: %w", err)
|
||||
}
|
||||
|
@ -196,27 +203,29 @@ func DownloadSpecified(specs []AppIndexEntry) error {
|
|||
}
|
||||
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)
|
||||
}
|
||||
// // 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
|
||||
}
|
||||
// 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,
|
||||
|
@ -242,7 +251,7 @@ func DownloadSpecified(specs []AppIndexEntry) error {
|
|||
}
|
||||
fmt.Printf("[INFO] Package '%s' decompressed successfully to: %s\n", matchingEntry.Name, tempDir)
|
||||
|
||||
// Add to pendingUpdates for InstallUpdates
|
||||
// 7) Store in pendingUpdates for AutoInstallUpdates
|
||||
fmt.Printf("[INFO] Adding '%s' to pending updates\n", matchingEntry.Name)
|
||||
pendingUpdates = append(pendingUpdates, *matchingEntry)
|
||||
}
|
||||
|
|
|
@ -108,11 +108,7 @@ func decompressTarGz(srcFile, destDir string, updateProgress func(int, string))
|
|||
// Update progress after extracting each file.
|
||||
if updateProgress != nil {
|
||||
percent := int((progressReader.BytesRead * 100) / totalSize)
|
||||
name := header.Name
|
||||
if len(name) > 50 {
|
||||
name = name[len(name)-50:]
|
||||
}
|
||||
updateProgress(percent, fmt.Sprintf("Extracted: %s", name))
|
||||
updateProgress(percent, fmt.Sprintf("Extracted: %s", header.Name))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
10
download.go
10
download.go
|
@ -11,9 +11,9 @@ import (
|
|||
)
|
||||
|
||||
// DownloadPackageFromAppIndex selects and downloads the correct package from the APPINDEX.
|
||||
func DownloadPackageFromAppIndex(packageName string, release string, pkgType string, destDir string) error {
|
||||
func DownloadPackageFromAppIndex(appIndexPath string, packageName string, release string, pkgType string, destDir string) error {
|
||||
// Parse the APPINDEX
|
||||
entries, err := GetIndex()
|
||||
entries, err := ParseAppIndex(appIndexPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse APPINDEX: %w", err)
|
||||
}
|
||||
|
@ -99,8 +99,8 @@ func DownloadPackageFromAppIndex(packageName string, release string, pkgType str
|
|||
|
||||
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 downloaded files so thats why it retries here
|
||||
maxRetries := 10
|
||||
// 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 {
|
||||
|
@ -115,7 +115,7 @@ func DownloadPackageFromAppIndex(packageName string, release string, pkgType str
|
|||
f.Close()
|
||||
|
||||
if i < maxRetries-1 {
|
||||
time.Sleep(250 * time.Millisecond) // Wait before retrying
|
||||
time.Sleep(500 * time.Millisecond) // Wait before retrying
|
||||
}
|
||||
}
|
||||
|
||||
|
|
9
go.mod
9
go.mod
|
@ -1,12 +1,7 @@
|
|||
module weforge.xyz/Spitfire/SPM
|
||||
|
||||
go 1.23.0
|
||||
go 1.21
|
||||
|
||||
toolchain go1.24.1
|
||||
|
||||
require (
|
||||
golang.org/x/sys v0.31.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
)
|
||||
require gopkg.in/ini.v1 v1.67.0
|
||||
|
||||
require github.com/stretchr/testify v1.10.0 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -4,8 +4,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
|
|
@ -287,8 +287,8 @@ func copyFile(src, dst string) error {
|
|||
return os.Chmod(dst, info.Mode())
|
||||
}
|
||||
|
||||
// finalizeInstall finalizes the installation by updating installed.ini.
|
||||
func finalizeInstall(packageName, release, version, arch, osName string) error {
|
||||
// FinalizeInstall finalizes the installation by updating installed.ini.
|
||||
func FinalizeInstall(packageName, release, version, arch, osName string) error {
|
||||
installDir, err := GetInstallDir()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -4,34 +4,14 @@
|
|||
|
||||
package spm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
import "fmt"
|
||||
|
||||
// RegisterApp is not supported on non-Windows platforms.
|
||||
func RegisterApp() error {
|
||||
return fmt.Errorf("[WARN] RegisterApp() is only available on Windows")
|
||||
return fmt.Errorf("RegisterApp is only available on Windows")
|
||||
}
|
||||
|
||||
// UnregisterApp is not supported on non-Windows platforms.
|
||||
func UnregisterApp() error {
|
||||
return fmt.Errorf("[WARN] UnregisterApp() is only available on Windows")
|
||||
}
|
||||
|
||||
// IsRegistered returns true if the application is detected as installed.
|
||||
// On Linux, we assume it is installed if the main executable exists in the install directory.
|
||||
func IsRegistered() bool {
|
||||
installDir, err := GetInstallDir()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Assume the executable is named "spitfire" and is located in installDir.
|
||||
exePath := filepath.Join(installDir, "browser", "spitfire")
|
||||
if _, err := os.Stat(exePath); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return fmt.Errorf("UnregisterApp is only available on Windows")
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
// RegisterApp writes the necessary registry keys, making it appear as officially installed app
|
||||
// RegisterApp writes the necessary registry keys, making it appear as offically installed app
|
||||
func RegisterApp() error {
|
||||
exePath, err := GetInstallDir()
|
||||
if err != nil {
|
||||
|
@ -133,15 +133,3 @@ func deleteRegistryTree(root registry.Key, path string) error {
|
|||
// Finally, delete the (now empty) key.
|
||||
return registry.DeleteKey(root, path)
|
||||
}
|
||||
|
||||
// IsRegistered returns true if the application is registered (installed) in the registry.
|
||||
func IsRegistered() bool {
|
||||
// Try to open the uninstall key with read-only access.
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `Software\Microsoft\Windows\CurrentVersion\Uninstall\SpitfireBrowser`, registry.READ)
|
||||
if err != nil {
|
||||
// If the key cannot be opened, assume the app is not registered.
|
||||
return false
|
||||
}
|
||||
defer key.Close()
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
|
@ -20,6 +21,9 @@ func Run() error {
|
|||
}
|
||||
|
||||
exePath := filepath.Join(installDir, "browser", "spitfire.exe")
|
||||
if runtime.GOOS != "windows" {
|
||||
exePath = filepath.Join(installDir, "browser", "spitfire")
|
||||
}
|
||||
|
||||
cmd := exec.Command(exePath)
|
||||
cmd.Dir = filepath.Join(installDir, "browser")
|
||||
|
|
32
run_win.go
32
run_win.go
|
@ -9,10 +9,8 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Run locates and starts the installed Spitfire browser without waiting for it to exit.
|
||||
|
@ -23,6 +21,9 @@ func Run() error {
|
|||
}
|
||||
|
||||
exePath := filepath.Join(installDir, "browser", "spitfire.exe")
|
||||
if runtime.GOOS != "windows" {
|
||||
exePath = filepath.Join(installDir, "browser", "spitfire")
|
||||
}
|
||||
|
||||
cmd := exec.Command(exePath)
|
||||
cmd.Dir = filepath.Join(installDir, "browser")
|
||||
|
@ -36,6 +37,7 @@ func RunAndWait() error {
|
|||
return fmt.Errorf("failed to get install directory: %w", err)
|
||||
}
|
||||
|
||||
// Construct the browser executable path
|
||||
exePath := filepath.Join(installDir, "browser", "spitfire.exe")
|
||||
if _, err := os.Stat(exePath); err != nil {
|
||||
return fmt.Errorf("browser executable not found at %s: %w", exePath, err)
|
||||
|
@ -44,29 +46,23 @@ func RunAndWait() error {
|
|||
cmd := exec.Command(exePath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = filepath.Join(installDir, "browser")
|
||||
|
||||
// Create job object starting the process
|
||||
job, err := windows.CreateJobObject(nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create job object: %w", err)
|
||||
// Use CREATE_NEW_PROCESS_GROUP flag for Windows
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
|
||||
}
|
||||
defer windows.CloseHandle(job)
|
||||
|
||||
fmt.Printf("Starting browser: %s\n", exePath)
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start browser: %w", err)
|
||||
}
|
||||
|
||||
for {
|
||||
cmd := exec.Command("tasklist", "/FI", "IMAGENAME eq spitfire.exe")
|
||||
output, _ := cmd.Output()
|
||||
if !strings.Contains(string(output), "spitfire.exe") {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Printf("Browser process started with PID %d\n", cmd.Process.Pid)
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return fmt.Errorf("browser exited with error: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Browser exited.")
|
||||
fmt.Println("Browser exited successfully.")
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue