@@ -3,91 +3,86 @@ package services
33import (
44 "bbrew/internal/models"
55 "encoding/json"
6+ "io"
67 "net/http"
8+ "os"
79 "os/exec"
10+ "path/filepath"
811 "sort"
912 "strconv"
1013 "strings"
11- "sync"
1214)
1315
14- var prefixPathCache = make (map [string ]string )
16+ const FormulaeAPIURL = "https://formulae.brew.sh/api/formula.json"
17+ const AnalyticsAPIURL = "https://formulae.brew.sh/api/analytics/install-on-request/90d.json"
1518
1619type BrewServiceInterface interface {
17- GetPrefixPath (packageName string ) (path string , err error )
18- GetAllFormulae () (formulae * []models.Formula )
19- LoadAllFormulae () (err error )
20- GetCurrentBrewVersion () (version string , err error )
20+ GetPrefixPath () (path string )
21+ GetFormulae () (formulae * []models.Formula )
22+ SetupData (forceDownload bool ) (err error )
23+ GetBrewVersion () (version string , err error )
24+ UpdateHomebrew () error
2125}
2226
2327type BrewService struct {
24- cache sync. Mutex
28+ // Package lists
2529 all * []models.Formula
2630 installed * []models.Formula
2731 remote * []models.Formula
2832 analytics map [string ]models.AnalyticsItem
33+
34+ brewVersion string
35+ prefixPath string
2936}
3037
3138var NewBrewService = func () BrewServiceInterface {
3239 return & BrewService {
33- cache : sync.Mutex {},
3440 all : new ([]models.Formula ),
3541 installed : new ([]models.Formula ),
3642 remote : new ([]models.Formula ),
3743 }
3844}
3945
40- func (s * BrewService ) GetPrefixPath (packageName string ) (path string , err error ) {
41- s .cache .Lock ()
42- defer s .cache .Unlock ()
43-
44- var found bool
45- if path , found = prefixPathCache [packageName ]; found {
46- return path , nil
46+ func (s * BrewService ) GetPrefixPath () (path string ) {
47+ if s .prefixPath != "" {
48+ return s .prefixPath
4749 }
4850
49- cmd := exec .Command ("brew" , "--prefix" , packageName )
51+ cmd := exec .Command ("brew" , "--prefix" )
5052 output , err := cmd .Output ()
5153 if err != nil {
52- return "Unknown" , err
54+ s .prefixPath = "Unknown"
55+ return
5356 }
5457
55- path = strings .TrimSpace (string (output ))
56- prefixPathCache [packageName ] = path
57- return path , nil
58+ s .prefixPath = strings .TrimSpace (string (output ))
59+ return s .prefixPath
5860}
5961
60- func (s * BrewService ) GetAllFormulae () (formulae * []models.Formula ) {
61- return s .all
62- }
63-
64- func (s * BrewService ) LoadAllFormulae () (err error ) {
65- _ = s .loadInstalled ()
66- _ = s .loadRemote ()
67- _ = s .loadAnalytics ()
68-
62+ func (s * BrewService ) GetFormulae () (formulae * []models.Formula ) {
6963 packageMap := make (map [string ]models.Formula )
7064
71- // Add installed packages to the map
72- for _ , formula := range * s .installed {
73- packageMap [formula .Name ] = formula
74- }
75-
7665 // Add remote packages to the map if they don't already exist
7766 for _ , formula := range * s .remote {
7867 if _ , exists := packageMap [formula .Name ]; ! exists {
7968 packageMap [formula .Name ] = formula
8069 }
8170 }
8271
72+ // Add installed packages to the map
73+ for _ , formula := range * s .installed {
74+ packageMap [formula .Name ] = formula
75+ }
76+
8377 * s .all = make ([]models.Formula , 0 , len (packageMap ))
8478 for _ , formula := range packageMap {
85- // patch analytics info
79+ // Merge analytics data if available
8680 if a , exists := s .analytics [formula .Name ]; exists && a .Number > 0 {
8781 downloads , _ := strconv .Atoi (strings .ReplaceAll (a .Count , "," , "" ))
8882 formula .Analytics90dRank = a .Number
8983 formula .Analytics90dDownloads = downloads
9084 }
85+
9186 * s .all = append (* s .all , formula )
9287 }
9388
@@ -96,6 +91,22 @@ func (s *BrewService) LoadAllFormulae() (err error) {
9691 return (* s .all )[i ].Name < (* s .all )[j ].Name
9792 })
9893
94+ return s .all
95+ }
96+
97+ func (s * BrewService ) SetupData (forceDownload bool ) (err error ) {
98+ if err = s .loadInstalled (); err != nil {
99+ return err
100+ }
101+
102+ if err = s .loadRemote (forceDownload ); err != nil {
103+ return err
104+ }
105+
106+ if err = s .loadAnalytics (); err != nil {
107+ return err
108+ }
109+
99110 return nil
100111}
101112
@@ -120,24 +131,57 @@ func (s *BrewService) loadInstalled() (err error) {
120131 return nil
121132}
122133
123- func (s * BrewService ) loadRemote () (err error ) {
124- resp , err := http .Get ("https://formulae.brew.sh/api/formula.json" )
134+ func (s * BrewService ) loadRemote (forceDownload bool ) (err error ) {
135+ homeDir , err := os .UserHomeDir ()
136+ if err != nil {
137+ return err
138+ }
139+
140+ bbrewDir := filepath .Join (homeDir , ".bbrew" ) // TODO: Move to config
141+ formulaFile := filepath .Join (bbrewDir , "formula.json" )
142+ if _ , err := os .Stat (bbrewDir ); os .IsNotExist (err ) {
143+ if err := os .MkdirAll (bbrewDir , 0755 ); err != nil {
144+ return err
145+ }
146+ }
147+
148+ // Check if we should use the cached file
149+ if ! forceDownload {
150+ if _ , err := os .Stat (formulaFile ); err == nil {
151+ data , err := os .ReadFile (formulaFile )
152+ if err == nil {
153+ * s .remote = make ([]models.Formula , 0 )
154+ if err := json .Unmarshal (data , & s .remote ); err == nil {
155+ return nil
156+ }
157+ }
158+ }
159+ }
160+
161+ resp , err := http .Get (FormulaeAPIURL )
125162 if err != nil {
126163 return err
127164 }
128165 defer resp .Body .Close ()
129166
167+ body , err := io .ReadAll (resp .Body )
168+ if err != nil {
169+ return err
170+ }
171+
130172 * s .remote = make ([]models.Formula , 0 )
131- err = json .NewDecoder ( resp . Body ). Decode ( & s .remote )
173+ err = json .Unmarshal ( body , s .remote )
132174 if err != nil {
133175 return err
134176 }
135177
178+ // Cache the remote formulae data
179+ _ = os .WriteFile (formulaFile , body , 0600 )
136180 return nil
137181}
138182
139183func (s * BrewService ) loadAnalytics () (err error ) {
140- resp , err := http .Get ("https://formulae.brew.sh/api/analytics/install-on-request/90d.json" )
184+ resp , err := http .Get (AnalyticsAPIURL )
141185 if err != nil {
142186 return err
143187 }
@@ -155,16 +199,28 @@ func (s *BrewService) loadAnalytics() (err error) {
155199 }
156200
157201 s .analytics = analyticsByFormula
158-
159202 return nil
160203}
161204
162- func (s * BrewService ) GetCurrentBrewVersion () (version string , err error ) {
205+ func (s * BrewService ) GetBrewVersion () (version string , err error ) {
206+ if s .brewVersion != "" {
207+ return s .brewVersion , nil
208+ }
209+
163210 cmd := exec .Command ("brew" , "--version" )
164211 output , err := cmd .Output ()
165212 if err != nil {
166213 return "" , err
167214 }
168215
169- return strings .TrimSpace (string (output )), nil
216+ s .brewVersion = strings .TrimSpace (string (output ))
217+ return s .brewVersion , nil
218+ }
219+
220+ func (s * BrewService ) UpdateHomebrew () error {
221+ cmd := exec .Command ("brew" , "update" )
222+ if err := cmd .Run (); err != nil {
223+ return err
224+ }
225+ return nil
170226}
0 commit comments