Integrate dyalpm for local/sync package queries
This commit is contained in:
@@ -2,4 +2,4 @@
|
|||||||
description: Make a git commit, asking if it was by the user or AI
|
description: Make a git commit, asking if it was by the user or AI
|
||||||
---
|
---
|
||||||
|
|
||||||
skill name=make-commit $1
|
skill [name=make-commit] $1
|
||||||
|
|||||||
+12
-3
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
|
|
||||||
@@ -56,17 +57,23 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func run(cfg *Config) error {
|
func run(cfg *Config) error {
|
||||||
|
start := time.Now()
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] run: starting...\n")
|
||||||
|
|
||||||
packages, err := input.ReadPackages(cfg.StateFiles)
|
packages, err := input.ReadPackages(cfg.StateFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] run: packages read (%.2fs)\n", time.Since(start).Seconds())
|
||||||
|
|
||||||
merged := merge.Merge(packages)
|
merged := merge.Merge(packages)
|
||||||
|
|
||||||
if err := validation.Validate(merged); err != nil {
|
if !cfg.DryRun {
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
if err := validation.CheckDBFreshness(); err != nil {
|
||||||
return err
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.DryRun {
|
if cfg.DryRun {
|
||||||
@@ -76,6 +83,7 @@ func run(cfg *Config) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println(output.Format(result))
|
fmt.Println(output.Format(result))
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] run: dry-run done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,5 +94,6 @@ func run(cfg *Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(output.Format(result))
|
fmt.Println(output.Format(result))
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] run: sync done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ module github.com/Riyyi/declpac
|
|||||||
|
|
||||||
go 1.26.2
|
go 1.26.2
|
||||||
|
|
||||||
require (
|
require github.com/urfave/cli/v3 v3.8.0
|
||||||
github.com/urfave/cli/v3 v3.8.0
|
|
||||||
)
|
require github.com/Jguer/dyalpm v0.1.2
|
||||||
|
|
||||||
|
require github.com/ebitengine/purego v0.10.0 // indirect
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
github.com/Jguer/aur v1.3.0 h1:skdjp/P9kB75TBaJmn9PKK/kCeA9QsgjdUrORZ3gldU=
|
|
||||||
github.com/Jguer/aur v1.3.0/go.mod h1:F8Awo+WKzTxlXtNOO4pDQjMkePLZ+oMSbu+1fKLTTLo=
|
|
||||||
github.com/Jguer/dyalpm v0.1.2 h1:Gl0+GDWBQmo3DSsfzTPnKqCwYqcroq0j6kAtsIUkpUw=
|
github.com/Jguer/dyalpm v0.1.2 h1:Gl0+GDWBQmo3DSsfzTPnKqCwYqcroq0j6kAtsIUkpUw=
|
||||||
github.com/Jguer/dyalpm v0.1.2/go.mod h1:FpcWwU1eYHVWMKmr/yHFqHYKsS+qGKCtk/FIXirj2MY=
|
github.com/Jguer/dyalpm v0.1.2/go.mod h1:FpcWwU1eYHVWMKmr/yHFqHYKsS+qGKCtk/FIXirj2MY=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
||||||
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI=
|
github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI=
|
||||||
github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ In dry-run mode, the system SHALL compute what WOULD happen without executing an
|
|||||||
|
|
||||||
#### Scenario: Dry-run lists packages to remove
|
#### Scenario: Dry-run lists packages to remove
|
||||||
- **WHEN** dry-run is enabled and orphan packages exist
|
- **WHEN** dry-run is enabled and orphan packages exist
|
||||||
- **THEN** the system SHALL NOT calculate or populate `Result.ToRemove` - orphan detection is skipped entirely in dry-run mode
|
- **THEN** the system SHALL populate `Result.ToRemove` with the list of orphan packages and `Result.Removed` with the count
|
||||||
|
|
||||||
#### Scenario: Dry-run skips pacman sync
|
#### Scenario: Dry-run skips pacman sync
|
||||||
- **WHEN** dry-run is enabled
|
- **WHEN** dry-run is enabled
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
## 1. Setup
|
## 1. Setup
|
||||||
|
|
||||||
- [ ] 1.1 Add `github.com/Jguer/dyalpm` to go.mod
|
- [x] 1.1 Add `github.com/Jguer/dyalpm` to go.mod
|
||||||
- [ ] 1.2 Run `go mod tidy` to fetch dependencies
|
- [x] 1.2 Run `go mod tidy` to fetch dependencies
|
||||||
|
|
||||||
## 2. Core Refactoring
|
## 2. Core Refactoring
|
||||||
|
|
||||||
- [ ] 2.1 Update `PackageInfo` struct to add `Installed bool` field
|
- [x] 2.1 Update `PackageInfo` struct to add `Installed bool` field
|
||||||
- [ ] 2.2 Create `Pac` struct with `alpm.Handle` instead of just aurCache
|
- [x] 2.2 Create `Pac` struct with `alpm.Handle` instead of just aurCache
|
||||||
- [ ] 2.3 Implement `NewPac()` that initializes alpm handle and local/sync DBs
|
- [x] 2.3 Implement `NewPac()` that initializes alpm handle and local/sync DBs
|
||||||
|
|
||||||
## 3. Package Resolution Algorithm
|
## 3. Package Resolution Algorithm
|
||||||
|
|
||||||
- [ ] 3.1 Implement `buildLocalPkgMap()` - iterate localDB.PkgCache() to create lookup map
|
- [x] 3.1 Implement `buildLocalPkgMap()` - iterate localDB.PkgCache() to create lookup map
|
||||||
- [ ] 3.2 Implement `checkSyncDBs()` - iterate each sync DB's PkgCache() to find packages
|
- [x] 3.2 Implement `checkSyncDBs()` - iterate each sync DB's PkgCache() to find packages
|
||||||
- [ ] 3.3 Implement `resolvePackages()` - unified algorithm:
|
- [x] 3.3 Implement `resolvePackages()` - unified algorithm:
|
||||||
- Step 1: Check local DB for all packages (batch)
|
- Step 1: Check local DB for all packages (batch)
|
||||||
- Step 2: Check sync DBs for remaining packages (batch per repo)
|
- Step 2: Check sync DBs for remaining packages (batch per repo)
|
||||||
- Step 3: Batch query AUR for remaining packages
|
- Step 3: Batch query AUR for remaining packages
|
||||||
@@ -22,24 +22,24 @@
|
|||||||
|
|
||||||
## 4. Sync and DryRun Integration
|
## 4. Sync and DryRun Integration
|
||||||
|
|
||||||
- [ ] 4.1 Refactor `Sync()` function to use new resolution algorithm
|
- [x] 4.1 Refactor `Sync()` function to use new resolution algorithm
|
||||||
- [ ] 4.2 Refactor `DryRun()` function to use new resolution algorithm
|
- [x] 4.2 Refactor `DryRun()` function to use new resolution algorithm
|
||||||
- [ ] 4.3 Preserve AUR batched HTTP calls (existing `fetchAURInfo`)
|
- [x] 4.3 Preserve AUR batched HTTP calls (existing `fetchAURInfo`)
|
||||||
- [ ] 4.4 Preserve orphan cleanup logic (`CleanupOrphans()`)
|
- [x] 4.4 Preserve orphan cleanup logic (`CleanupOrphans()`)
|
||||||
|
|
||||||
## 5. Marking Operations
|
## 5. Marking Operations
|
||||||
|
|
||||||
- [ ] 5.1 Keep `MarkExplicit()` for marking state packages
|
- [x] 5.1 Keep `MarkExplicit()` for marking state packages
|
||||||
- [ ] 5.2 After sync, run `pacman -D --asdeps` on ALL installed packages (simplifies tracking)
|
- [x] 5.2 After sync, run `pacman -D --asdeps` on ALL installed packages (simplifies tracking)
|
||||||
- [ ] 5.3 After deps marking, run `pacman -D --asexplicit` on collected state packages (overrides deps)
|
- [x] 5.3 After deps marking, run `pacman -D --asexplicit` on collected state packages (overrides deps)
|
||||||
- [ ] 5.4 Skip marking operations in dry-run mode
|
- [x] 5.4 Skip marking operations in dry-run mode
|
||||||
|
|
||||||
## 6. Cleanup and Output
|
## 6. Cleanup and Output
|
||||||
|
|
||||||
- [ ] 6.1 Remove subprocess-based `ValidatePackage()` implementation
|
- [x] 6.1 Remove subprocess-based `ValidatePackage()` implementation
|
||||||
- [ ] 6.2 Remove subprocess-based `GetInstalledPackages()` implementation
|
- [x] 6.2 Remove subprocess-based `GetInstalledPackages()` implementation
|
||||||
- [ ] 6.3 Update output summary to show installed/removed counts
|
- [x] 6.3 Update output summary to show installed/removed counts
|
||||||
- [ ] 6.4 In dry-run mode, populate `ToInstall` and `ToRemove` lists
|
- [x] 6.4 In dry-run mode, populate `ToInstall` and `ToRemove` lists
|
||||||
|
|
||||||
## 7. Testing
|
## 7. Testing
|
||||||
|
|
||||||
|
|||||||
+300
-71
@@ -12,6 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Jguer/dyalpm"
|
||||||
"github.com/Riyyi/declpac/pkg/output"
|
"github.com/Riyyi/declpac/pkg/output"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,21 +24,179 @@ var (
|
|||||||
|
|
||||||
type Pac struct {
|
type Pac struct {
|
||||||
aurCache map[string]AURPackage
|
aurCache map[string]AURPackage
|
||||||
|
handle dyalpm.Handle
|
||||||
|
localDB dyalpm.Database
|
||||||
|
syncDBs []dyalpm.Database
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() (*Pac, error) {
|
func New() (*Pac, error) {
|
||||||
return &Pac{aurCache: make(map[string]AURPackage)}, nil
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
func (p *Pac) Close() error {
|
||||||
|
if p.handle != nil {
|
||||||
|
p.handle.Release()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type PackageInfo struct {
|
type PackageInfo struct {
|
||||||
Name string
|
Name string
|
||||||
InAUR bool
|
InAUR bool
|
||||||
Exists bool
|
Exists bool
|
||||||
AURInfo *AURPackage
|
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 {
|
type AURResponse struct {
|
||||||
@@ -51,61 +210,67 @@ type AURPackage struct {
|
|||||||
URL string `json:"URL"`
|
URL string `json:"URL"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pac) ValidatePackage(name string) (*PackageInfo, error) {
|
|
||||||
cmd := exec.Command("pacman", "-Qip", name)
|
|
||||||
if err := cmd.Run(); err == nil {
|
|
||||||
return &PackageInfo{Name: name, Exists: true, InAUR: false}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd = exec.Command("pacman", "-Sip", name)
|
|
||||||
if err := cmd.Run(); err == nil {
|
|
||||||
return &PackageInfo{Name: name, Exists: true, InAUR: false}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
p.ensureAURCache([]string{name})
|
|
||||||
if aurInfo, ok := p.aurCache[name]; ok {
|
|
||||||
return &PackageInfo{Name: name, Exists: true, InAUR: true, AURInfo: &aurInfo}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &PackageInfo{Name: name, Exists: false, InAUR: false}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pac) IsDBFresh() (bool, error) {
|
func (p *Pac) IsDBFresh() (bool, error) {
|
||||||
|
start := time.Now()
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] IsDBFresh: starting...\n")
|
||||||
|
|
||||||
info, err := os.Stat(LockFile)
|
info, err := os.Stat(LockFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
age := time.Since(info.ModTime())
|
age := time.Since(info.ModTime())
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] IsDBFresh: done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
return age < 24*time.Hour, nil
|
return age < 24*time.Hour, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pac) SyncDB() error {
|
func (p *Pac) SyncDB() error {
|
||||||
|
start := time.Now()
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] SyncDB: starting...\n")
|
||||||
|
|
||||||
cmd := exec.Command("pacman", "-Syy")
|
cmd := exec.Command("pacman", "-Syy")
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
return cmd.Run()
|
err := cmd.Run()
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] SyncDB: done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pac) GetInstalledPackages() ([]string, error) {
|
func (p *Pac) MarkAllAsDeps() error {
|
||||||
cmd := exec.Command("pacman", "-Qq")
|
start := time.Now()
|
||||||
output, err := cmd.Output()
|
fmt.Fprintf(os.Stderr, "[debug] MarkAllAsDeps: starting...\n")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
packages := strings.Split(strings.TrimSpace(string(output)), "\n")
|
cmd := exec.Command("pacman", "-D", "--asdeps")
|
||||||
return packages, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pac) MarkExplicit(pkgName string) error {
|
|
||||||
cmd := exec.Command("pacman", "-D", "--explicit", pkgName)
|
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
return cmd.Run()
|
err := cmd.Run()
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] MarkAllAsDeps: done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pac) MarkAsExplicit(packages []string) error {
|
||||||
|
if len(packages) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
start := time.Now()
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] MarkAsExplicit: starting...\n")
|
||||||
|
|
||||||
|
args := append([]string{"-D", "--asexplicit"}, packages...)
|
||||||
|
cmd := exec.Command("pacman", args...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] MarkAsExplicit: done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func Sync(packages []string) (*output.Result, error) {
|
func Sync(packages []string) (*output.Result, error) {
|
||||||
|
start := time.Now()
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] Sync: starting...\n")
|
||||||
|
|
||||||
before, err := getInstalledCount()
|
before, err := getInstalledCount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -116,35 +281,53 @@ func Sync(packages []string) (*output.Result, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer p.Close()
|
defer p.Close()
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] Sync: initialized pacman (%.2fs)\n", time.Since(start).Seconds())
|
||||||
|
|
||||||
fresh, err := p.IsDBFresh()
|
fresh, err := p.IsDBFresh()
|
||||||
if err != nil || !fresh {
|
if err != nil || !fresh {
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] Sync: syncing database...\n")
|
||||||
if err := p.SyncDB(); err != nil {
|
if err := p.SyncDB(); err != nil {
|
||||||
return nil, fmt.Errorf("failed to sync database: %w", err)
|
return nil, fmt.Errorf("failed to sync database: %w", err)
|
||||||
}
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] Sync: database synced (%.2fs)\n", time.Since(start).Seconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
pacmanPkgs, aurPkgs := p.categorizePackages(packages)
|
fmt.Fprintf(os.Stderr, "[debug] Sync: categorizing packages...\n")
|
||||||
|
pacmanPkgs, aurPkgs, err := p.categorizePackages(packages)
|
||||||
for _, pkg := range packages {
|
if err != nil {
|
||||||
if err := p.MarkExplicit(pkg); err != nil {
|
return nil, err
|
||||||
fmt.Fprintf(os.Stderr, "warning: could not mark %s as explicit: %v\n", pkg, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] Sync: packages categorized (%.2fs)\n", time.Since(start).Seconds())
|
||||||
|
|
||||||
if len(pacmanPkgs) > 0 {
|
if len(pacmanPkgs) > 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] Sync: syncing %d pacman packages...\n", len(pacmanPkgs))
|
||||||
_, err = p.SyncPackages(pacmanPkgs)
|
_, err = p.SyncPackages(pacmanPkgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] Sync: pacman packages synced (%.2fs)\n", time.Since(start).Seconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pkg := range aurPkgs {
|
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 := p.InstallAUR(pkg); err != nil {
|
||||||
return nil, err
|
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 {
|
||||||
|
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 {
|
||||||
|
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 := p.CleanupOrphans()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -153,39 +336,43 @@ func Sync(packages []string) (*output.Result, error) {
|
|||||||
after, _ := getInstalledCount()
|
after, _ := getInstalledCount()
|
||||||
installedCount := max(after-before, 0)
|
installedCount := max(after-before, 0)
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] Sync: done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
return &output.Result{
|
return &output.Result{
|
||||||
Installed: installedCount,
|
Installed: installedCount,
|
||||||
Removed: removed,
|
Removed: removed,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pac) categorizePackages(packages []string) (pacmanPkgs, aurPkgs []string) {
|
func (p *Pac) categorizePackages(packages []string) (pacmanPkgs, aurPkgs []string, err error) {
|
||||||
var notInPacman []string
|
start := time.Now()
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] categorizePackages: starting...\n")
|
||||||
|
|
||||||
|
resolved, err := p.resolvePackages(packages)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for _, pkg := range packages {
|
for _, pkg := range packages {
|
||||||
info, err := p.ValidatePackage(pkg)
|
info := resolved[pkg]
|
||||||
if err != nil || !info.Exists {
|
if info == nil || !info.Exists {
|
||||||
notInPacman = append(notInPacman, pkg)
|
fmt.Fprintf(os.Stderr, "error: package not found: %s\n", pkg)
|
||||||
} else if !info.InAUR {
|
continue
|
||||||
|
}
|
||||||
|
if info.InAUR {
|
||||||
|
aurPkgs = append(aurPkgs, pkg)
|
||||||
|
} else {
|
||||||
pacmanPkgs = append(pacmanPkgs, pkg)
|
pacmanPkgs = append(pacmanPkgs, pkg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(notInPacman) > 0 {
|
fmt.Fprintf(os.Stderr, "[debug] categorizePackages: done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
p.ensureAURCache(notInPacman)
|
return pacmanPkgs, aurPkgs, nil
|
||||||
for _, pkg := range notInPacman {
|
|
||||||
if _, ok := p.aurCache[pkg]; ok {
|
|
||||||
aurPkgs = append(aurPkgs, pkg)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: package not found: %s\n", pkg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pacmanPkgs, aurPkgs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pac) ensureAURCache(packages []string) {
|
func (p *Pac) ensureAURCache(packages []string) {
|
||||||
|
start := time.Now()
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] ensureAURCache: starting...\n")
|
||||||
|
|
||||||
if len(packages) == 0 {
|
if len(packages) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -198,13 +385,18 @@ func (p *Pac) ensureAURCache(packages []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(uncached) == 0 {
|
if len(uncached) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] ensureAURCache: done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p.fetchAURInfo(uncached)
|
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 {
|
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)
|
result := make(map[string]AURPackage)
|
||||||
|
|
||||||
if len(packages) == 0 {
|
if len(packages) == 0 {
|
||||||
@@ -237,10 +429,14 @@ func (p *Pac) fetchAURInfo(packages []string) map[string]AURPackage {
|
|||||||
result[r.Name] = r
|
result[r.Name] = r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] fetchAURInfo: done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pac) InstallAUR(pkgName string) error {
|
func (p *Pac) InstallAUR(pkgName string) error {
|
||||||
|
start := time.Now()
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] InstallAUR: starting...\n")
|
||||||
|
|
||||||
aurInfo, ok := p.aurCache[pkgName]
|
aurInfo, ok := p.aurCache[pkgName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("AUR package not found in cache: %s", pkgName)
|
return fmt.Errorf("AUR package not found in cache: %s", pkgName)
|
||||||
@@ -259,6 +455,7 @@ func (p *Pac) InstallAUR(pkgName string) error {
|
|||||||
if err := cloneCmd.Run(); err != nil {
|
if err := cloneCmd.Run(); err != nil {
|
||||||
return fmt.Errorf("failed to clone AUR repo: %w", err)
|
return fmt.Errorf("failed to clone AUR repo: %w", err)
|
||||||
}
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] InstallAUR: cloned (%.2fs)\n", time.Since(start).Seconds())
|
||||||
|
|
||||||
makepkgCmd := exec.Command("makepkg", "-si", "--noconfirm")
|
makepkgCmd := exec.Command("makepkg", "-si", "--noconfirm")
|
||||||
makepkgCmd.Stdout = os.Stdout
|
makepkgCmd.Stdout = os.Stdout
|
||||||
@@ -267,11 +464,16 @@ func (p *Pac) InstallAUR(pkgName string) error {
|
|||||||
if err := makepkgCmd.Run(); err != nil {
|
if err := makepkgCmd.Run(); err != nil {
|
||||||
return fmt.Errorf("makepkg failed to build AUR package: %w", err)
|
return fmt.Errorf("makepkg failed to build AUR package: %w", err)
|
||||||
}
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] InstallAUR: built (%.2fs)\n", time.Since(start).Seconds())
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] InstallAUR: done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getInstalledCount() (int, error) {
|
func getInstalledCount() (int, error) {
|
||||||
|
start := time.Now()
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] getInstalledCount: starting...\n")
|
||||||
|
|
||||||
cmd := exec.Command("pacman", "-Qq")
|
cmd := exec.Command("pacman", "-Qq")
|
||||||
output, err := cmd.Output()
|
output, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -281,10 +483,15 @@ func getInstalledCount() (int, error) {
|
|||||||
if strings.TrimSpace(string(output)) == "" {
|
if strings.TrimSpace(string(output)) == "" {
|
||||||
count = 0
|
count = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] getInstalledCount: done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pac) SyncPackages(packages []string) (int, error) {
|
func (p *Pac) SyncPackages(packages []string) (int, error) {
|
||||||
|
start := time.Now()
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] SyncPackages: starting...\n")
|
||||||
|
|
||||||
args := append([]string{"-Syu"}, packages...)
|
args := append([]string{"-Syu"}, packages...)
|
||||||
cmd := exec.Command("pacman", args...)
|
cmd := exec.Command("pacman", args...)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
@@ -294,10 +501,15 @@ func (p *Pac) SyncPackages(packages []string) (int, error) {
|
|||||||
|
|
||||||
re := regexp.MustCompile(`upgrading (\S+)`)
|
re := regexp.MustCompile(`upgrading (\S+)`)
|
||||||
matches := re.FindAllStringSubmatch(string(output), -1)
|
matches := re.FindAllStringSubmatch(string(output), -1)
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] SyncPackages: done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
return len(matches), nil
|
return len(matches), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pac) CleanupOrphans() (int, error) {
|
func (p *Pac) CleanupOrphans() (int, error) {
|
||||||
|
start := time.Now()
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] CleanupOrphans: starting...\n")
|
||||||
|
|
||||||
listCmd := exec.Command("pacman", "-Qdtq")
|
listCmd := exec.Command("pacman", "-Qdtq")
|
||||||
orphans, err := listCmd.Output()
|
orphans, err := listCmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -306,6 +518,7 @@ func (p *Pac) CleanupOrphans() (int, error) {
|
|||||||
|
|
||||||
orphanList := strings.TrimSpace(string(orphans))
|
orphanList := strings.TrimSpace(string(orphans))
|
||||||
if orphanList == "" {
|
if orphanList == "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] CleanupOrphans: done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,33 +529,41 @@ func (p *Pac) CleanupOrphans() (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
count := strings.Count(orphanList, "\n") + 1
|
count := strings.Count(orphanList, "\n") + 1
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] CleanupOrphans: done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DryRun(packages []string) (*output.Result, error) {
|
func DryRun(packages []string) (*output.Result, error) {
|
||||||
|
start := time.Now()
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] DryRun: starting...\n")
|
||||||
|
|
||||||
p, err := New()
|
p, err := New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer p.Close()
|
defer p.Close()
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] DryRun: initialized pacman (%.2fs)\n", time.Since(start).Seconds())
|
||||||
|
|
||||||
current, err := p.GetInstalledPackages()
|
resolved, err := p.resolvePackages(packages)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
currentSet := make(map[string]bool)
|
fmt.Fprintf(os.Stderr, "[debug] DryRun: packages resolved (%.2fs)\n", time.Since(start).Seconds())
|
||||||
for _, pkg := range current {
|
|
||||||
currentSet[pkg] = true
|
localPkgs, err := p.buildLocalPkgMap()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var toInstall []string
|
var toInstall []string
|
||||||
var aurPkgs []string
|
var aurPkgs []string
|
||||||
for _, pkg := range packages {
|
for _, pkg := range packages {
|
||||||
if !currentSet[pkg] {
|
info := resolved[pkg]
|
||||||
info, err := p.ValidatePackage(pkg)
|
if info == nil || !info.Exists {
|
||||||
if err != nil || !info.Exists {
|
return nil, fmt.Errorf("package not found: %s", pkg)
|
||||||
return nil, fmt.Errorf("package not found: %s", pkg)
|
}
|
||||||
}
|
if _, installed := localPkgs[pkg]; !installed {
|
||||||
if info.InAUR {
|
if info.InAUR {
|
||||||
aurPkgs = append(aurPkgs, pkg)
|
aurPkgs = append(aurPkgs, pkg)
|
||||||
} else {
|
} else {
|
||||||
@@ -350,12 +571,15 @@ 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 := p.listOrphans()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] DryRun: orphans listed (%.2fs)\n", time.Since(start).Seconds())
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] DryRun: done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
return &output.Result{
|
return &output.Result{
|
||||||
Installed: len(toInstall) + len(aurPkgs),
|
Installed: len(toInstall) + len(aurPkgs),
|
||||||
Removed: len(orphans),
|
Removed: len(orphans),
|
||||||
@@ -365,6 +589,9 @@ func DryRun(packages []string) (*output.Result, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pac) listOrphans() ([]string, error) {
|
func (p *Pac) listOrphans() ([]string, error) {
|
||||||
|
start := time.Now()
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] listOrphans: starting...\n")
|
||||||
|
|
||||||
cmd := exec.Command("pacman", "-Qdtq")
|
cmd := exec.Command("pacman", "-Qdtq")
|
||||||
orphans, err := cmd.Output()
|
orphans, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -373,8 +600,10 @@ func (p *Pac) listOrphans() ([]string, error) {
|
|||||||
|
|
||||||
list := strings.TrimSpace(string(orphans))
|
list := strings.TrimSpace(string(orphans))
|
||||||
if list == "" {
|
if list == "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] listOrphans: done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] listOrphans: done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
return strings.Split(list, "\n"), nil
|
return strings.Split(list, "\n"), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
@@ -14,33 +9,10 @@ import (
|
|||||||
|
|
||||||
var LockFile = "/var/lib/pacman/db.lock"
|
var LockFile = "/var/lib/pacman/db.lock"
|
||||||
|
|
||||||
const AURInfoURL = "https://aur.archlinux.org/rpc?v=5&type=info"
|
func CheckDBFreshness() error {
|
||||||
|
start := time.Now()
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] CheckDBFreshness: starting...\n")
|
||||||
|
|
||||||
type AURResponse struct {
|
|
||||||
Results []AURResult `json:"results"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AURResult struct {
|
|
||||||
Name string `json:"Name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Validate(packages []string) error {
|
|
||||||
if len(packages) == 0 {
|
|
||||||
return errors.New("no packages found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := checkDBFreshness(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validatePackages(packages); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkDBFreshness() error {
|
|
||||||
info, err := os.Stat(LockFile)
|
info, err := os.Stat(LockFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -56,79 +28,6 @@ func checkDBFreshness() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] CheckDBFreshness: done (%.2fs)\n", time.Since(start).Seconds())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatePackages(packages []string) error {
|
|
||||||
var pacmanPkgs []string
|
|
||||||
var aurPkgs []string
|
|
||||||
|
|
||||||
for _, pkg := range packages {
|
|
||||||
if inPacman(pkg) {
|
|
||||||
pacmanPkgs = append(pacmanPkgs, pkg)
|
|
||||||
} else {
|
|
||||||
aurPkgs = append(aurPkgs, pkg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(aurPkgs) > 0 {
|
|
||||||
foundAUR := batchSearchAUR(aurPkgs)
|
|
||||||
for _, pkg := range aurPkgs {
|
|
||||||
if !foundAUR[pkg] {
|
|
||||||
return fmt.Errorf("package not found: %s", pkg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func inPacman(name string) bool {
|
|
||||||
cmd := exec.Command("pacman", "-Qip", name)
|
|
||||||
if err := cmd.Run(); err == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd = exec.Command("pacman", "-Sip", name)
|
|
||||||
if err := cmd.Run(); err == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func batchSearchAUR(packages []string) map[string]bool {
|
|
||||||
result := make(map[string]bool)
|
|
||||||
|
|
||||||
if len(packages) == 0 {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
v := url.Values{}
|
|
||||||
v.Set("type", "info")
|
|
||||||
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 {
|
|
||||||
result[r.Name] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user