127 lines
3.7 KiB
Go
127 lines
3.7 KiB
Go
package gui
|
|
|
|
import (
|
|
"cmp"
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/yetone/magpie/internal/provider"
|
|
"github.com/yetone/magpie/internal/settings"
|
|
)
|
|
|
|
// Settings → Usage in the menu bar: one subscription's or plan's windows
|
|
// beside the tray icon ("42% · 18%"), for keeping an eye on them without
|
|
// opening magpie. The Mac's menu bar shows the text; Windows' tray has no
|
|
// room for any, and gets it in the icon's tooltip only.
|
|
|
|
// trayUsageEvery is how often the text is brought up to date. The vendors
|
|
// are asked no more than the Usage page asks them: their answers are
|
|
// cached for a minute, and this reads the cache.
|
|
const trayUsageEvery = 3 * time.Minute
|
|
|
|
// trayCardID names a card for settings.TrayUsage: its provider, and the
|
|
// account when there is one, as two of one vendor can be signed in.
|
|
func trayCardID(q provider.SubscriptionQuota) string {
|
|
if q.User == "" {
|
|
return q.Provider
|
|
}
|
|
return q.Provider + "|" + q.User
|
|
}
|
|
|
|
// trayWindows are the windows a card's text shows: those that stop the
|
|
// account (not on-demand spending, nor one that counts a single model's
|
|
// use), the shortest first where the vendor says how long they run.
|
|
func trayWindows(q provider.SubscriptionQuota) []provider.QuotaWindow {
|
|
var ws []provider.QuotaWindow
|
|
for _, w := range q.Windows {
|
|
if !w.Aside && w.Model == "" {
|
|
ws = append(ws, w)
|
|
}
|
|
}
|
|
slices.SortStableFunc(ws, func(a, b provider.QuotaWindow) int {
|
|
if a.Span == 0 || b.Span == 0 {
|
|
return 0
|
|
}
|
|
return cmp.Compare(a.Span, b.Span)
|
|
})
|
|
return ws
|
|
}
|
|
|
|
// trayUsageText is the menu bar's text for a card and the tooltip that
|
|
// spells it out: each window's use, and when it starts again.
|
|
func trayUsageText(q provider.SubscriptionQuota, now time.Time) (label, tip string) {
|
|
if q.Error != "" {
|
|
return "", q.Name + ": " + q.Error
|
|
}
|
|
ws := trayWindows(q)
|
|
if len(ws) == 0 {
|
|
if q.Balance == "" {
|
|
return "", ""
|
|
}
|
|
return q.Balance, q.Name + " · " + q.Balance
|
|
}
|
|
var short, long []string
|
|
for _, w := range ws {
|
|
pct := fmt.Sprintf("%d%%", int(math.Round(math.Max(0, math.Min(100, w.Used)))))
|
|
short = append(short, pct)
|
|
line := w.Name + " " + pct
|
|
if w.Display != "" {
|
|
line = w.Name + " " + w.Display
|
|
}
|
|
if at := resetAt(w, now); !at.IsZero() && at.After(now) {
|
|
line += " · resets in " + until(at.Sub(now))
|
|
}
|
|
long = append(long, line)
|
|
}
|
|
if len(short) > 2 {
|
|
short = short[:2]
|
|
}
|
|
return strings.Join(short, " · "), q.Name + "\n" + strings.Join(long, "\n")
|
|
}
|
|
|
|
func resetAt(w provider.QuotaWindow, now time.Time) time.Time {
|
|
if w.ResetsAt != nil {
|
|
return *w.ResetsAt
|
|
}
|
|
if w.ResetSecs > 0 {
|
|
return now.Add(time.Duration(w.ResetSecs) * time.Second)
|
|
}
|
|
return time.Time{}
|
|
}
|
|
|
|
// until says a wait in the largest two units: 3d 4h, 2h 10m, 7m.
|
|
func until(d time.Duration) string {
|
|
d = d.Round(time.Minute)
|
|
days, hours, mins := int(d/(24*time.Hour)), int(d%(24*time.Hour)/time.Hour), int(d%time.Hour/time.Minute)
|
|
switch {
|
|
case days > 0:
|
|
return fmt.Sprintf("%dd %dh", days, hours)
|
|
case hours > 0:
|
|
return fmt.Sprintf("%dh %dm", hours, mins)
|
|
}
|
|
return fmt.Sprintf("%dm", max(mins, 1))
|
|
}
|
|
|
|
// trayUsageCard is the card settings.TrayUsage names, among the Usage
|
|
// page's (cached as there); false when it is off or gone.
|
|
func trayUsageCard(ctx context.Context) (provider.SubscriptionQuota, bool) {
|
|
id := settings.Load().TrayUsage
|
|
if id == "" {
|
|
return provider.SubscriptionQuota{}, false
|
|
}
|
|
cards := provider.Quotas(ctx)
|
|
i := slices.IndexFunc(cards, func(q provider.SubscriptionQuota) bool { return trayCardID(q) == id })
|
|
if i < 0 {
|
|
return provider.SubscriptionQuota{}, false
|
|
}
|
|
return cards[i], true
|
|
}
|
|
|
|
// onTrayUsage brings the tray's text up to date at once, when the Settings
|
|
// page changes which card it shows; set by the process that has the tray.
|
|
var onTrayUsage func()
|