diff --git a/.gitignore b/.gitignore index 9a3a8d8..e6a889b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out +# binary +rxsm-updater/rxsm-updater diff --git a/rxsm-updater/rxsm-updater.go b/rxsm-updater/rxsm-updater.go index 7db57e7..feb507f 100644 --- a/rxsm-updater/rxsm-updater.go +++ b/rxsm-updater/rxsm-updater.go @@ -6,52 +6,186 @@ import ( "runtime" "os" "io" - "log" + "fmt" + "errors" "archive/zip" "path/filepath" + "flag" ) const ( - DownloadTempFile string = "rxsm-update.zip" + DefaultZipFile string = "rxsm-update.zip" + RXSMUpdaterVersion string = "v0.0.1" + DefaultUpdateServer string = "https://rxsm-update.exmods.org" ) +var ( + // command line flag arguments + Zipfile string = DefaultZipFile + Target string + Unattended bool + PlatformStream string + UpdateServer string + TargetBinary string +) + +func init() { + fmt.Println("RXSM Updater version", RXSMUpdaterVersion) + fmt.Println("This program updates RXSM headlessly. Use rxsm-updater --help to view advanced features") + fmt.Println(os.Args) + flag.StringVar(&Zipfile, "zip", DefaultZipFile, "The zipfile to extract from (and download to, in unattended mode)") + flag.StringVar(&Target, "target", "", "The directory to target for the update (the directory containing RXSM)") + flag.BoolVar(&Unattended, "unattended", false, "Download & extract the RXSM update (WIP)") + flag.StringVar(&PlatformStream, "stream", "release", "The update stream to use when retrieving updates in unattended mode") + flag.StringVar(&UpdateServer, "server", DefaultUpdateServer, "The web server to use for retrieving update information") +} + func main() { - InstallRXSMUpdate(func(int, string){}) + flag.Parse() + if Unattended { + fmt.Println("Attempting WIP Unattended update") + fmt.Println("This is not advised, and will fail if an updater update is necessary") + URL, isUpdatable, ok := CheckForUpdate(UpdateServer, "", runtime.GOOS+"/"+runtime.GOARCH+"/"+PlatformStream) + if !(ok && isUpdatable && URL != "") { + fmt.Println("No update found") + return + } + downloadErr := DownloadRXSMUpdate(URL, func(int, string){}) + if downloadErr != nil { + fmt.Println(downloadErr) + return + } + proc, forkErr := ForkRXSMUpdate() + if forkErr != nil { + fmt.Println(forkErr) + return + } + fmt.Println("Extraction forked to", proc.Pid) + } else { + fmt.Println("Installing from", Zipfile) + installErr := InstallRXSMUpdate(func(int, string){}) + if installErr != nil { + fmt.Println(installErr) + return + } + } + fmt.Println("Operation complete") // TODO: implement callback for Qt window } -func InstallRXSMUpdate(statusCallback func(progress int, description string)) { - f, openErr := os.Open(DownloadTempFile) +func DownloadRXSMUpdateQuiet(URL string) { + DownloadRXSMUpdate(URL, func(int, string){}) +} + +func DownloadRXSMUpdate(URL string, statusCallback func(progress int, description string)) (error){ + // progress is out of 100 + statusCallback(25, "Downloading") + fmt.Println("Downloading update from "+URL) + f, createErr := os.Create(Zipfile) + if createErr != nil { + fmt.Println("Error creating temporary update file") + statusCallback(-1, "Error creating update temporary file") + return createErr + } + defer f.Close() + ok := DownloadUpdate(URL, f) + if !ok { + fmt.Println("Error downloading update") + statusCallback(-1, "Download failed") + return errors.New("download failed in update API") + } + statusCallback(50, "Installing Updater") + f.Sync() + f.Seek(0,0) + fStat, statErr := f.Stat() + if statErr != nil { + fmt.Println("Error getting download temp file stat") + return statErr + } + fmt.Println("Downloaded", fStat.Size(), "bytes") + statusCallback(75, "Extracting Updater") + // TODO: implement way to have this actually work + zipFile, zipErr := zip.NewReader(f, fStat.Size()) + if zipErr != nil { + fmt.Println("Error creating zip reader") + fmt.Println(zipErr) + return zipErr + } + for _, f := range zipFile.File { + if !f.FileHeader.Mode().IsDir() { + filename := filepath.Clean(f.FileHeader.Name) + var updaterFilename string + if runtime.GOOS == "windows" { + updaterFilename = "rxsm-updater.exe" + TargetBinary = "rxsm-updater2.exe" + } else { + updaterFilename = "rxsm-updater" + TargetBinary = "rxsm-updater2" + } + if len(filename) >= len(updaterFilename) && filename[:len(updaterFilename)] == updaterFilename { + fileReadCloser, openErr := f.Open() + if openErr != nil { + fmt.Println("Error opening updater in zip archive") + return openErr + } + defer fileReadCloser.Close() + destFile, createErr := os.Create(TargetBinary) + if createErr != nil { + fmt.Println("Error creating updater file") + return createErr + } + defer destFile.Close() + _, copyErr := io.Copy(destFile, fileReadCloser) + if copyErr != nil { + fmt.Println("Error copying/extracting updater") + return copyErr + } + } + } + } + statusCallback(100, "Complete") + return nil +} + +func ForkRXSMUpdate() (process *os.Process, err error) { + if runtime.GOOS == "windows" { + return os.StartProcess(TargetBinary, []string{TargetBinary, "--zip", Zipfile, "--target", Target}, nil) + } else { + return os.StartProcess(TargetBinary, []string{TargetBinary, "--zip", Zipfile, "--target", Target}, nil) + } +} + +func InstallRXSMUpdate(statusCallback func(progress int, description string)) (error){ + f, openErr := os.Open(Zipfile) if openErr != nil { - log.Println("Error opening zip file") - log.Println(openErr) - return + fmt.Println("Error opening zip file") + statusCallback(-1, "Opening zip file failed") + return openErr } statusCallback(1, "Extracting") f.Sync() f.Seek(0,0) fStat, statErr := f.Stat() if statErr != nil { - log.Println("Error retrieving file stats") - log.Println(statErr) - statusCallback(-1, "Extracting failed") + fmt.Println("Error retrieving file stats") + statusCallback(-1, "Extracting zip file failed") + return statErr } unpackErr := unpackRXSMInstall(f, fStat.Size(), func(p int, d string){statusCallback(1, d)}) if unpackErr != nil { - log.Println("Error extracting update") - log.Println(unpackErr) - statusCallback(-1, "Extracting failed") - return + fmt.Println("Error extracting update") + statusCallback(-1, "Unpacking failed") + return unpackErr } statusCallback(2, "Installing") // TODO: is there installation required? - rmErr := os.Remove(DownloadTempFile) + rmErr := os.Remove(Zipfile) if rmErr != nil { - log.Println("Error removing download file") - log.Println(rmErr) + fmt.Println("Error removing download file") statusCallback(-1, "Installing failed") - return + return rmErr } + return nil } func unpackRXSMInstallPath(path string) (error) { @@ -93,9 +227,10 @@ func unpackRXSMInstall(reader io.ReaderAt, size int64, statusCallback func(progr filename = filename[len(runtime.GOOS)+1:] } } + filename = filepath.Join(Target, filename) dirErr := os.MkdirAll(filepath.Dir(filename), os.ModeDir | os.ModePerm) if dirErr != nil { - log.Println("Dir err "+filepath.Dir(filename)) + fmt.Println("Dir err "+filepath.Dir(filename)) return dirErr } newFile, createErr := os.Create(filename)