1package config
2
3import (
4 "context"
5 "errors"
6 "log/slog"
7 "sync"
8 "sync/atomic"
9
10 "github.com/charmbracelet/catwalk/pkg/catwalk"
11 "github.com/charmbracelet/catwalk/pkg/embedded"
12)
13
14type catwalkClient interface {
15 GetProviders(context.Context, string) ([]catwalk.Provider, error)
16}
17
18var _ syncer[[]catwalk.Provider] = (*catwalkSync)(nil)
19
20type catwalkSync struct {
21 once sync.Once
22 result []catwalk.Provider
23 cache cache[[]catwalk.Provider]
24 client catwalkClient
25 autoupdate bool
26 init atomic.Bool
27}
28
29func (s *catwalkSync) Init(client catwalkClient, path string, autoupdate bool) {
30 s.client = client
31 s.cache = newCache[[]catwalk.Provider](path)
32 s.autoupdate = autoupdate
33 s.init.Store(true)
34}
35
36func (s *catwalkSync) Get(ctx context.Context) ([]catwalk.Provider, error) {
37 if !s.init.Load() {
38 panic("called Get before Init")
39 }
40
41 var throwErr error
42 s.once.Do(func() {
43 if !s.autoupdate {
44 slog.Info("Using embedded Catwalk providers")
45 s.result = embedded.GetAll()
46 return
47 }
48
49 cached, etag, cachedErr := s.cache.Get()
50 if len(cached) == 0 || cachedErr != nil {
51 // if cached file is empty, default to embedded providers
52 cached = embedded.GetAll()
53 }
54
55 slog.Info("Fetching providers from Catwalk")
56 result, err := s.client.GetProviders(ctx, etag)
57 if errors.Is(err, context.DeadlineExceeded) {
58 slog.Warn("Catwalk providers not updated in time")
59 s.result = cached
60 return
61 }
62 if errors.Is(err, catwalk.ErrNotModified) {
63 slog.Info("Catwalk providers not modified")
64 s.result = cached
65 return
66 }
67 if err != nil {
68 // On error, fall back to cached (which defaults to embedded if empty).
69 s.result = cached
70 return
71 }
72 if len(result) == 0 {
73 s.result = cached
74 throwErr = errors.New("empty providers list from catwalk")
75 return
76 }
77
78 s.result = result
79 throwErr = s.cache.Store(result)
80 })
81 return s.result, throwErr
82}