Sync a declarative package list with the pacman package manager
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

179 lines
3.6 KiB

package pacman
import (
"fmt"
"os"
"os/exec"
"regexp"
"strings"
"time"
"github.com/Riyyi/declpac/pkg/output"
)
var (
Root = "/"
LockFile = "/var/lib/pacman/db.lock"
)
type Pac struct{}
func New() (*Pac, error) {
return &Pac{}, nil
}
func (p *Pac) Close() error {
return nil
}
type PackageInfo struct {
Name string
InAUR bool
Exists bool
}
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
}
cmd = exec.Command("aur", "search", name)
if out, err := cmd.Output(); err == nil && len(out) > 0 {
return &PackageInfo{Name: name, Exists: true, InAUR: true}, nil
}
return &PackageInfo{Name: name, Exists: false, InAUR: false}, nil
}
func (p *Pac) IsDBFresh() (bool, error) {
info, err := os.Stat(LockFile)
if err != nil {
return false, nil
}
age := time.Since(info.ModTime())
return age < 24*time.Hour, nil
}
func (p *Pac) SyncDB() error {
cmd := exec.Command("pacman", "-Syy")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func (p *Pac) GetInstalledPackages() ([]string, error) {
cmd := exec.Command("pacman", "-Qq")
output, err := cmd.Output()
if err != nil {
return nil, err
}
packages := strings.Split(strings.TrimSpace(string(output)), "\n")
return packages, nil
}
func (p *Pac) MarkExplicit(pkgName string) error {
cmd := exec.Command("pacman", "-D", "--explicit", pkgName)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func Sync(packages []string) (*output.Result, error) {
before, err := getInstalledCount()
if err != nil {
return nil, err
}
p, err := New()
if err != nil {
return nil, err
}
defer p.Close()
fresh, err := p.IsDBFresh()
if err != nil || !fresh {
if err := p.SyncDB(); err != nil {
return nil, fmt.Errorf("failed to sync database: %w", err)
}
}
for _, pkg := range packages {
if err := p.MarkExplicit(pkg); err != nil {
fmt.Fprintf(os.Stderr, "warning: could not mark %s as explicit: %v\n", pkg, err)
}
}
_, err = p.SyncPackages(packages)
if err != nil {
return nil, err
}
removed, err := p.CleanupOrphans()
if err != nil {
return nil, err
}
after, _ := getInstalledCount()
installedCount := max(after - before, 0)
return &output.Result{
Installed: installedCount,
Removed: removed,
}, nil
}
func getInstalledCount() (int, error) {
cmd := exec.Command("pacman", "-Qq")
output, err := cmd.Output()
if err != nil {
return 0, nil
}
count := strings.Count(string(output), "\n") + 1
if strings.TrimSpace(string(output)) == "" {
count = 0
}
return count, nil
}
func (p *Pac) SyncPackages(packages []string) (int, error) {
args := append([]string{"-Syu"}, packages...)
cmd := exec.Command("pacman", args...)
output, err := cmd.CombinedOutput()
if err != nil {
return 0, fmt.Errorf("pacman sync failed: %s", output)
}
re := regexp.MustCompile(`upgrading (\S+)`)
matches := re.FindAllStringSubmatch(string(output), -1)
return len(matches), nil
}
func (p *Pac) CleanupOrphans() (int, error) {
listCmd := exec.Command("pacman", "-Qdtq")
orphans, err := listCmd.Output()
if err != nil {
return 0, nil
}
orphanList := strings.TrimSpace(string(orphans))
if orphanList == "" {
return 0, nil
}
removeCmd := exec.Command("pacman", "-Rns")
output, err := removeCmd.CombinedOutput()
if err != nil {
return 0, fmt.Errorf("%s: %s", err, output)
}
count := strings.Count(orphanList, "\n") + 1
return count, nil
}