From 2bead6af27edd611c9d2c212966a9ac4a73b8b1b Mon Sep 17 00:00:00 2001 From: AI Bot Date: Tue, 14 Apr 2026 22:37:48 +0200 Subject: [PATCH] Refactor pkg into modular packages --- .../changes/refactor-modularize-pkg/tasks.md | 38 +- pkg/fetch/fetch.go | 345 ++++++++++++++ pkg/pacman/pacman.go | 419 ++---------------- 3 files changed, 400 insertions(+), 402 deletions(-) create mode 100644 pkg/fetch/fetch.go diff --git a/openspec/changes/refactor-modularize-pkg/tasks.md b/openspec/changes/refactor-modularize-pkg/tasks.md index 19deb21..d28d044 100644 --- a/openspec/changes/refactor-modularize-pkg/tasks.md +++ b/openspec/changes/refactor-modularize-pkg/tasks.md @@ -2,37 +2,37 @@ ## Phase 1: Create pkg/fetch -- [ ] 1.1 Create `pkg/fetch/fetch.go` -- [ ] 1.2 Move `AURResponse`, `AURPackage`, `PackageInfo` structs to fetch -- [ ] 1.3 Move `buildLocalPkgMap()` to fetch as `Fetcher.buildLocalPkgMap()` -- [ ] 1.4 Move `checkSyncDBs()` to fetch as `Fetcher.checkSyncDBs()` -- [ ] 1.5 Move `resolvePackages()` to fetch as `Fetcher.Resolve()` -- [ ] 1.6 Move AUR cache methods (`ensureAURCache`, `fetchAURInfo`) to fetch -- [ ] 1.7 Add `New()` and `Close()` to Fetcher -- [ ] 1.8 Add `ListOrphans()` to Fetcher +- [x] 1.1 Create `pkg/fetch/fetch.go` +- [x] 1.2 Move `AURResponse`, `AURPackage`, `PackageInfo` structs to fetch +- [x] 1.3 Move `buildLocalPkgMap()` to fetch as `Fetcher.buildLocalPkgMap()` +- [x] 1.4 Move `checkSyncDBs()` to fetch as `Fetcher.checkSyncDBs()` +- [x] 1.5 Move `resolvePackages()` to fetch as `Fetcher.Resolve()` +- [x] 1.6 Move AUR cache methods (`ensureAURCache`, `fetchAURInfo`) to fetch +- [x] 1.7 Add `New()` and `Close()` to Fetcher +- [x] 1.8 Add `ListOrphans()` to Fetcher ## Phase 2: Refactor pkg/pacman -- [ ] 2.1 Remove from pacman.go (now in fetch): +- [x] 2.1 Remove from pacman.go (now in fetch): - `buildLocalPkgMap()` - `checkSyncDBs()` - `resolvePackages()` - `ensureAURCache()` - `fetchAURInfo()` - `AURResponse`, `AURPackage`, `PackageInfo` structs -- [ ] 2.2 Remove `IsDBFresh()` and `SyncDB()` (use validation instead) -- [ ] 2.3 Update imports in pacman.go to include fetch package -- [ ] 2.4 Update `Sync()` to use `fetch.Fetcher` for resolution -- [ ] 2.5 Update `DryRun()` to call `fetcher.ListOrphans()` instead of duplicate call -- [ ] 2.6 Update `CleanupOrphans()` to call `fetcher.ListOrphans()` instead of duplicate call +- [x] 2.2 Remove `IsDBFresh()` and `SyncDB()` (use validation instead) +- [x] 2.3 Update imports in pacman.go to include fetch package +- [x] 2.4 Update `Sync()` to use `fetch.Fetcher` for resolution +- [x] 2.5 Update `DryRun()` to call `fetcher.ListOrphans()` instead of duplicate call +- [x] 2.6 Update `CleanupOrphans()` to call `fetcher.ListOrphans()` instead of duplicate call ## Phase 3: Clean Up Validation -- [ ] 3.1 Keep `validation.CheckDBFreshness()` as-is -- [ ] 3.2 Remove any remaining DB freshness duplication +- [x] 3.1 Keep `validation.CheckDBFreshness()` as-is +- [x] 3.2 Remove any remaining DB freshness duplication ## Phase 4: Verify -- [ ] 4.1 Run tests (if any exist) -- [ ] 4.2 Build: `go build ./...` -- [ ] 4.3 Verify CLI still works: test dry-run, sync, orphan cleanup \ No newline at end of file +- [x] 4.1 Run tests (if any exist) +- [x] 4.2 Build: `go build ./...` +- [x] 4.3 Verify CLI still works: test dry-run, sync, orphan cleanup \ No newline at end of file diff --git a/pkg/fetch/fetch.go b/pkg/fetch/fetch.go new file mode 100644 index 0000000..54e1287 --- /dev/null +++ b/pkg/fetch/fetch.go @@ -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 +} diff --git a/pkg/pacman/pacman.go b/pkg/pacman/pacman.go index 16e470a..3ec5d57 100644 --- a/pkg/pacman/pacman.go +++ b/pkg/pacman/pacman.go @@ -1,278 +1,19 @@ package pacman import ( - "encoding/json" "fmt" - "io" - "net/http" - "net/url" "os" "os/exec" "regexp" "strings" "time" - "github.com/Jguer/dyalpm" + "github.com/Riyyi/declpac/pkg/fetch" "github.com/Riyyi/declpac/pkg/output" + "github.com/Riyyi/declpac/pkg/validation" ) -var ( - Root = "/" - LockFile = "/var/lib/pacman/db.lock" - AURInfoURL = "https://aur.archlinux.org/rpc?v=5&type=info" -) - -type Pac struct { - aurCache map[string]AURPackage - handle dyalpm.Handle - localDB dyalpm.Database - syncDBs []dyalpm.Database -} - -func New() (*Pac, error) { - start := time.Now() - fmt.Fprintf(os.Stderr, "[debug] 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] New: done (%.2fs)\n", time.Since(start).Seconds()) - return &Pac{ - aurCache: make(map[string]AURPackage), - handle: handle, - localDB: localDB, - syncDBs: syncDBs, - }, nil -} - -func (p *Pac) Close() error { - if p.handle != nil { - p.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 -} - -type PackageInfo struct { - Name string - InAUR bool - Exists bool - Installed bool - AURInfo *AURPackage - syncPkg dyalpm.Package -} - -func (p *Pac) buildLocalPkgMap() (map[string]dyalpm.Package, error) { - start := time.Now() - fmt.Fprintf(os.Stderr, "[debug] buildLocalPkgMap: starting...\n") - - localPkgs := make(map[string]dyalpm.Package) - - err := p.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 (p *Pac) 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 p.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 (p *Pac) resolvePackages(packages []string) (map[string]*PackageInfo, error) { - start := time.Now() - fmt.Fprintf(os.Stderr, "[debug] resolvePackages: starting...\n") - - result := make(map[string]*PackageInfo) - - localPkgs, err := p.buildLocalPkgMap() - if err != nil { - return nil, err - } - fmt.Fprintf(os.Stderr, "[debug] resolvePackages: 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 := p.checkSyncDBs(notInLocal) - if err != nil { - return nil, err - } - fmt.Fprintf(os.Stderr, "[debug] resolvePackages: 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 { - p.ensureAURCache(notInSync) - fmt.Fprintf(os.Stderr, "[debug] resolvePackages: AUR cache ensured (%.2fs)\n", time.Since(start).Seconds()) - - var unfound []string - for _, pkg := range notInSync { - if aurInfo, ok := p.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] resolvePackages: done (%.2fs)\n", time.Since(start).Seconds()) - return result, nil -} - -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 (p *Pac) IsDBFresh() (bool, error) { - start := time.Now() - fmt.Fprintf(os.Stderr, "[debug] IsDBFresh: starting...\n") - - info, err := os.Stat(LockFile) - if err != nil { - return false, nil - } - - age := time.Since(info.ModTime()) - fmt.Fprintf(os.Stderr, "[debug] IsDBFresh: done (%.2fs)\n", time.Since(start).Seconds()) - return age < 24*time.Hour, nil -} - -func (p *Pac) SyncDB() error { - start := time.Now() - fmt.Fprintf(os.Stderr, "[debug] SyncDB: starting...\n") - - cmd := exec.Command("pacman", "-Syy") - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() - - fmt.Fprintf(os.Stderr, "[debug] SyncDB: done (%.2fs)\n", time.Since(start).Seconds()) - return err -} - -func (p *Pac) MarkAllAsDeps() error { +func MarkAllAsDeps() error { start := time.Now() fmt.Fprintf(os.Stderr, "[debug] MarkAllAsDeps: starting...\n") @@ -285,7 +26,7 @@ func (p *Pac) MarkAllAsDeps() error { return err } -func (p *Pac) MarkAsExplicit(packages []string) error { +func MarkAsExplicit(packages []string) error { if len(packages) == 0 { return nil } @@ -311,24 +52,20 @@ func Sync(packages []string) (*output.Result, error) { return nil, err } - p, err := New() - if err != nil { + if err := validation.CheckDBFreshness(); err != nil { return nil, err } - defer p.Close() - fmt.Fprintf(os.Stderr, "[debug] Sync: initialized pacman (%.2fs)\n", time.Since(start).Seconds()) - - fresh, err := p.IsDBFresh() - if err != nil || !fresh { - fmt.Fprintf(os.Stderr, "[debug] Sync: syncing database...\n") - if err := p.SyncDB(); err != nil { - return nil, fmt.Errorf("failed to sync database: %w", err) - } - fmt.Fprintf(os.Stderr, "[debug] Sync: database synced (%.2fs)\n", time.Since(start).Seconds()) + fmt.Fprintf(os.Stderr, "[debug] Sync: database fresh (%.2fs)\n", time.Since(start).Seconds()) + + f, err := fetch.New() + if err != nil { + return nil, err } + defer f.Close() + fmt.Fprintf(os.Stderr, "[debug] Sync: initialized fetcher (%.2fs)\n", time.Since(start).Seconds()) fmt.Fprintf(os.Stderr, "[debug] Sync: categorizing packages...\n") - pacmanPkgs, aurPkgs, err := p.categorizePackages(packages) + pacmanPkgs, aurPkgs, err := categorizePackages(f, packages) if err != nil { return nil, err } @@ -336,7 +73,7 @@ func Sync(packages []string) (*output.Result, error) { if len(pacmanPkgs) > 0 { fmt.Fprintf(os.Stderr, "[debug] Sync: syncing %d pacman packages...\n", len(pacmanPkgs)) - _, err = p.SyncPackages(pacmanPkgs) + _, err = SyncPackages(pacmanPkgs) if err != nil { return nil, err } @@ -345,25 +82,25 @@ func Sync(packages []string) (*output.Result, error) { for _, pkg := range aurPkgs { fmt.Fprintf(os.Stderr, "[debug] Sync: installing AUR package %s...\n", pkg) - if err := p.InstallAUR(pkg); err != nil { + if err := InstallAUR(f, pkg); err != nil { return nil, err } fmt.Fprintf(os.Stderr, "[debug] Sync: AUR package %s installed (%.2fs)\n", pkg, time.Since(start).Seconds()) } fmt.Fprintf(os.Stderr, "[debug] Sync: marking all as deps...\n") - if err := p.MarkAllAsDeps(); err != nil { + if err := MarkAllAsDeps(); err != nil { fmt.Fprintf(os.Stderr, "warning: could not mark all as deps: %v\n", err) } fmt.Fprintf(os.Stderr, "[debug] Sync: all marked as deps (%.2fs)\n", time.Since(start).Seconds()) fmt.Fprintf(os.Stderr, "[debug] Sync: marking state packages as explicit...\n") - if err := p.MarkAsExplicit(packages); err != nil { + if err := MarkAsExplicit(packages); err != nil { fmt.Fprintf(os.Stderr, "warning: could not mark state packages as explicit: %v\n", err) } fmt.Fprintf(os.Stderr, "[debug] Sync: state packages marked as explicit (%.2fs)\n", time.Since(start).Seconds()) - removed, err := p.CleanupOrphans() + removed, err := CleanupOrphans() if err != nil { return nil, err } @@ -378,11 +115,11 @@ func Sync(packages []string) (*output.Result, error) { }, nil } -func (p *Pac) categorizePackages(packages []string) (pacmanPkgs, aurPkgs []string, err error) { +func categorizePackages(f *fetch.Fetcher, packages []string) (pacmanPkgs, aurPkgs []string, err error) { start := time.Now() fmt.Fprintf(os.Stderr, "[debug] categorizePackages: starting...\n") - resolved, err := p.resolvePackages(packages) + resolved, err := f.Resolve(packages) if err != nil { return nil, nil, err } @@ -404,75 +141,11 @@ func (p *Pac) categorizePackages(packages []string) (pacmanPkgs, aurPkgs []strin return pacmanPkgs, aurPkgs, nil } -func (p *Pac) 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 := p.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 - } - - p.fetchAURInfo(uncached) - fmt.Fprintf(os.Stderr, "[debug] ensureAURCache: done (%.2fs)\n", time.Since(start).Seconds()) -} - -func (p *Pac) 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 { - p.aurCache[r.Name] = r - result[r.Name] = r - } - - fmt.Fprintf(os.Stderr, "[debug] fetchAURInfo: done (%.2fs)\n", time.Since(start).Seconds()) - return result -} - -func (p *Pac) InstallAUR(pkgName string) error { +func InstallAUR(f *fetch.Fetcher, pkgName string) error { start := time.Now() fmt.Fprintf(os.Stderr, "[debug] InstallAUR: starting...\n") - aurInfo, ok := p.aurCache[pkgName] + aurInfo, ok := f.GetAURPackage(pkgName) if !ok { return fmt.Errorf("AUR package not found in cache: %s", pkgName) } @@ -523,7 +196,7 @@ func getInstalledCount() (int, error) { return count, nil } -func (p *Pac) SyncPackages(packages []string) (int, error) { +func SyncPackages(packages []string) (int, error) { start := time.Now() fmt.Fprintf(os.Stderr, "[debug] SyncPackages: starting...\n") @@ -541,18 +214,18 @@ func (p *Pac) SyncPackages(packages []string) (int, error) { return len(matches), nil } -func (p *Pac) CleanupOrphans() (int, error) { +func CleanupOrphans() (int, error) { start := time.Now() fmt.Fprintf(os.Stderr, "[debug] CleanupOrphans: starting...\n") - listCmd := exec.Command("pacman", "-Qdtq") - orphans, err := listCmd.Output() + f, err := fetch.New() if err != nil { - return 0, nil + return 0, err } + defer f.Close() - orphanList := strings.TrimSpace(string(orphans)) - if orphanList == "" { + orphans, err := f.ListOrphans() + if err != nil || len(orphans) == 0 { fmt.Fprintf(os.Stderr, "[debug] CleanupOrphans: done (%.2fs)\n", time.Since(start).Seconds()) return 0, nil } @@ -563,7 +236,7 @@ func (p *Pac) CleanupOrphans() (int, error) { return 0, fmt.Errorf("%s: %s", err, output) } - count := strings.Count(orphanList, "\n") + 1 + count := len(orphans) fmt.Fprintf(os.Stderr, "[debug] CleanupOrphans: done (%.2fs)\n", time.Since(start).Seconds()) return count, nil @@ -573,20 +246,20 @@ func DryRun(packages []string) (*output.Result, error) { start := time.Now() fmt.Fprintf(os.Stderr, "[debug] DryRun: starting...\n") - p, err := New() + f, err := fetch.New() if err != nil { return nil, err } - defer p.Close() - fmt.Fprintf(os.Stderr, "[debug] DryRun: initialized pacman (%.2fs)\n", time.Since(start).Seconds()) + defer f.Close() + fmt.Fprintf(os.Stderr, "[debug] DryRun: initialized fetcher (%.2fs)\n", time.Since(start).Seconds()) - resolved, err := p.resolvePackages(packages) + resolved, err := f.Resolve(packages) if err != nil { return nil, err } fmt.Fprintf(os.Stderr, "[debug] DryRun: packages resolved (%.2fs)\n", time.Since(start).Seconds()) - localPkgs, err := p.buildLocalPkgMap() + localPkgs, err := f.BuildLocalPkgMap() if err != nil { return nil, err } @@ -608,7 +281,7 @@ func DryRun(packages []string) (*output.Result, error) { } fmt.Fprintf(os.Stderr, "[debug] DryRun: packages categorized (%.2fs)\n", time.Since(start).Seconds()) - orphans, err := p.listOrphans() + orphans, err := f.ListOrphans() if err != nil { return nil, err } @@ -622,23 +295,3 @@ func DryRun(packages []string) (*output.Result, error) { ToRemove: orphans, }, nil } - -func (p *Pac) 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 -}