package main import ( "brainbaking.com/restictray/restic" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "os" "time" "fyne.io/systray" "fyne.io/systray/example/icon" ) // I'm ignoring go threading issues here; assume no clicks happen at the same time. var wrapper *restic.Wrapper func main() { // init and setup logging log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) wrapper = &restic.Wrapper{} // bootstrap systray (this is a blocking call, second func is onExit) systray.Run(onSystrayReady, func() {}) } func addMenuLatestSnapshot() { snapshot := wrapper.LastSnapshot() systray.AddMenuItem("Latest: "+snapshot.Id+" @ "+snapshot.ShortTime(), "Latest Restic snapshot") } func addMenuNextTime(cnf *restic.Config) { nextTime := wrapper.LastSnapshot().Time.Add(time.Duration(cnf.BackupTimeInHours) * time.Hour) msg := "Next @ " + nextTime.Format(restic.ShortTimeFormat) if time.Now().After(nextTime) { msg = "⚠️ Overdue - " + msg } systray.AddMenuItem(msg, "Future Restic snapshot") } func addMenuQuit() { systray.AddSeparator() addMenuWithQuitAction("Quit", "Quit Restictray") } func addMenuError(err error) { addMenuWithQuitAction("❗ "+err.Error(), "Restictray Error") } func handleError(err error) { log.Err(err).Msg("") systray.ResetMenu() addMenuError(err) addMenuQuit() } func addMenuWithQuitAction(title string, tooltip string) { mQuit := systray.AddMenuItem(title, tooltip) go func() { <-mQuit.ClickedCh log.Info().Msg("Requesting quit") systray.Quit() }() } func onSystrayReady() { systray.SetTemplateIcon(icon.Data, icon.Data) systray.SetTooltip("Restictray") systray.AddMenuItem("... Initializing", "Initializing, please wait.") addMenuQuit() cnf, err := restic.ReadConfig() if err != nil { handleError(err) return } go func() { err := wrapper.UpdateLatestSnapshots(cnf) if err != nil { handleError(err) return } resetAndBuildMainMenu(cnf) }() } // See https://github.com/fyne-io/systray/tree/master/example for more examples func resetAndBuildMainMenu(cnf *restic.Config) { systray.ResetMenu() addMenuLatestSnapshot() addMenuNextTime(cnf) systray.AddSeparator() mnuBackupNow := systray.AddMenuItem("Backup now", "Backup now") mnuBrowse := systray.AddMenuItem("Browse backups in Finder...", "Mount and browse backups") addMenuQuit() for { select { case <-mnuBackupNow.ClickedCh: onClickedMenuBackupNow(mnuBackupNow, cnf) case <-mnuBrowse.ClickedCh: onClickedMenuBrowse(mnuBrowse, cnf) } } } func onClickedMenuBrowse(browse *systray.MenuItem, cnf *restic.Config) { err := wrapper.MountBackups(cnf) if err != nil { handleError(err) } } func onClickedMenuBackupNow(mnu *systray.MenuItem, cnf *restic.Config) { mnu.SetTitle("🔄 Backup in progress...") mnu.Disable() // TODO after a backup, reinitialize latest snapshot + latest/next menus // TODO not by calling resetAndBuild again: this is from the for{}? // TODO how does this interop with a future goroutine that auto-backups? // TODO need for separate "backupInProgress" bool? err := wrapper.Backup(cnf) mnu.SetTitle("Backup now") mnu.Enable() if err != nil { handleError(err) } }