metadata_aur.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. package metadata
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "log"
  7. "os"
  8. "time"
  9. "github.com/Jguer/aur"
  10. "github.com/itchyny/gojq"
  11. "github.com/ohler55/ojg/oj"
  12. )
  13. const (
  14. searchCacheCap = 300
  15. cacheValidity = 1 * time.Hour
  16. )
  17. type AURCache struct {
  18. cache []byte
  19. searchCache map[string][]*aur.Pkg
  20. cachePath string
  21. unmarshalledCache []interface{}
  22. cacheHits int
  23. gojqCode *gojq.Code
  24. DebugLoggerFn func(a ...interface{})
  25. }
  26. type AURQuery struct {
  27. ByProvides bool // Returns multiple results of different bases
  28. ByBase bool // Returns multiple results of the same base
  29. ByName bool // Returns only 1 or 0 results
  30. Needles []string
  31. }
  32. func NewAURCache(cachePath string) (*AURCache, error) {
  33. aurCache, err := MakeOrReadCache(cachePath)
  34. if err != nil {
  35. return nil, err
  36. }
  37. inputStruct, err := oj.Parse(aurCache)
  38. return &AURCache{
  39. cache: aurCache,
  40. cachePath: cachePath,
  41. searchCache: make(map[string][]*aur.Pkg, searchCacheCap),
  42. unmarshalledCache: inputStruct.([]interface{}),
  43. gojqCode: makeGoJQ(),
  44. }, nil
  45. }
  46. // needsUpdate checks if cachepath is older than 24 hours
  47. func (a *AURCache) needsUpdate() (bool, error) {
  48. // check if cache is older than 24 hours
  49. info, err := os.Stat(a.cachePath)
  50. if err != nil {
  51. return false, fmt.Errorf("unable to read cache: %w", err)
  52. }
  53. return info.ModTime().Before(time.Now().Add(-cacheValidity)), nil
  54. }
  55. func (a *AURCache) cacheKey(needle string, byProvides, byBase, byName bool) string {
  56. return fmt.Sprintf("%s-%v-%v-%v", needle, byProvides, byBase, byName)
  57. }
  58. func (a *AURCache) DebugInfo() {
  59. fmt.Println("Byte Cache", len(a.cache))
  60. fmt.Println("Entries Cached", len(a.searchCache))
  61. fmt.Println("Cache Hits", a.cacheHits)
  62. }
  63. func (a *AURCache) SetProvideCache(needle string, pkgs []*aur.Pkg) {
  64. a.searchCache[needle] = pkgs
  65. }
  66. // Get returns a list of packages that provide the given search term.
  67. func (a *AURCache) Get(ctx context.Context, query *AURQuery) ([]*aur.Pkg, error) {
  68. update, err := a.needsUpdate()
  69. if err != nil {
  70. return nil, err
  71. }
  72. if update {
  73. if a.DebugLoggerFn != nil {
  74. a.DebugLoggerFn("AUR Cache is out of date, updating")
  75. }
  76. var makeErr error
  77. if a.cache, makeErr = MakeCache(a.cachePath); makeErr != nil {
  78. return nil, makeErr
  79. }
  80. inputStruct, unmarshallErr := oj.Parse(a.cache)
  81. if unmarshallErr != nil {
  82. return nil, unmarshallErr
  83. }
  84. a.unmarshalledCache = inputStruct.([]interface{})
  85. }
  86. found := make([]*aur.Pkg, 0, len(query.Needles))
  87. if len(query.Needles) == 0 {
  88. return found, nil
  89. }
  90. iterFound, errNeedle := a.gojqGetBatch(ctx, query)
  91. if errNeedle != nil {
  92. return nil, errNeedle
  93. }
  94. found = append(found, iterFound...)
  95. return found, nil
  96. }
  97. // Get returns a list of packages that provide the given search term
  98. func (a *AURCache) FindPackage(ctx context.Context, needle string) ([]*aur.Pkg, error) {
  99. cacheKey := a.cacheKey(needle, true, true, true)
  100. if pkgs, ok := a.searchCache[cacheKey]; ok {
  101. a.cacheHits++
  102. return pkgs, nil
  103. }
  104. final, error := a.gojqGet(ctx, needle)
  105. if error != nil {
  106. return nil, error
  107. }
  108. a.searchCache[cacheKey] = final
  109. return final, nil
  110. }
  111. func (a *AURCache) gojqGetBatch(ctx context.Context, query *AURQuery) ([]*aur.Pkg, error) {
  112. pattern := ".[] | select("
  113. for i, searchTerm := range query.Needles {
  114. if i != 0 {
  115. pattern += " or "
  116. }
  117. if query.ByName {
  118. pattern += fmt.Sprintf("(.Name == \"%s\")", searchTerm)
  119. if query.ByBase || query.ByProvides {
  120. pattern += " or "
  121. }
  122. }
  123. if query.ByBase {
  124. pattern += fmt.Sprintf("(.PackageBase == \"%s\")", searchTerm)
  125. if query.ByProvides {
  126. pattern += " or "
  127. }
  128. }
  129. if query.ByProvides {
  130. pattern += fmt.Sprintf("(.Provides[]? == \"%s\")", searchTerm)
  131. }
  132. }
  133. pattern += ")"
  134. parsed, err := gojq.Parse(pattern)
  135. if err != nil {
  136. log.Fatalln(err)
  137. }
  138. final := make([]*aur.Pkg, 0, len(query.Needles))
  139. iter := parsed.RunWithContext(ctx, a.unmarshalledCache) // or query.RunWithContext
  140. for v, ok := iter.Next(); ok; v, ok = iter.Next() {
  141. if err, ok := v.(error); ok {
  142. return nil, err
  143. }
  144. pkg := new(aur.Pkg)
  145. bValue, err := gojq.Marshal(v)
  146. if err != nil {
  147. log.Fatalln(err)
  148. }
  149. oj.Unmarshal(bValue, pkg)
  150. final = append(final, pkg)
  151. }
  152. if a.DebugLoggerFn != nil {
  153. a.DebugLoggerFn("AUR Query", pattern, "Found", len(final))
  154. }
  155. return final, nil
  156. }
  157. func (a *AURCache) gojqGet(ctx context.Context, searchTerm string) ([]*aur.Pkg, error) {
  158. final := make([]*aur.Pkg, 0, 1)
  159. iter := a.gojqCode.RunWithContext(ctx, a.unmarshalledCache, searchTerm) // or query.RunWithContext
  160. for v, ok := iter.Next(); ok; v, ok = iter.Next() {
  161. if err, ok := v.(error); ok {
  162. return nil, err
  163. }
  164. pkg := &aur.Pkg{}
  165. bValue, err := gojq.Marshal(v)
  166. if err != nil {
  167. log.Fatalln(err)
  168. }
  169. json.Unmarshal(bValue, pkg)
  170. final = append(final, pkg)
  171. }
  172. return final, nil
  173. }
  174. func makeGoJQ() *gojq.Code {
  175. // pattern := ".[] | select((.PackageBase == $x) or (.Name == $x) or (.Provides[]? == ($x)))"
  176. pattern := ".[] | select((.Name == $x) or (.Provides[]? == ($x)))"
  177. query, err := gojq.Parse(pattern)
  178. if err != nil {
  179. log.Fatalln(err)
  180. }
  181. compiled, err := gojq.Compile(query, gojq.WithVariables([]string{"$x"}))
  182. if err != nil {
  183. log.Fatalln(err)
  184. }
  185. return compiled
  186. }