cache
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
abs3nt 2024-02-18 11:48:33 -08:00
parent e56ffc60a3
commit aaa467eee0
Signed by: abs3nt
GPG Key ID: A7BD96A8BAB04C09
5 changed files with 169 additions and 0 deletions

View File

@ -4,6 +4,7 @@ import (
"go.uber.org/fx"
"git.asdf.cafe/abs3nt/gospt-ng/src/app"
"git.asdf.cafe/abs3nt/gospt-ng/src/components/cache"
"git.asdf.cafe/abs3nt/gospt-ng/src/components/cli"
"git.asdf.cafe/abs3nt/gospt-ng/src/components/commands"
)
@ -14,6 +15,7 @@ func main() {
fx.Populate(&s),
app.Config,
fx.Provide(
cache.NewCache,
commands.NewCommander,
),
fx.Invoke(

117
src/components/cache/cache.go vendored Normal file
View File

@ -0,0 +1,117 @@
package cache
import (
"encoding/json"
"fmt"
"log/slog"
"os"
"path/filepath"
"time"
"go.uber.org/fx"
)
type CacheEntry struct {
Expire time.Time `json:"e"`
Value string `json:"v"`
}
type CacheResult struct {
fx.Out
Cache *Cache
}
type Cache struct {
Root string
Log *slog.Logger
}
type CacheParams struct {
fx.In
Log *slog.Logger
}
func NewCache(p CacheParams) CacheResult {
c := &Cache{
Root: filepath.Join(os.TempDir(), "gospt.cache"),
Log: p.Log,
}
return CacheResult{
Cache: c,
}
}
func (c *Cache) load() (map[string]CacheEntry, error) {
out := map[string]CacheEntry{}
cache, err := os.Open(c.Root)
if err != nil {
return nil, err
}
if err := json.NewDecoder(cache).Decode(&out); err != nil {
return nil, err
}
return out, nil
}
func (c *Cache) save(m map[string]CacheEntry) error {
payload, err := json.Marshal(m)
if err != nil {
return err
}
slog.Debug("CACHE", "saving", string(payload))
err = os.WriteFile(c.Root, payload, 0o600)
if err != nil {
return err
}
return nil
}
func (c *Cache) GetOrDo(key string, do func() (string, error), ttl time.Duration) (string, error) {
conf, err := c.load()
if err != nil {
slog.Debug("CACHE", "failed read", err)
return c.Do(key, do, ttl)
}
val, ok := conf[key]
if !ok {
return c.Do(key, do, ttl)
}
if time.Now().After(val.Expire) {
return c.Do(key, do, ttl)
}
return val.Value, nil
}
func (c *Cache) Do(key string, do func() (string, error), ttl time.Duration) (string, error) {
if do == nil {
return "", nil
}
res, err := do()
if err != nil {
return "", err
}
return c.Put(key, res, ttl)
}
func (c *Cache) Put(key string, value string, ttl time.Duration) (string, error) {
conf, err := c.load()
if err != nil {
conf = map[string]CacheEntry{}
}
conf[key] = CacheEntry{
Expire: time.Now().Add(ttl),
Value: value,
}
slog.Debug("CACHE", "new item", fmt.Sprintf("%s: %s", key, value))
err = c.save(conf)
if err != nil {
slog.Debug("CACHE", "failed to save", err)
}
return value, nil
}
func (c *Cache) Clear() error {
return os.Remove(c.Root)
}

View File

@ -204,6 +204,13 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
return c.ClearRadio()
},
},
{
Name: "status",
Usage: "Prints the current status",
Action: func(ctx *cli.Context) error {
return c.Status()
},
},
{
Name: "devices",
Usage: "Lists available devices",

View File

@ -7,6 +7,8 @@ import (
"github.com/zmb3/spotify/v2"
"go.uber.org/fx"
"git.asdf.cafe/abs3nt/gospt-ng/src/components/cache"
)
type CommanderResult struct {
@ -21,6 +23,7 @@ type CommanderParams struct {
Context context.Context
Client *spotify.Client
Log *slog.Logger
Cache *cache.Cache
}
type Commander struct {
@ -28,6 +31,7 @@ type Commander struct {
Client *spotify.Client
User *spotify.PrivateUser
Log *slog.Logger
Cache *cache.Cache
}
func NewCommander(p CommanderParams) CommanderResult {
@ -41,6 +45,7 @@ func NewCommander(p CommanderParams) CommanderResult {
Client: p.Client,
User: currentUser,
Log: p.Log,
Cache: p.Cache,
}
return CommanderResult{
Commander: c,

View File

@ -0,0 +1,38 @@
package commands
import (
"encoding/json"
"fmt"
"time"
"github.com/zmb3/spotify/v2"
)
func (c *Commander) Status() error {
state, err := c.Cache.GetOrDo("state", func() (string, error) {
state, err := c.Client.PlayerState(c.Context)
if err != nil {
return "", err
}
str, err := c.FormatState(state)
if err != nil {
return "", nil
}
return str, nil
}, 5*time.Second)
if err != nil {
return err
}
fmt.Println(state)
return nil
}
func (c *Commander) FormatState(state *spotify.PlayerState) (string, error) {
state.Item.AvailableMarkets = []string{}
state.Item.Album.AvailableMarkets = []string{}
out, err := json.MarshalIndent(state, "", " ")
if err != nil {
return "", err
}
return (string(out)), nil
}