123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- package metadata
- import (
- "context"
- "encoding/json"
- "fmt"
- "log"
- "os"
- "time"
- "github.com/Jguer/aur"
- "github.com/itchyny/gojq"
- "github.com/ohler55/ojg/oj"
- )
- const (
- searchCacheCap = 300
- cacheValidity = 1 * time.Hour
- )
- type AURCache struct {
- cache []byte
- searchCache map[string][]*aur.Pkg
- cachePath string
- unmarshalledCache []interface{}
- cacheHits int
- gojqCode *gojq.Code
- DebugLoggerFn func(a ...interface{})
- }
- type AURQuery struct {
- ByProvides bool // Returns multiple results of different bases
- ByBase bool // Returns multiple results of the same base
- ByName bool // Returns only 1 or 0 results
- Needles []string
- }
- func NewAURCache(cachePath string) (*AURCache, error) {
- aurCache, err := MakeOrReadCache(cachePath)
- if err != nil {
- return nil, err
- }
- inputStruct, err := oj.Parse(aurCache)
- return &AURCache{
- cache: aurCache,
- cachePath: cachePath,
- searchCache: make(map[string][]*aur.Pkg, searchCacheCap),
- unmarshalledCache: inputStruct.([]interface{}),
- gojqCode: makeGoJQ(),
- }, nil
- }
- // needsUpdate checks if cachepath is older than 24 hours
- func (a *AURCache) needsUpdate() (bool, error) {
- // check if cache is older than 24 hours
- info, err := os.Stat(a.cachePath)
- if err != nil {
- return false, fmt.Errorf("unable to read cache: %w", err)
- }
- return info.ModTime().Before(time.Now().Add(-cacheValidity)), nil
- }
- func (a *AURCache) cacheKey(needle string, byProvides, byBase, byName bool) string {
- return fmt.Sprintf("%s-%v-%v-%v", needle, byProvides, byBase, byName)
- }
- func (a *AURCache) DebugInfo() {
- fmt.Println("Byte Cache", len(a.cache))
- fmt.Println("Entries Cached", len(a.searchCache))
- fmt.Println("Cache Hits", a.cacheHits)
- }
- func (a *AURCache) SetProvideCache(needle string, pkgs []*aur.Pkg) {
- a.searchCache[needle] = pkgs
- }
- // Get returns a list of packages that provide the given search term.
- func (a *AURCache) Get(ctx context.Context, query *AURQuery) ([]*aur.Pkg, error) {
- update, err := a.needsUpdate()
- if err != nil {
- return nil, err
- }
- if update {
- if a.DebugLoggerFn != nil {
- a.DebugLoggerFn("AUR Cache is out of date, updating")
- }
- var makeErr error
- if a.cache, makeErr = MakeCache(a.cachePath); makeErr != nil {
- return nil, makeErr
- }
- inputStruct, unmarshallErr := oj.Parse(a.cache)
- if unmarshallErr != nil {
- return nil, unmarshallErr
- }
- a.unmarshalledCache = inputStruct.([]interface{})
- }
- found := make([]*aur.Pkg, 0, len(query.Needles))
- if len(query.Needles) == 0 {
- return found, nil
- }
- iterFound, errNeedle := a.gojqGetBatch(ctx, query)
- if errNeedle != nil {
- return nil, errNeedle
- }
- found = append(found, iterFound...)
- return found, nil
- }
- // Get returns a list of packages that provide the given search term
- func (a *AURCache) FindPackage(ctx context.Context, needle string) ([]*aur.Pkg, error) {
- cacheKey := a.cacheKey(needle, true, true, true)
- if pkgs, ok := a.searchCache[cacheKey]; ok {
- a.cacheHits++
- return pkgs, nil
- }
- final, error := a.gojqGet(ctx, needle)
- if error != nil {
- return nil, error
- }
- a.searchCache[cacheKey] = final
- return final, nil
- }
- func (a *AURCache) gojqGetBatch(ctx context.Context, query *AURQuery) ([]*aur.Pkg, error) {
- pattern := ".[] | select("
- for i, searchTerm := range query.Needles {
- if i != 0 {
- pattern += " or "
- }
- if query.ByName {
- pattern += fmt.Sprintf("(.Name == \"%s\")", searchTerm)
- if query.ByBase || query.ByProvides {
- pattern += " or "
- }
- }
- if query.ByBase {
- pattern += fmt.Sprintf("(.PackageBase == \"%s\")", searchTerm)
- if query.ByProvides {
- pattern += " or "
- }
- }
- if query.ByProvides {
- pattern += fmt.Sprintf("(.Provides[]? == \"%s\")", searchTerm)
- }
- }
- pattern += ")"
- parsed, err := gojq.Parse(pattern)
- if err != nil {
- log.Fatalln(err)
- }
- final := make([]*aur.Pkg, 0, len(query.Needles))
- iter := parsed.RunWithContext(ctx, a.unmarshalledCache) // or query.RunWithContext
- for v, ok := iter.Next(); ok; v, ok = iter.Next() {
- if err, ok := v.(error); ok {
- return nil, err
- }
- pkg := new(aur.Pkg)
- bValue, err := gojq.Marshal(v)
- if err != nil {
- log.Fatalln(err)
- }
- oj.Unmarshal(bValue, pkg)
- final = append(final, pkg)
- }
- if a.DebugLoggerFn != nil {
- a.DebugLoggerFn("AUR Query", pattern, "Found", len(final))
- }
- return final, nil
- }
- func (a *AURCache) gojqGet(ctx context.Context, searchTerm string) ([]*aur.Pkg, error) {
- final := make([]*aur.Pkg, 0, 1)
- iter := a.gojqCode.RunWithContext(ctx, a.unmarshalledCache, searchTerm) // or query.RunWithContext
- for v, ok := iter.Next(); ok; v, ok = iter.Next() {
- if err, ok := v.(error); ok {
- return nil, err
- }
- pkg := &aur.Pkg{}
- bValue, err := gojq.Marshal(v)
- if err != nil {
- log.Fatalln(err)
- }
- json.Unmarshal(bValue, pkg)
- final = append(final, pkg)
- }
- return final, nil
- }
- func makeGoJQ() *gojq.Code {
- // pattern := ".[] | select((.PackageBase == $x) or (.Name == $x) or (.Provides[]? == ($x)))"
- pattern := ".[] | select((.Name == $x) or (.Provides[]? == ($x)))"
- query, err := gojq.Parse(pattern)
- if err != nil {
- log.Fatalln(err)
- }
- compiled, err := gojq.Compile(query, gojq.WithVariables([]string{"$x"}))
- if err != nil {
- log.Fatalln(err)
- }
- return compiled
- }
|