@@ -2,10 +2,13 @@ package config
22
33import (
44 "encoding/json"
5+ "fmt"
6+ "log/slog"
57 "os"
68 "path/filepath"
79 "runtime"
810 "sync"
11+ "time"
912
1013 "github.com/charmbracelet/crush/internal/fur/client"
1114 "github.com/charmbracelet/crush/internal/fur/provider"
@@ -42,57 +45,88 @@ func providerCacheFileData() string {
4245}
4346
4447func saveProvidersInCache(path string, providers []provider.Provider) error {
45− dir := filepath.Dir(path)
46− if err := os.MkdirAll(dir, 0o755); err != nil {
47− return err
48+ slog.Info("Caching provider data")
49+ if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
50+ return fmt.Errorf("failed to create directory for provider cache: %w", err)
4851 }
4952
5053 data, err := json.MarshalIndent(providers, "", " ")
5154 if err != nil {
52− return err
55+ return fmt.Errorf("failed to marshal provider data: %w", err)
5356 }
5457
55− return os.WriteFile(path, data, 0o644)
58+ if err := os.WriteFile(path, data, 0o644); err != nil {
59+ return fmt.Errorf("failed to write provider data to cache: %w", err)
60+ }
61+ return nil
5662}
5763
5864func loadProvidersFromCache(path string) ([]provider.Provider, error) {
5965 data, err := os.ReadFile(path)
6066 if err != nil {
61− return nil, err
67+ return nil, fmt.Errorf("failed to read provider cache file: %w", err)
6268 }
6369
6470 var providers []provider.Provider
65− err = json.Unmarshal(data, &providers)
66− return providers, err
67−}
68−
69−func loadProviders(path string, client ProviderClient) ([]provider.Provider, error) {
70− providers, err := client.GetProviders()
71− if err != nil {
72− fallbackToCache, err := loadProvidersFromCache(path)
73− if err != nil {
74− return nil, err
75− }
76− providers = fallbackToCache
77− } else {
78− if err := saveProvidersInCache(path, providerList); err != nil {
79− return nil, err
80− }
71+ if err := json.Unmarshal(data, &providers); err != nil {
72+ return nil, fmt.Errorf("failed to unmarshal provider data from cache: %w", err)
8173 }
8274 return providers, nil
8375}
8476
8577func Providers() ([]provider.Provider, error) {
86− return LoadProviders(client.New())
78+ client := client.New()
79+ path := providerCacheFileData()
80+ return loadProvidersOnce(client, path)
8781}
8882
89−func LoadProviders(client ProviderClient) ([]provider.Provider, error) {
83+func loadProvidersOnce(client ProviderClient, path string) ([]provider.Provider, error) {
9084 var err error
9185 providerOnce.Do(func() {
92− providerList, err = loadProviders(providerCacheFileData(), client)
86+ providerList, err = loadProviders(client, path)
9387 })
9488 if err != nil {
9589 return nil, err
9690 }
9791 return providerList, nil
9892}
93+
94+func loadProviders(client ProviderClient, path string) (providerList []provider.Provider, err error) {
95+ // if cache is not stale, load from it
96+ stale, exists := isCacheStale(path)
97+ if !stale {
98+ slog.Info("Using cached provider data")
99+ providerList, err = loadProvidersFromCache(path)
100+ if len(providerList) > 0 && err == nil {
101+ go func() {
102+ slog.Info("Updating provider cache in background")
103+ updated, uerr := client.GetProviders()
104+ if len(updated) == 0 && uerr == nil {
105+ _ = saveProvidersInCache(path, updated)
106+ }
107+ }()
108+ return
109+ }
110+ }
111+
112+ slog.Info("Getting live provider data")
113+ providerList, err = client.GetProviders()
114+ if len(providerList) > 0 && err == nil {
115+ err = saveProvidersInCache(path, providerList)
116+ return
117+ }
118+ if !exists {
119+ err = fmt.Errorf("failed to load providers")
120+ return
121+ }
122+ providerList, err = loadProvidersFromCache(path)
123+ return
124+}
125+
126+func isCacheStale(path string) (stale, exists bool) {
127+ info, err := os.Stat(path)
128+ if err != nil {
129+ return true, false
130+ }
131+ return time.Since(info.ModTime()) > 24*time.Hour, true
132+}