3 changed files with 400 additions and 402 deletions
@ -0,0 +1,345 @@
|
||||
package fetch |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"net/http" |
||||
"net/url" |
||||
"os" |
||||
"os/exec" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/Jguer/dyalpm" |
||||
) |
||||
|
||||
var ( |
||||
Root = "/" |
||||
LockFile = "/var/lib/pacman/db.lock" |
||||
AURInfoURL = "https://aur.archlinux.org/rpc?v=5&type=info" |
||||
) |
||||
|
||||
type Fetcher struct { |
||||
aurCache map[string]AURPackage |
||||
handle dyalpm.Handle |
||||
localDB dyalpm.Database |
||||
syncDBs []dyalpm.Database |
||||
} |
||||
|
||||
type PackageInfo struct { |
||||
Name string |
||||
InAUR bool |
||||
Exists bool |
||||
Installed bool |
||||
AURInfo *AURPackage |
||||
syncPkg dyalpm.Package |
||||
} |
||||
|
||||
type AURResponse struct { |
||||
Results []AURPackage `json:"results"` |
||||
} |
||||
|
||||
type AURPackage struct { |
||||
Name string `json:"Name"` |
||||
PackageBase string `json:"PackageBase"` |
||||
Version string `json:"Version"` |
||||
URL string `json:"URL"` |
||||
} |
||||
|
||||
func New() (*Fetcher, error) { |
||||
start := time.Now() |
||||
fmt.Fprintf(os.Stderr, "[debug] Fetcher New: starting...\n") |
||||
|
||||
handle, err := dyalpm.Initialize(Root, "/var/lib/pacman") |
||||
if err != nil { |
||||
return nil, fmt.Errorf("failed to initialize alpm: %w", err) |
||||
} |
||||
|
||||
localDB, err := handle.LocalDB() |
||||
if err != nil { |
||||
handle.Release() |
||||
return nil, fmt.Errorf("failed to get local database: %w", err) |
||||
} |
||||
|
||||
syncDBs, err := handle.SyncDBs() |
||||
if err != nil { |
||||
handle.Release() |
||||
return nil, fmt.Errorf("failed to get sync databases: %w", err) |
||||
} |
||||
|
||||
if len(syncDBs) == 0 { |
||||
syncDBs, err = registerSyncDBs(handle) |
||||
if err != nil { |
||||
handle.Release() |
||||
return nil, fmt.Errorf("failed to register sync databases: %w", err) |
||||
} |
||||
} |
||||
|
||||
fmt.Fprintf(os.Stderr, "[debug] Fetcher New: done (%.2fs)\n", time.Since(start).Seconds()) |
||||
return &Fetcher{ |
||||
aurCache: make(map[string]AURPackage), |
||||
handle: handle, |
||||
localDB: localDB, |
||||
syncDBs: syncDBs, |
||||
}, nil |
||||
} |
||||
|
||||
func (f *Fetcher) Close() error { |
||||
if f.handle != nil { |
||||
f.handle.Release() |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (f *Fetcher) GetAURPackage(name string) (AURPackage, bool) { |
||||
pkg, ok := f.aurCache[name] |
||||
return pkg, ok |
||||
} |
||||
|
||||
func (f *Fetcher) BuildLocalPkgMap() (map[string]interface{}, error) { |
||||
localPkgs, err := f.buildLocalPkgMap() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
result := make(map[string]interface{}) |
||||
for k, v := range localPkgs { |
||||
result[k] = v |
||||
} |
||||
return result, nil |
||||
} |
||||
|
||||
func registerSyncDBs(handle dyalpm.Handle) ([]dyalpm.Database, error) { |
||||
fmt.Fprintf(os.Stderr, "[debug] registerSyncDBs: starting...\n") |
||||
|
||||
repos := []string{"core", "extra", "multilib"} |
||||
var dbs []dyalpm.Database |
||||
|
||||
for _, repo := range repos { |
||||
db, err := handle.RegisterSyncDB(repo, 0) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
|
||||
count := 0 |
||||
db.PkgCache().ForEach(func(pkg dyalpm.Package) error { |
||||
count++ |
||||
return nil |
||||
}) |
||||
|
||||
if count > 0 { |
||||
dbs = append(dbs, db) |
||||
} |
||||
} |
||||
|
||||
fmt.Fprintf(os.Stderr, "[debug] registerSyncDBs: done (%d dbs)\n", len(dbs)) |
||||
return dbs, nil |
||||
} |
||||
|
||||
func (f *Fetcher) buildLocalPkgMap() (map[string]dyalpm.Package, error) { |
||||
start := time.Now() |
||||
fmt.Fprintf(os.Stderr, "[debug] buildLocalPkgMap: starting...\n") |
||||
|
||||
localPkgs := make(map[string]dyalpm.Package) |
||||
|
||||
err := f.localDB.PkgCache().ForEach(func(pkg dyalpm.Package) error { |
||||
localPkgs[pkg.Name()] = pkg |
||||
return nil |
||||
}) |
||||
|
||||
if err != nil { |
||||
return nil, fmt.Errorf("failed to iterate local package cache: %w", err) |
||||
} |
||||
|
||||
fmt.Fprintf(os.Stderr, "[debug] buildLocalPkgMap: done (%.2fs)\n", time.Since(start).Seconds()) |
||||
return localPkgs, nil |
||||
} |
||||
|
||||
func (f *Fetcher) checkSyncDBs(pkgNames []string) (map[string]dyalpm.Package, error) { |
||||
start := time.Now() |
||||
fmt.Fprintf(os.Stderr, "[debug] checkSyncDBs: starting...\n") |
||||
|
||||
syncPkgs := make(map[string]dyalpm.Package) |
||||
pkgSet := make(map[string]bool) |
||||
for _, name := range pkgNames { |
||||
pkgSet[name] = true |
||||
} |
||||
|
||||
for _, db := range f.syncDBs { |
||||
err := db.PkgCache().ForEach(func(pkg dyalpm.Package) error { |
||||
if pkgSet[pkg.Name()] { |
||||
if _, exists := syncPkgs[pkg.Name()]; !exists { |
||||
syncPkgs[pkg.Name()] = pkg |
||||
} |
||||
} |
||||
return nil |
||||
}) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("failed to iterate sync database %s: %w", db.Name(), err) |
||||
} |
||||
} |
||||
|
||||
fmt.Fprintf(os.Stderr, "[debug] checkSyncDBs: done (%.2fs)\n", time.Since(start).Seconds()) |
||||
return syncPkgs, nil |
||||
} |
||||
|
||||
func (f *Fetcher) Resolve(packages []string) (map[string]*PackageInfo, error) { |
||||
start := time.Now() |
||||
fmt.Fprintf(os.Stderr, "[debug] Resolve: starting...\n") |
||||
|
||||
result := make(map[string]*PackageInfo) |
||||
|
||||
localPkgs, err := f.buildLocalPkgMap() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
fmt.Fprintf(os.Stderr, "[debug] Resolve: local pkgs built (%.2fs)\n", time.Since(start).Seconds()) |
||||
|
||||
var notInLocal []string |
||||
for _, pkg := range packages { |
||||
if localPkg, ok := localPkgs[pkg]; ok { |
||||
result[pkg] = &PackageInfo{ |
||||
Name: pkg, |
||||
Exists: true, |
||||
InAUR: false, |
||||
Installed: true, |
||||
syncPkg: localPkg, |
||||
} |
||||
} else { |
||||
notInLocal = append(notInLocal, pkg) |
||||
} |
||||
} |
||||
|
||||
if len(notInLocal) > 0 { |
||||
syncPkgs, err := f.checkSyncDBs(notInLocal) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
fmt.Fprintf(os.Stderr, "[debug] Resolve: sync db checked (%.2fs)\n", time.Since(start).Seconds()) |
||||
|
||||
var notInSync []string |
||||
for _, pkg := range notInLocal { |
||||
if syncPkg, ok := syncPkgs[pkg]; ok { |
||||
result[pkg] = &PackageInfo{ |
||||
Name: pkg, |
||||
Exists: true, |
||||
InAUR: false, |
||||
Installed: false, |
||||
syncPkg: syncPkg, |
||||
} |
||||
} else { |
||||
notInSync = append(notInSync, pkg) |
||||
} |
||||
} |
||||
|
||||
if len(notInSync) > 0 { |
||||
f.ensureAURCache(notInSync) |
||||
fmt.Fprintf(os.Stderr, "[debug] Resolve: AUR cache ensured (%.2fs)\n", time.Since(start).Seconds()) |
||||
|
||||
var unfound []string |
||||
for _, pkg := range notInSync { |
||||
if aurInfo, ok := f.aurCache[pkg]; ok { |
||||
result[pkg] = &PackageInfo{ |
||||
Name: pkg, |
||||
Exists: true, |
||||
InAUR: true, |
||||
Installed: false, |
||||
AURInfo: &aurInfo, |
||||
} |
||||
} else { |
||||
unfound = append(unfound, pkg) |
||||
} |
||||
} |
||||
if len(unfound) > 0 { |
||||
return nil, fmt.Errorf("package(s) not found: %s", strings.Join(unfound, ", ")) |
||||
} |
||||
} |
||||
} |
||||
|
||||
fmt.Fprintf(os.Stderr, "[debug] Resolve: done (%.2fs)\n", time.Since(start).Seconds()) |
||||
return result, nil |
||||
} |
||||
|
||||
func (f *Fetcher) ensureAURCache(packages []string) { |
||||
start := time.Now() |
||||
fmt.Fprintf(os.Stderr, "[debug] ensureAURCache: starting...\n") |
||||
|
||||
if len(packages) == 0 { |
||||
return |
||||
} |
||||
|
||||
var uncached []string |
||||
for _, pkg := range packages { |
||||
if _, ok := f.aurCache[pkg]; !ok { |
||||
uncached = append(uncached, pkg) |
||||
} |
||||
} |
||||
|
||||
if len(uncached) == 0 { |
||||
fmt.Fprintf(os.Stderr, "[debug] ensureAURCache: done (%.2fs)\n", time.Since(start).Seconds()) |
||||
return |
||||
} |
||||
|
||||
f.fetchAURInfo(uncached) |
||||
fmt.Fprintf(os.Stderr, "[debug] ensureAURCache: done (%.2fs)\n", time.Since(start).Seconds()) |
||||
} |
||||
|
||||
func (f *Fetcher) fetchAURInfo(packages []string) map[string]AURPackage { |
||||
start := time.Now() |
||||
fmt.Fprintf(os.Stderr, "[debug] fetchAURInfo: starting...\n") |
||||
|
||||
result := make(map[string]AURPackage) |
||||
|
||||
if len(packages) == 0 { |
||||
return result |
||||
} |
||||
|
||||
v := url.Values{} |
||||
for _, pkg := range packages { |
||||
v.Add("arg[]", pkg) |
||||
} |
||||
|
||||
resp, err := http.Get(AURInfoURL + "&" + v.Encode()) |
||||
if err != nil { |
||||
return result |
||||
} |
||||
defer resp.Body.Close() |
||||
|
||||
body, err := io.ReadAll(resp.Body) |
||||
if err != nil { |
||||
return result |
||||
} |
||||
|
||||
var aurResp AURResponse |
||||
if err := json.Unmarshal(body, &aurResp); err != nil { |
||||
return result |
||||
} |
||||
|
||||
for _, r := range aurResp.Results { |
||||
f.aurCache[r.Name] = r |
||||
result[r.Name] = r |
||||
} |
||||
|
||||
fmt.Fprintf(os.Stderr, "[debug] fetchAURInfo: done (%.2fs)\n", time.Since(start).Seconds()) |
||||
return result |
||||
} |
||||
|
||||
func (f *Fetcher) ListOrphans() ([]string, error) { |
||||
start := time.Now() |
||||
fmt.Fprintf(os.Stderr, "[debug] ListOrphans: starting...\n") |
||||
|
||||
cmd := exec.Command("pacman", "-Qdtq") |
||||
orphans, err := cmd.Output() |
||||
if err != nil { |
||||
return nil, nil |
||||
} |
||||
|
||||
list := strings.TrimSpace(string(orphans)) |
||||
if list == "" { |
||||
fmt.Fprintf(os.Stderr, "[debug] ListOrphans: done (%.2fs)\n", time.Since(start).Seconds()) |
||||
return nil, nil |
||||
} |
||||
|
||||
fmt.Fprintf(os.Stderr, "[debug] ListOrphans: done (%.2fs)\n", time.Since(start).Seconds()) |
||||
return strings.Split(list, "\n"), nil |
||||
} |
||||
Loading…
Reference in new issue