@@ -12,6 +12,7 @@ import (
1212 "time"
1313
1414 "github.com/charmbracelet/catwalk/pkg/catwalk"
15+ "github.com/charmbracelet/catwalk/pkg/embedded"
1516 "github.com/charmbracelet/crush/internal/home"
1617)
1718
@@ -22,6 +23,7 @@ type ProviderClient interface {
2223var (
2324 providerOnce sync.Once
2425 providerList []catwalk.Provider
26+ providerErr error
2527)
2628
2729// file to cache provider data
@@ -75,55 +77,93 @@ func loadProvidersFromCache(path string) ([]catwalk.Provider, error) {
7577 return providers, nil
7678}
7779
78−func Providers() ([]catwalk.Provider, error) {
79− catwalkURL := cmp.Or(os.Getenv("CATWALK_URL"), defaultCatwalkURL)
80− client := catwalk.NewWithURL(catwalkURL)
81− path := providerCacheFileData()
82− return loadProvidersOnce(client, path)
83−}
84−
85−func loadProvidersOnce(client ProviderClient, path string) ([]catwalk.Provider, error) {
86− var err error
80+func Providers(cfg *Config) ([]catwalk.Provider, error) {
8781 providerOnce.Do(func() {
88− providerList, err = loadProviders(client, path)
82+ catwalkURL := cmp.Or(os.Getenv("CATWALK_URL"), defaultCatwalkURL)
83+ client := catwalk.NewWithURL(catwalkURL)
84+ path := providerCacheFileData()
85+
86+ autoUpdateDisabled := cfg.Options.DisableProviderAutoUpdate
87+ providerList, providerErr = loadProviders(autoUpdateDisabled, client, path)
8988 })
90− if err != nil {
91− return nil, err
92− }
93− return providerList, nil
89+ return providerList, providerErr
9490}
9591
96−func loadProviders(client ProviderClient, path string) (providerList []catwalk.Provider, err error) {
97− // if cache is not stale, load from it
98− stale, exists := isCacheStale(path)
99− if !stale {
100− slog.Info("Using cached provider data", "path", path)
101− providerList, err = loadProvidersFromCache(path)
102− if len(providerList) > 0 && err == nil {
103− go func() {
104− slog.Info("Updating provider cache in background", "path", path)
105− updated, uerr := client.GetProviders()
106− if len(updated) > 0 && uerr == nil {
107− _ = saveProvidersInCache(path, updated)
108− }
109− }()
110− return
92+func loadProviders(autoUpdateDisabled bool, client ProviderClient, path string) ([]catwalk.Provider, error) {
93+ cacheIsStale, cacheExists := isCacheStale(path)
94+
95+ catwalkGetAndSave := func() ([]catwalk.Provider, error) {
96+ providers, err := client.GetProviders()
97+ if err != nil {
98+ return nil, fmt.Errorf("failed to fetch providers from catwalk: %w", err)
99+ }
100+ if len(providers) == 0 {
101+ return nil, fmt.Errorf("empty providers list from catwalk")
102+ }
103+ if err := saveProvidersInCache(path, providers); err != nil {
104+ return nil, err
111105 }
106+ return providers, nil
112107 }
113108
114− slog.Info("Getting live provider data", "path", path)
115− providerList, err = client.GetProviders()
116− if len(providerList) > 0 && err == nil {
117− err = saveProvidersInCache(path, providerList)
118− return
109+ backgroundCacheUpdate := func() {
110+ go func() {
111+ slog.Info("Updating providers cache in background", "path", path)
112+
113+ providers, err := client.GetProviders()
114+ if err != nil {
115+ slog.Error("Failed to fetch providers in background from Catwalk", "error", err)
116+ return
117+ }
118+ if len(providers) == 0 {
119+ slog.Error("Empty providers list from Catwalk")
120+ return
121+ }
122+ if err := saveProvidersInCache(path, providers); err != nil {
123+ slog.Error("Failed to update providers.json in background", "error", err)
124+ }
125+ }()
119126 }
120− if !exists {
121− err = fmt.Errorf("failed to load providers")
122− return
127+
128+ switch {
129+ case autoUpdateDisabled:
130+ slog.Warn("Providers auto-update is disabled")
131+
132+ if cacheExists {
133+ slog.Warn("Using locally cached providers")
134+ return loadProvidersFromCache(path)
135+ }
136+
137+ slog.Warn("Saving embedded providers to cache")
138+ providers := embedded.GetAll()
139+ if err := saveProvidersInCache(path, providers); err != nil {
140+ return nil, err
141+ }
142+ return providers, nil
143+
144+ case cacheExists && !cacheIsStale:
145+ slog.Info("Recent providers cache is available.", "path", path)
146+
147+ providers, err := loadProvidersFromCache(path)
148+ if err != nil {
149+ return nil, err
150+ }
151+ if len(providers) == 0 {
152+ return catwalkGetAndSave()
153+ }
154+ backgroundCacheUpdate()
155+ return providers, nil
156+
157+ default:
158+ slog.Info("Cache is not available or is stale. Fetching providers from Catwalk.", "path", path)
159+
160+ providers, err := catwalkGetAndSave()
161+ if err != nil {
162+ catwalkUrl := fmt.Sprintf("%s/providers", cmp.Or(os.Getenv("CATWALK_URL"), defaultCatwalkURL))
163+ return nil, fmt.Errorf("crush was unable to fetch an updated list of providers from %s. Consider setting CRUSH_DISABLE_PROVIDER_AUTO_UPDATE=1 to use embedded version from the time of this Crush release. %w", catwalkUrl, err)
164+ }
165+ return providers, nil
123166 }
124− slog.Info("Loading provider data from cache", "path", path)
125− providerList, err = loadProvidersFromCache(path)
126− return
127167}
128168
129169func isCacheStale(path string) (stale, exists bool) {