1package config
 2
 3import (
 4	"encoding/json"
 5	"os"
 6	"path/filepath"
 7	"runtime"
 8	"sync"
 9
10	"github.com/charmbracelet/crush/internal/fur/client"
11	"github.com/charmbracelet/crush/internal/fur/provider"
12)
13
14type ProviderClient interface {
15	GetProviders() ([]provider.Provider, error)
16}
17
18var (
19	providerOnce sync.Once
20	providerList []provider.Provider
21)
22
23// file to cache provider data
24func providerCacheFileData() string {
25	xdgDataHome := os.Getenv("XDG_DATA_HOME")
26	if xdgDataHome != "" {
27		return filepath.Join(xdgDataHome, appName)
28	}
29
30	// return the path to the main data directory
31	// for windows, it should be in `%LOCALAPPDATA%/crush/`
32	// for linux and macOS, it should be in `$HOME/.local/share/crush/`
33	if runtime.GOOS == "windows" {
34		localAppData := os.Getenv("LOCALAPPDATA")
35		if localAppData == "" {
36			localAppData = filepath.Join(os.Getenv("USERPROFILE"), "AppData", "Local")
37		}
38		return filepath.Join(localAppData, appName)
39	}
40
41	return filepath.Join(os.Getenv("HOME"), ".local", "share", appName, "providers.json")
42}
43
44func 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	}
49
50	data, err := json.MarshalIndent(providers, "", "  ")
51	if err != nil {
52		return err
53	}
54
55	return os.WriteFile(path, data, 0o644)
56}
57
58func loadProvidersFromCache(path string) ([]provider.Provider, error) {
59	data, err := os.ReadFile(path)
60	if err != nil {
61		return nil, err
62	}
63
64	var providers []provider.Provider
65	err = json.Unmarshal(data, &providers)
66	return providers, err
67}
68
69func 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		}
81	}
82	return providers, nil
83}
84
85func Providers() ([]provider.Provider, error) {
86	return LoadProviders(client.New())
87}
88
89func LoadProviders(client ProviderClient) ([]provider.Provider, error) {
90	var err error
91	providerOnce.Do(func() {
92		providerList, err = loadProviders(providerCacheFileData(), client)
93	})
94	if err != nil {
95		return nil, err
96	}
97	return providerList, nil
98}