Refactor fetch and pacman packages into subpackages

Split fetch into alpm and aur subpackages for better organization.
Rename state to log. Split pacman into read and sync subpackages.
Remove validation in favor of read.DBFreshness.
This commit is contained in:
AI Bot
2026-04-18 18:18:23 +02:00
committed by Riyyi
parent 88ff093365
commit 83de6acc74
10 changed files with 607 additions and 536 deletions
+137
View File
@@ -0,0 +1,137 @@
package alpm
import (
"fmt"
"os"
"time"
"github.com/Jguer/dyalpm"
)
var (
Root = "/"
PacmanState = "/var/lib/pacman"
)
type Handle struct {
handle dyalpm.Handle
localDB dyalpm.Database
syncDBs []dyalpm.Database
}
func New() (*Handle, error) {
start := time.Now()
fmt.Fprintf(os.Stderr, "[debug] alpm.New: starting...\n")
handle, err := dyalpm.Initialize(Root, PacmanState)
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] alpm.New: done (%.2fs)\n", time.Since(start).Seconds())
return &Handle{
handle: handle,
localDB: localDB,
syncDBs: syncDBs,
}, nil
}
func (h *Handle) Release() error {
if h.handle != nil {
h.handle.Release()
}
return 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 (h *Handle) LocalPackages() (map[string]dyalpm.Package, error) {
start := time.Now()
fmt.Fprintf(os.Stderr, "[debug] LocalPackages: starting...\n")
localPkgs := make(map[string]dyalpm.Package)
err := h.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] LocalPackages: done (%.2fs)\n", time.Since(start).Seconds())
return localPkgs, nil
}
func (h *Handle) SyncPackages(pkgNames []string) (map[string]dyalpm.Package, error) {
start := time.Now()
fmt.Fprintf(os.Stderr, "[debug] SyncPackages: starting...\n")
syncPkgs := make(map[string]dyalpm.Package)
pkgSet := make(map[string]bool)
for _, name := range pkgNames {
pkgSet[name] = true
}
for _, db := range h.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] SyncPackages: done (%.2fs)\n", time.Since(start).Seconds())
return syncPkgs, nil
}
+94
View File
@@ -0,0 +1,94 @@
package aur
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"time"
)
var AURInfoURL = "https://aur.archlinux.org/rpc?v=5&type=info"
type Package struct {
Name string `json:"Name"`
PackageBase string `json:"PackageBase"`
Version string `json:"Version"`
URL string `json:"URL"`
}
type Response struct {
Results []Package `json:"results"`
}
type Client struct {
cache map[string]Package
}
func New() *Client {
return &Client{
cache: make(map[string]Package),
}
}
func (c *Client) Fetch(packages []string) (map[string]Package, error) {
start := time.Now()
fmt.Fprintf(os.Stderr, "[debug] aur.Fetch: starting...\n")
result := make(map[string]Package)
if len(packages) == 0 {
return result, nil
}
var uncached []string
for _, pkg := range packages {
if _, ok := c.cache[pkg]; !ok {
uncached = append(uncached, pkg)
}
}
if len(uncached) == 0 {
fmt.Fprintf(os.Stderr, "[debug] aur.Fetch: done (cached) (%.2fs)\n", time.Since(start).Seconds())
for _, pkg := range packages {
result[pkg] = c.cache[pkg]
}
return result, nil
}
v := url.Values{}
for _, pkg := range packages {
v.Add("arg[]", pkg)
}
resp, err := http.Get(AURInfoURL + "&" + v.Encode())
if err != nil {
return result, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return result, err
}
var aurResp Response
if err := json.Unmarshal(body, &aurResp); err != nil {
return result, err
}
for _, r := range aurResp.Results {
c.cache[r.Name] = r
result[r.Name] = r
}
fmt.Fprintf(os.Stderr, "[debug] aur.Fetch: done (%.2fs)\n", time.Since(start).Seconds())
return result, nil
}
func (c *Client) Get(name string) (Package, bool) {
pkg, ok := c.cache[name]
return pkg, ok
}
+28 -238
View File
@@ -1,105 +1,55 @@
package fetch
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/exec"
"strings"
"time"
"github.com/Jguer/dyalpm"
"github.com/Riyyi/declpac/pkg/fetch/alpm"
"github.com/Riyyi/declpac/pkg/fetch/aur"
)
const (
Root = "/"
PacmanState = "/var/lib/pacman"
LockFile = PacmanState + "/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
AURInfo *aur.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"`
type Fetcher struct {
alpmHandle *alpm.Handle
aurClient *aur.Client
}
func New() (*Fetcher, error) {
start := time.Now()
fmt.Fprintf(os.Stderr, "[debug] Fetcher New: starting...\n")
fmt.Fprintf(os.Stderr, "[debug] fetch.Fetcher New: starting...\n")
handle, err := dyalpm.Initialize(Root, PacmanState)
alpmHandle, err := alpm.New()
if err != nil {
return nil, fmt.Errorf("failed to initialize alpm: %w", err)
return nil, err
}
localDB, err := handle.LocalDB()
if err != nil {
handle.Release()
return nil, fmt.Errorf("failed to get local database: %w", err)
}
aurClient := aur.New()
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())
fmt.Fprintf(os.Stderr, "[debug] fetch.Fetcher New: done (%.2fs)\n", time.Since(start).Seconds())
return &Fetcher{
aurCache: make(map[string]AURPackage),
handle: handle,
localDB: localDB,
syncDBs: syncDBs,
alpmHandle: alpmHandle,
aurClient: aurClient,
}, nil
}
func (f *Fetcher) Close() error {
if f.handle != nil {
f.handle.Release()
}
return nil
return f.alpmHandle.Release()
}
func (f *Fetcher) GetAURPackage(name string) (AURPackage, bool) {
pkg, ok := f.aurCache[name]
return pkg, ok
func (f *Fetcher) GetAURPackage(name string) (aur.Package, bool) {
return f.aurClient.Get(name)
}
func (f *Fetcher) BuildLocalPkgMap() (map[string]interface{}, error) {
localPkgs, err := f.buildLocalPkgMap()
localPkgs, err := f.alpmHandle.LocalPackages()
if err != nil {
return nil, err
}
@@ -110,106 +60,31 @@ func (f *Fetcher) BuildLocalPkgMap() (map[string]interface{}, error) {
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")
fmt.Fprintf(os.Stderr, "[debug] fetch.Resolve: starting...\n")
result := make(map[string]*PackageInfo)
for _, pkg := range packages {
result[pkg] = &PackageInfo{Name: pkg, Exists: false}
}
syncPkgs, err := f.checkSyncDBs(packages)
syncPkgs, err := f.alpmHandle.SyncPackages(packages)
if err != nil {
return nil, err
}
fmt.Fprintf(os.Stderr, "[debug] Resolve: sync db check done (%.2fs)\n", time.Since(start).Seconds())
fmt.Fprintf(os.Stderr, "[debug] fetch.Resolve: sync db check done (%.2fs)\n", time.Since(start).Seconds())
for pkg, syncPkg := range syncPkgs {
for pkg := range syncPkgs {
result[pkg].Exists = true
result[pkg].InAUR = false
result[pkg].syncPkg = syncPkg
}
localPkgs, err := f.buildLocalPkgMap()
localPkgs, err := f.alpmHandle.LocalPackages()
if err != nil {
return nil, err
}
fmt.Fprintf(os.Stderr, "[debug] Resolve: local pkgs built (%.2fs)\n", time.Since(start).Seconds())
fmt.Fprintf(os.Stderr, "[debug] fetch.Resolve: local pkgs built (%.2fs)\n", time.Since(start).Seconds())
for pkg := range localPkgs {
if info, ok := result[pkg]; ok {
@@ -225,7 +100,9 @@ func (f *Fetcher) Resolve(packages []string) (map[string]*PackageInfo, error) {
}
if len(notInSync) > 0 {
f.ensureAURCache(notInSync)
if _, err := f.aurClient.Fetch(notInSync); err != nil {
fmt.Fprintf(os.Stderr, "[debug] fetch.Resolve: aur fetch error: %v\n", err)
}
for _, pkg := range packages {
info := result[pkg]
@@ -233,7 +110,7 @@ func (f *Fetcher) Resolve(packages []string) (map[string]*PackageInfo, error) {
continue
}
if aurInfo, ok := f.aurCache[pkg]; ok {
if aurInfo, ok := f.aurClient.Get(pkg); ok {
info.InAUR = true
info.AURInfo = &aurInfo
continue
@@ -250,93 +127,6 @@ func (f *Fetcher) Resolve(packages []string) (map[string]*PackageInfo, error) {
}
}
fmt.Fprintf(os.Stderr, "[debug] Resolve: done (%.2fs)\n", time.Since(start).Seconds())
fmt.Fprintf(os.Stderr, "[debug] fetch.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
}
_, err := f.fetchAURInfo(uncached)
if err != nil {
fmt.Fprintf(os.Stderr, "[debug] ensureAURCache: fetch error: %v\n", err)
}
fmt.Fprintf(os.Stderr, "[debug] ensureAURCache: done (%.2fs)\n", time.Since(start).Seconds())
}
func (f *Fetcher) fetchAURInfo(packages []string) (map[string]AURPackage, error) {
start := time.Now()
fmt.Fprintf(os.Stderr, "[debug] fetchAURInfo: starting...\n")
result := make(map[string]AURPackage)
if len(packages) == 0 {
return result, nil
}
v := url.Values{}
for _, pkg := range packages {
v.Add("arg[]", pkg)
}
resp, err := http.Get(AURInfoURL + "&" + v.Encode())
if err != nil {
return result, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return result, err
}
var aurResp AURResponse
if err := json.Unmarshal(body, &aurResp); err != nil {
return result, err
}
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, nil
}
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
}