discord/cmd/rpcfetch/apply.go

138 lines
2.9 KiB
Go

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))
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{}
fyne.DoAndWait(func() {
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 {
if errors.Is(err, syscall.EINTR) || errors.Is(err, discord.ErrNonce) {
log.Printf("abandoned further attempts on current tick: %v", err)
return
}
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) })
i := 0
for range retryTicker.C {
if i == 5 {
break
}
nonce, err = d.SetActivity(act)
if err == nil {
fyne.DoAndWait(func() { uiSetFailureState(true) })
break
}
i++
if errors.Is(err, syscall.EINTR) || errors.Is(err, discord.ErrNonce) {
log.Printf("retrying in 100 milliseconds...")
} else {
log.Printf("cannot set activity: %v", err)
}
fyne.DoAndWait(func() { uiSetFailureState(false) })
}
return
}