yay/pkg/metadata/metadata_aur.go

206 lines
4.5 KiB
Go
Raw Normal View History

2022-09-04 23:45:40 +02:00
package metadata
import (
2022-11-08 01:32:21 +01:00
"context"
2022-09-04 23:45:40 +02:00
"fmt"
2022-11-08 01:32:21 +01:00
"os"
"time"
2022-09-04 23:45:40 +02:00
"github.com/Jguer/aur"
"github.com/itchyny/gojq"
"github.com/ohler55/ojg/oj"
2022-11-08 01:32:21 +01:00
)
const (
cacheValidity = 1 * time.Hour
2022-09-04 23:45:40 +02:00
)
2022-11-15 16:22:57 +01:00
type AURCacheClient struct {
httpClient HTTPRequestDoer
cachePath string
DebugLoggerFn func(a ...interface{})
2022-09-04 23:45:40 +02:00
unmarshalledCache []interface{}
2022-11-08 01:32:21 +01:00
}
type AURQuery struct {
2022-11-13 23:53:37 +01:00
Needles []string
By aur.By
Contains bool // if true, search for packages containing the needle, not exact matches
2022-09-04 23:45:40 +02:00
}
2022-11-15 16:22:57 +01:00
// ClientOption allows setting custom parameters during construction.
type ClientOption func(*AURCacheClient) error
2022-11-15 16:22:57 +01:00
func NewAURCache(httpClient HTTPRequestDoer, cachePath string, opts ...ClientOption) (*AURCacheClient, error) {
return &AURCacheClient{
httpClient: httpClient,
cachePath: cachePath,
2022-09-04 23:45:40 +02:00
}, nil
}
// needsUpdate checks if cachepath is older than 24 hours.
2022-11-15 16:22:57 +01:00
func (a *AURCacheClient) needsUpdate() (bool, error) {
2022-11-08 01:32:21 +01:00
// check if cache is older than 24 hours
info, err := os.Stat(a.cachePath)
if err != nil {
2022-11-15 16:22:57 +01:00
if os.IsNotExist(err) {
return true, nil
}
2022-11-08 01:32:21 +01:00
return false, fmt.Errorf("unable to read cache: %w", err)
}
return info.ModTime().Before(time.Now().Add(-cacheValidity)), nil
}
// Get returns a list of packages that provide the given search term.
2022-11-15 16:22:57 +01:00
func (a *AURCacheClient) Get(ctx context.Context, query *AURQuery) ([]*aur.Pkg, error) {
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
}
func (a *AURCacheClient) cache(ctx context.Context) ([]interface{}, error) {
if a.unmarshalledCache != nil {
return a.unmarshalledCache, nil
}
2022-11-08 01:32:21 +01:00
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")
}
2022-11-15 16:22:57 +01:00
cache, makeErr := MakeCache(ctx, a.httpClient, a.cachePath)
if makeErr != nil {
2022-11-08 01:32:21 +01:00
return nil, makeErr
}
2022-11-15 16:22:57 +01:00
inputStruct, unmarshallErr := oj.Parse(cache)
2022-11-08 01:32:21 +01:00
if unmarshallErr != nil {
return nil, fmt.Errorf("aur metadata unable to parse cache: %w", unmarshallErr)
2022-11-08 01:32:21 +01:00
}
a.unmarshalledCache = inputStruct.([]interface{})
2022-11-15 16:22:57 +01:00
} else {
aurCache, err := ReadCache(a.cachePath)
if err != nil {
return nil, err
}
2022-11-08 01:32:21 +01:00
2022-11-15 16:22:57 +01:00
inputStruct, err := oj.Parse(aurCache)
if err != nil {
return nil, fmt.Errorf("aur metadata unable to parse cache: %w", err)
}
2022-11-08 01:32:21 +01:00
2022-11-15 16:22:57 +01:00
a.unmarshalledCache = inputStruct.([]interface{})
2022-11-08 01:32:21 +01:00
}
2022-11-15 16:22:57 +01:00
return a.unmarshalledCache, nil
2022-09-06 23:25:44 +02:00
}
2022-11-15 16:22:57 +01:00
func (a *AURCacheClient) gojqGetBatch(ctx context.Context, query *AURQuery) ([]*aur.Pkg, error) {
2022-11-08 01:32:21 +01:00
pattern := ".[] | select("
2022-11-13 17:47:19 +01:00
2022-11-08 01:32:21 +01:00
for i, searchTerm := range query.Needles {
if i != 0 {
pattern += ","
2022-11-08 01:32:21 +01:00
}
2022-11-13 17:47:19 +01:00
bys := toSearchBy(query.By)
for j, by := range bys {
2022-11-13 23:53:37 +01:00
if query.Contains {
2022-11-15 15:44:50 +01:00
pattern += fmt.Sprintf("(.%s // empty | test(%q))", by, searchTerm)
2022-11-13 23:53:37 +01:00
} else {
2022-11-15 15:44:50 +01:00
pattern += fmt.Sprintf("(.%s == %q)", by, searchTerm)
2022-11-13 23:53:37 +01:00
}
2022-11-13 17:47:19 +01:00
if j != len(bys)-1 {
pattern += ","
2022-11-08 01:32:21 +01:00
}
}
}
2022-09-04 23:45:40 +02:00
2022-11-08 01:32:21 +01:00
pattern += ")"
2022-09-04 23:45:40 +02:00
2022-11-13 23:53:37 +01:00
if a.DebugLoggerFn != nil {
a.DebugLoggerFn("AUR metadata query", pattern)
}
2022-11-08 01:32:21 +01:00
parsed, err := gojq.Parse(pattern)
if err != nil {
return nil, fmt.Errorf("unable to parse query: %w", err)
2022-11-08 01:32:21 +01:00
}
2022-09-04 23:45:40 +02:00
2022-11-15 16:22:57 +01:00
unmarshalledCache, errCache := a.cache(ctx)
if errCache != nil {
return nil, errCache
}
2022-11-08 01:32:21 +01:00
final := make([]*aur.Pkg, 0, len(query.Needles))
2022-11-15 16:22:57 +01:00
iter := parsed.RunWithContext(ctx, unmarshalledCache) // or query.RunWithContext
2022-09-04 23:45:40 +02:00
for pkgMap, ok := iter.Next(); ok; pkgMap, ok = iter.Next() {
if err, ok := pkgMap.(error); ok {
2022-11-08 01:32:21 +01:00
return nil, err
2022-09-04 23:45:40 +02:00
}
2022-11-08 01:32:21 +01:00
pkg := new(aur.Pkg)
bValue, err := gojq.Marshal(pkgMap)
2022-11-08 01:32:21 +01:00
if err != nil {
return nil, fmt.Errorf("unable to marshal aur package: %w", err)
}
errU := oj.Unmarshal(bValue, pkg)
if errU != nil {
return nil, fmt.Errorf("unable to unmarshal aur package: %w", errU)
2022-11-08 01:32:21 +01:00
}
final = append(final, pkg)
}
if a.DebugLoggerFn != nil {
2022-11-13 23:53:37 +01:00
a.DebugLoggerFn("AUR metadata query found", len(final))
2022-09-04 23:45:40 +02:00
}
2022-11-08 01:32:21 +01:00
2022-09-04 23:45:40 +02:00
return final, nil
}
2022-11-13 17:47:19 +01:00
func toSearchBy(by aur.By) []string {
switch by {
case aur.Name:
return []string{"Name"}
case aur.NameDesc:
return []string{"Name", "Description"}
case aur.Maintainer:
return []string{"Maintainer"}
case aur.Depends:
return []string{"Depends[]?"}
case aur.MakeDepends:
return []string{"MakeDepends[]?"}
case aur.OptDepends:
return []string{"OptDepends[]?"}
case aur.CheckDepends:
return []string{"CheckDepends[]?"}
case aur.None:
return []string{"Name", "Provides[]?"}
default:
panic("invalid By")
}
}