diff --git a/README.md b/README.md index b65fd01..63f036c 100644 --- a/README.md +++ b/README.md @@ -47,9 +47,9 @@ func main() { } // -- Download -- - // spm.AutoDownloadSpecified(specs) downloads specified packages to temp dir and decompresses them, making them ready for install by running "spm.AutoInstallUpdates()". + // spm.DownloadSpecified(specs) downloads specified packages to temp dir and decompresses them, making them ready for install by running "spm.InstallUpdates()". fmt.Println("Starting download and decompression...") - if err := spm.AutoDownloadSpecified(specs); err != nil { + if err := spm.DownloadSpecified(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.AutoInstallUpdates()". - if err := spm.AutoInstallUpdates(); 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.InstallUpdates()". + if err := spm.InstallUpdates(); 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,7 +78,6 @@ 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.*
License diff --git a/appindex.go b/appindex.go index 14a77be..848d59e 100644 --- a/appindex.go +++ b/appindex.go @@ -6,14 +6,46 @@ import ( "io" "net/http" "os" + "path/filepath" + "sort" "strings" + + "gopkg.in/ini.v1" ) -const appIndexURL = "https://downloads.sourceforge.net/project/spitfire-browser/APPINDEX" +// 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 +} -func DownloadAppIndex(dest string) error { +// 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 { UpdateProgress(0, "Downloading APPINDEX") - resp, err := http.Get(appIndexURL) + resp, err := http.Get(url) if err != nil { return err } @@ -52,25 +84,10 @@ func DownloadAppIndex(dest string) error { return nil } -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() - +// parseAppIndexFromReader parses an APPINDEX from any io.Reader. +func parseAppIndexFromReader(r io.Reader) ([]AppIndexEntry, error) { var entries []AppIndexEntry - scanner := bufio.NewScanner(file) + scanner := bufio.NewScanner(r) entry := AppIndexEntry{} for scanner.Scan() { @@ -112,12 +129,222 @@ func ParseAppIndex(filePath string) ([]AppIndexEntry, error) { entries = append(entries, entry) } - // Log all parsed entries - fmt.Printf("[INFO] Total parsed entries: %d\n", len(entries)) + 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)) 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, scanner.Err() + 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 } diff --git a/auto.go b/auto.go index 92e574a..d544a44 100644 --- a/auto.go +++ b/auto.go @@ -2,7 +2,6 @@ package spm import ( "fmt" - "os" "path/filepath" ) @@ -10,22 +9,19 @@ import ( // 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 +// 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 AutoInstallUpdates(). -func AutoDownloadUpdates() error { +// 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 - appIndexPath := filepath.Join(os.TempDir(), "APPINDEX") - fmt.Println("[INFO] Starting APPINDEX download to:", appIndexPath) - err := DownloadAppIndex(appIndexPath) + 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 - fmt.Println("[INFO] Parsing APPINDEX file:", appIndexPath) - entries, err := ParseAppIndex(appIndexPath) + entries, err := GetIndex() if err != nil { return fmt.Errorf("[ERROR] Failed to parse APPINDEX: %w", err) } @@ -84,7 +80,7 @@ func AutoDownloadUpdates() error { 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) + 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) } @@ -99,7 +95,7 @@ func AutoDownloadUpdates() error { } fmt.Printf("[INFO] Package '%s' decompressed successfully to: %s\n", matchingEntry.Name, tempDir) - // 7) Store in pendingUpdates so that AutoInstallUpdates can finish the job + // 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, @@ -111,13 +107,13 @@ func AutoDownloadUpdates() error { }) } - fmt.Println("[INFO] AutoDownloadUpdates completed successfully") + fmt.Println("[INFO] DownloadUpdates completed successfully") return nil } -// AutoInstallUpdates installs any packages that were downloaded and decompressed by AutoDownloadUpdates. +// 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 AutoInstallUpdates() error { +func InstallUpdates() error { installDir, err := GetInstallDir() if err != nil { return err @@ -146,7 +142,7 @@ func AutoInstallUpdates() 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) } @@ -156,18 +152,15 @@ func AutoInstallUpdates() error { return nil } -func AutoDownloadSpecified(specs []AppIndexEntry) error { +func DownloadSpecified(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 { + 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 - fmt.Println("[INFO] Parsing APPINDEX file:", appIndexPath) - entries, err := ParseAppIndex(appIndexPath) + entries, err := GetIndex() if err != nil { return fmt.Errorf("[ERROR] Failed to parse APPINDEX: %w", err) } @@ -203,29 +196,27 @@ func AutoDownloadSpecified(specs []AppIndexEntry) error { } 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) - // } + 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, @@ -251,7 +242,7 @@ func AutoDownloadSpecified(specs []AppIndexEntry) error { } fmt.Printf("[INFO] Package '%s' decompressed successfully to: %s\n", matchingEntry.Name, tempDir) - // 7) Store in pendingUpdates for AutoInstallUpdates + // Add to pendingUpdates for InstallUpdates fmt.Printf("[INFO] Adding '%s' to pending updates\n", matchingEntry.Name) pendingUpdates = append(pendingUpdates, *matchingEntry) } diff --git a/decompress.go b/decompress.go index 7f7a207..00003e2 100644 --- a/decompress.go +++ b/decompress.go @@ -108,7 +108,11 @@ func decompressTarGz(srcFile, destDir string, updateProgress func(int, string)) // Update progress after extracting each file. if updateProgress != nil { percent := int((progressReader.BytesRead * 100) / totalSize) - updateProgress(percent, fmt.Sprintf("Extracted: %s", header.Name)) + name := header.Name + if len(name) > 50 { + name = name[len(name)-50:] + } + updateProgress(percent, fmt.Sprintf("Extracted: %s", name)) } } diff --git a/download.go b/download.go index 30d239c..c6464f2 100644 --- a/download.go +++ b/download.go @@ -11,9 +11,9 @@ import ( ) // DownloadPackageFromAppIndex selects and downloads the correct package from the APPINDEX. -func DownloadPackageFromAppIndex(appIndexPath string, packageName string, release string, pkgType string, destDir string) error { +func DownloadPackageFromAppIndex(packageName string, release string, pkgType string, destDir string) error { // Parse the APPINDEX - entries, err := ParseAppIndex(appIndexPath) + entries, err := GetIndex() if err != nil { return fmt.Errorf("failed to parse APPINDEX: %w", err) } @@ -99,8 +99,8 @@ func DownloadPackageFromAppIndex(appIndexPath string, packageName string, releas 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 + // 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 for i := 0; i < maxRetries; i++ { err = os.Rename(downloadedFilePath, expectedFilePath) if err == nil { @@ -115,7 +115,7 @@ func DownloadPackageFromAppIndex(appIndexPath string, packageName string, releas f.Close() if i < maxRetries-1 { - time.Sleep(500 * time.Millisecond) // Wait before retrying + time.Sleep(250 * time.Millisecond) // Wait before retrying } } diff --git a/go.mod b/go.mod index 8b67881..f4c1524 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,12 @@ module weforge.xyz/Spitfire/SPM -go 1.21 +go 1.23.0 -require gopkg.in/ini.v1 v1.67.0 +toolchain go1.24.1 + +require ( + golang.org/x/sys v0.31.0 + gopkg.in/ini.v1 v1.67.0 +) require github.com/stretchr/testify v1.10.0 // indirect diff --git a/go.sum b/go.sum index be248fb..bd54818 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ 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= diff --git a/install.go b/install.go index 9fbf274..a8d8521 100644 --- a/install.go +++ b/install.go @@ -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 diff --git a/register_unix.go b/register_unix.go index 6568edd..f9e35c9 100644 --- a/register_unix.go +++ b/register_unix.go @@ -4,14 +4,34 @@ package spm -import "fmt" +import ( + "fmt" + "os" + "path/filepath" +) // RegisterApp is not supported on non-Windows platforms. func RegisterApp() error { - return fmt.Errorf("RegisterApp is only available on Windows") + return fmt.Errorf("[WARN] RegisterApp() is only available on Windows") } // UnregisterApp is not supported on non-Windows platforms. func UnregisterApp() error { - return fmt.Errorf("UnregisterApp is only available on Windows") + 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 } diff --git a/register_win.go b/register_win.go index b2cf47f..206f0ae 100644 --- a/register_win.go +++ b/register_win.go @@ -10,7 +10,7 @@ import ( "golang.org/x/sys/windows/registry" ) -// RegisterApp writes the necessary registry keys, making it appear as offically installed app +// RegisterApp writes the necessary registry keys, making it appear as officially installed app func RegisterApp() error { exePath, err := GetInstallDir() if err != nil { @@ -133,3 +133,15 @@ 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 +} diff --git a/run_unix.go b/run_unix.go index cc82b79..aa580b2 100644 --- a/run_unix.go +++ b/run_unix.go @@ -9,7 +9,6 @@ import ( "os" "os/exec" "path/filepath" - "runtime" "syscall" ) @@ -21,9 +20,6 @@ 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") diff --git a/run_win.go b/run_win.go index d1f2b38..706b6d4 100644 --- a/run_win.go +++ b/run_win.go @@ -9,8 +9,10 @@ import ( "os" "os/exec" "path/filepath" - "runtime" - "syscall" + "strings" + "time" + + "golang.org/x/sys/windows" ) // Run locates and starts the installed Spitfire browser without waiting for it to exit. @@ -21,9 +23,6 @@ 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") @@ -37,7 +36,6 @@ 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) @@ -46,23 +44,29 @@ func RunAndWait() error { cmd := exec.Command(exePath) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr + cmd.Dir = filepath.Join(installDir, "browser") - // Use CREATE_NEW_PROCESS_GROUP flag for Windows - cmd.SysProcAttr = &syscall.SysProcAttr{ - CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP, + // 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) } + 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) } - 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) + 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.Println("Browser exited successfully.") + fmt.Println("Browser exited.") return nil }