package main import ( "errors" "fmt" "log" "os" "strconv" "strings" "sync/atomic" "syscall" "time" "fyne.io/fyne/v2" "git.gensokyo.uk/cat/discord" ) var ( pidS = strconv.Itoa(os.Getpid()) hostname string launchTime = time.Now().UTC() replCounter atomic.Uint64 ) var substitute = map[string]func(s *applyState) string{ "pid": func(_ *applyState) string { return pidS }, "hostname": func(_ *applyState) string { return hostname }, "1min": func(s *applyState) string { s.populateLoadavg() return s.loadavg[0] }, "5min": func(s *applyState) string { s.populateLoadavg() return s.loadavg[1] }, "15min": func(s *applyState) string { s.populateLoadavg() return s.loadavg[2] }, "used": func(s *applyState) string { s.populateMem() return fmt.Sprintf("%.1f GiB", float64(s.mem[1]-s.mem[0])/(1<<20)) }, "total": func(s *applyState) string { s.populateMem() return fmt.Sprintf("%.1f GiB", float64(s.mem[1])/(1<<20)) }, } type applyState struct { loadavg *[3]string mem *[2]int } func (s *applyState) replace(t string) string { for k, f := range substitute { t = strings.ReplaceAll(t, "%"+k, f(s)) go func() { replCounter.Add(uint64(len(t))) statusReplaces.SetText(strconv.Itoa(int(replCounter.Load())) + " bytes") }() } return t } func apply() { prof := conf.profile() // open new client if ID changes if d.ID() != prof.ID { // close previous client if err := d.Close(); err != nil { log.Fatalf("error closing previous client: %s", err) } // open new client d = discord.New(prof.ID) } confLock.RLock() act := *prof.Activity s := &applyState{} act.State = s.replace(act.State) act.Details = s.replace(act.Details) act.Timestamps = &discord.ActivityTimestamps{Start: launchTime.Unix()} act.Assets = &discord.ActivityAssets{ LargeImage: act.Assets.LargeImage, LargeText: s.replace(act.Assets.LargeText), SmallImage: act.Assets.SmallImage, SmallText: s.replace(act.Assets.SmallText), } confLock.RUnlock() if nonce, err := retry(&act, s); err != nil { log.Fatalf("error setting activity: %s", err) } else { log.Printf("activity updated with nonce %s", nonce) } } var retryTicker = time.NewTicker(100 * time.Millisecond) func retry(act *discord.Activity, s *applyState) (nonce string, err error) { fyne.DoAndWait(func() { uiUpdateClientInfo(s) }) for range retryTicker.C { nonce, err = d.SetActivity(act) if err == nil { break } if errors.Is(err, syscall.EINTR) || errors.Is(err, discord.ErrNonce) { log.Println("retrying in 100 milliseconds...") } else { log.Printf("cannot set activity: %v", err) } fyne.DoAndWait(func() { uiSetFailureState(false) }) } fyne.DoAndWait(func() { uiSetFailureState(true) }) return }