package main import ( "fmt" "path/filepath" "spitfire-installer/spm" ) // Installer manages the download, decompression, and installation processes. type Installer struct { // Progress info from SPM Progress int Task string // Internal states IsDownloading bool IsInstalling bool DoneDownload bool DoneInstall bool LastError error PendingInstall bool // Paths DownloadDir string TempDir string } // NewInstaller creates a new Installer with initial state. func NewInstaller() *Installer { return &Installer{} } // StartDownloadDecompress starts the download and decompression in a background goroutine. func (inst *Installer) StartDownloadDecompress() { inst.IsDownloading = true go func() { defer func() { inst.IsDownloading = false inst.DoneDownload = (inst.LastError == nil) // If user requested install while we were downloading (PendingInstall), // automatically do the install now that we're done decompressing. if inst.PendingInstall && inst.DoneDownload && !inst.IsInstalling && !inst.DoneInstall { inst.doFinalInstall() } }() spm.UpdateProgress(0, "Preparing to download...") inst.DownloadDir = spm.GetTempDownloadDir() // 1) Download APPINDEX appIndexPath := filepath.Join(inst.DownloadDir, "APPINDEX") spm.UpdateProgress(0, "Downloading APPINDEX") if err := spm.DownloadAppIndex(appIndexPath); err != nil { inst.LastError = err return } // 2) Download package packageName := "spitfire-browser" release := "nightly" spm.UpdateProgress(0, "Downloading package...") if err := spm.DownloadPackageFromAppIndex(appIndexPath, packageName, release, inst.DownloadDir); err != nil { inst.LastError = err return } // 3) Decompress spm.UpdateProgress(0, "Decompressing...") tempDir, err := spm.DecompressPackage(inst.DownloadDir) if err != nil { inst.LastError = err return } inst.TempDir = tempDir }() } // FinalInstall is called by the UI to request installation. // If download is done, it runs immediately, otherwise sets PendingInstall=true. func (inst *Installer) FinalInstall() { // Already installed or installing => ignore repeated calls if inst.IsInstalling || inst.DoneInstall { return } // If not done downloading, just mark that we want to install once finished if !inst.DoneDownload { fmt.Println("Cannot install now: download and decompression not complete -> pending install.") inst.PendingInstall = true return } // Otherwise, go ahead and install now inst.doFinalInstall() } // doFinalInstall does the actual file move and sets states func (inst *Installer) doFinalInstall() { inst.IsInstalling = true inst.PendingInstall = false // we are fulfilling the install now go func() { defer func() { inst.IsInstalling = false inst.DoneInstall = (inst.LastError == nil) }() // Generate default install directory installDir, err := spm.GetDefaultInstallDir() if err != nil { inst.LastError = fmt.Errorf("failed to determine default install directory: %w", err) return } // Move files spm.UpdateProgress(0, "Installing...") if err := spm.MoveFilesToInstallDir(inst.TempDir, installDir); err != nil { inst.LastError = err return } spm.UpdateProgress(100, "Installation complete!") }() } // PollProgress fetches the latest progress and task from SPM. func (inst *Installer) PollProgress() { p, t := spm.GetProgress() inst.Progress, inst.Task = p, t }