package main import ( "fmt" "log" "os" "os/exec" "path/filepath" "strconv" "strings" "sync" "time" spm "weforge.xyz/Spitfire/SPM" ) // ------------------------------------------------------------------- // GLOBALS // ------------------------------------------------------------------- var ( copyingInProgress int32 updateStatusMsg string browserCmd *exec.Cmd browserMutex sync.Mutex // Protects the lock file map isBackgroundMode bool ) // StateFile holds the update state from our INI type StateFile struct { IsUpdating bool Progress int32 Stage string // "Idle","Downloading","Decompressing","ReadyToInstall","Installing","Complete","Failed" } // ------------------------------------------------------------------- // LOG INIT // ------------------------------------------------------------------- func init() { log.SetFlags(0) log.SetPrefix("[UPDATER] ") } // ------------------------------------------------------------------- // INI Helpers // ------------------------------------------------------------------- func getSpmDir() string { installDir, err := spm.GetDefaultInstallDir() if err != nil { log.Println("Warning: Using '.' for spmDir =>", err) return "." } spmDir := filepath.Join(installDir, "spm") _ = os.MkdirAll(spmDir, 0755) return spmDir } func getStateFilePath() string { return filepath.Join(getSpmDir(), "update_state.ini") } func WriteState(isUpdating bool, progress int32, stage string) { content := "[UpdateState]\n" + fmt.Sprintf("IsUpdating=%v\n", isUpdating) + fmt.Sprintf("Progress=%d\n", progress) + fmt.Sprintf("Stage=%s\n", stage) _ = os.WriteFile(getStateFilePath(), []byte(content), 0644) } func ReadState() StateFile { sf := StateFile{} data, err := os.ReadFile(getStateFilePath()) if err != nil { return sf } lines := strings.Split(string(data), "\n") for _, line := range lines { line = strings.TrimSpace(line) if line == "" || strings.HasPrefix(line, "[") { continue } parts := strings.SplitN(line, "=", 2) if len(parts) != 2 { continue } key := strings.TrimSpace(parts[0]) val := strings.TrimSpace(parts[1]) switch key { case "IsUpdating": sf.IsUpdating = (val == "true") case "Progress": if i, errAtoi := strconv.Atoi(val); errAtoi == nil { sf.Progress = int32(i) } case "Stage": sf.Stage = val } } return sf } // ------------------------------------------------------------------- // BROWSER // ------------------------------------------------------------------- // LaunchBrowser starts the browser and creates a unique lock file in the spmdir. func LaunchBrowser() { spmDir := getSpmDir() // Generate a unique lock file name (based on timestamp) lockFileName := fmt.Sprintf("browser_lock_%d.lock", time.Now().UnixNano()) lockFilePath := filepath.Join(spmDir, lockFileName) // Create the lock file file, err := os.Create(lockFilePath) if err != nil { fmt.Println("Failed to create lock file:", err) return } file.Close() // Run the browser process and wait for it to finish fmt.Println("Starting browser...") err = spm.RunAndWait() if err != nil { fmt.Println("Browser process encountered an error:", err) } // Remove the lock file after the browser finishes fmt.Printf("Removing lock file: %s\n", lockFilePath) err = os.Remove(lockFilePath) if err != nil { fmt.Println("Failed to remove lock file:", err) } else { fmt.Println("Lock file removed successfully.") } } // IsBrowserRunning checks if there are any browser lock files in the spmdir. func IsBrowserRunning() bool { spmDir := getSpmDir() // Look for lock files in the spmdir lockFiles, err := filepath.Glob(filepath.Join(spmDir, "browser_lock_*.lock")) if err != nil { fmt.Println("Error checking for lock files:", err) return false } // Return true if at least one lock file is found return len(lockFiles) > 0 } // ------------------------------------------------------------------- // BACKGROUND SERVICE // ------------------------------------------------------------------- func runBackgroundUpdater() { // Delete the update_state.ini file on startup stateFilePath := getStateFilePath() if _, err := os.Stat(stateFilePath); err == nil { err = os.Remove(stateFilePath) if err != nil { log.Printf("Failed to delete state file %s: %v\n", stateFilePath, err) } else { log.Printf("Deleted state file: %s\n", stateFilePath) } } else { log.Printf("State file does not exist, no need to delete: %s\n", stateFilePath) } // Remove all lock files in the spmdir spmDir := getSpmDir() lockFilePattern := filepath.Join(spmDir, "browser_lock_*.lock") lockFiles, err := filepath.Glob(lockFilePattern) if err != nil { log.Printf("Failed to scan for lock files in %s: %v\n", spmDir, err) } else { for _, lockFile := range lockFiles { err := os.Remove(lockFile) if err != nil { log.Printf("Failed to delete lock file %s: %v\n", lockFile, err) } else { log.Printf("Deleted lock file: %s\n", lockFile) } } } log.Println("Background updater started (no GUI).") // Start a separate goroutine to periodically update the progress in update_state.ini go func() { for { // Get current progress and task from spm progress, task := spm.GetProgress() // Determine if the updater is in "Copying files to install directory" stage isUpdating := task == "Copying files to install directory" // Write the state to the update_state.ini file WriteState(isUpdating, int32(progress), task) // Sleep for 1 hour before updating the progress again time.Sleep(1 * time.Hour) } }() // Main loop for periodic AutoDownloadUpdates and AutoInstallUpdates for { // Run AutoDownloadUpdates every 10 seconds err := spm.AutoDownloadUpdates() if err != nil { log.Printf("AutoDownloadUpdates failed: %v\n", err) WriteState(false, 0, fmt.Sprintf("Update failed: %v", err)) time.Sleep(10 * time.Second) continue } // Check if the browser is running if IsBrowserRunning() { log.Println("Browser is running. Waiting before installing updates.") time.Sleep(10 * time.Second) continue } // If the browser is not running, run AutoInstallUpdates log.Println("\nBrowser is not running. Starting installation of updates.") err = spm.AutoInstallUpdates() if err != nil { log.Printf("AutoInstallUpdates failed: %v\n", err) WriteState(false, 0, fmt.Sprintf("Installation failed: %v", err)) time.Sleep(10 * time.Second) continue } // Write to update_state.ini indicating updates are complete WriteState(false, 100, "Complete") log.Println("Updates installed successfully. Waiting before next check.") // Wait 10 seconds before checking again time.Sleep(10 * time.Second) } } // ------------------------------------------------------------------- // MAIN // ------------------------------------------------------------------- func main() { if len(os.Args) > 1 && os.Args[1] == "--update-service" { isBackgroundMode = true runBackgroundUpdater() return } else { log.Println("Launcher started (foreground mode).") ShowUpdateWindow() } fmt.Println("Exiting launcher.") }