185 lines
3.5 KiB
Go
185 lines
3.5 KiB
Go
package updater
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"os/exec"
|
|
"strings"
|
|
)
|
|
|
|
type Package struct {
|
|
Source string
|
|
Name string
|
|
Current string
|
|
Available string
|
|
Ignored bool
|
|
}
|
|
|
|
type Result struct {
|
|
Packages []Package
|
|
Warnings []string
|
|
}
|
|
|
|
type Options struct {
|
|
CheckAUR bool
|
|
IgnoredPackages []string
|
|
}
|
|
|
|
func (r Result) Total() int {
|
|
total := 0
|
|
for _, pkg := range r.Packages {
|
|
if !pkg.Ignored {
|
|
total++
|
|
}
|
|
}
|
|
return total
|
|
}
|
|
|
|
func (r Result) IgnoredTotal() int {
|
|
total := 0
|
|
for _, pkg := range r.Packages {
|
|
if pkg.Ignored {
|
|
total++
|
|
}
|
|
}
|
|
return total
|
|
}
|
|
|
|
func (r Result) Summary() string {
|
|
total := r.Total()
|
|
switch total {
|
|
case 0:
|
|
return "No updates available."
|
|
case 1:
|
|
return "1 update available."
|
|
default:
|
|
return strings.TrimSpace(strings.Join([]string{itoa(total), "updates available."}, " "))
|
|
}
|
|
}
|
|
|
|
func Check(ctx context.Context) (Result, error) {
|
|
return CheckWithOptions(ctx, Options{CheckAUR: true})
|
|
}
|
|
|
|
func CheckWithOptions(ctx context.Context, opts Options) (Result, error) {
|
|
var result Result
|
|
|
|
pacman, err := checkPacman(ctx)
|
|
if err != nil {
|
|
result.Warnings = append(result.Warnings, err.Error())
|
|
}
|
|
result.Packages = append(result.Packages, pacman...)
|
|
|
|
if opts.CheckAUR {
|
|
aur, err := checkAUR(ctx)
|
|
if err != nil {
|
|
result.Warnings = append(result.Warnings, err.Error())
|
|
}
|
|
result.Packages = append(result.Packages, aur...)
|
|
}
|
|
|
|
markIgnored(&result, opts.IgnoredPackages)
|
|
return result, nil
|
|
}
|
|
|
|
func markIgnored(result *Result, names []string) {
|
|
ignored := map[string]bool{}
|
|
for _, name := range names {
|
|
name = strings.TrimSpace(name)
|
|
if name != "" {
|
|
ignored[name] = true
|
|
}
|
|
}
|
|
if len(ignored) == 0 {
|
|
return
|
|
}
|
|
for i := range result.Packages {
|
|
result.Packages[i].Ignored = ignored[result.Packages[i].Name]
|
|
}
|
|
}
|
|
|
|
func checkPacman(ctx context.Context) ([]Package, error) {
|
|
if _, err := exec.LookPath("checkupdates"); err == nil {
|
|
out, err := exec.CommandContext(ctx, "checkupdates").Output()
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
return parseUpdates("pacman", string(out)), nil
|
|
}
|
|
|
|
out, err := exec.CommandContext(ctx, "pacman", "-Qu").Output()
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
return parseUpdates("pacman", string(out)), nil
|
|
}
|
|
|
|
func checkAUR(ctx context.Context) ([]Package, error) {
|
|
helper := AURHelper()
|
|
if helper == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
out, err := exec.CommandContext(ctx, helper, "-Qua").Output()
|
|
if err != nil {
|
|
return nil, nil
|
|
}
|
|
return parseUpdates("aur", string(out)), nil
|
|
}
|
|
|
|
func AURHelper() string {
|
|
for _, name := range []string{"paru", "yay"} {
|
|
if _, err := exec.LookPath(name); err == nil {
|
|
return name
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func parseUpdates(source, output string) []Package {
|
|
var packages []Package
|
|
scanner := bufio.NewScanner(strings.NewReader(output))
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" {
|
|
continue
|
|
}
|
|
packages = append(packages, parseLine(source, line))
|
|
}
|
|
return packages
|
|
}
|
|
|
|
func parseLine(source, line string) Package {
|
|
fields := strings.Fields(line)
|
|
pkg := Package{Source: source}
|
|
if len(fields) == 0 {
|
|
return pkg
|
|
}
|
|
|
|
pkg.Name = fields[0]
|
|
if len(fields) >= 4 && fields[2] == "->" {
|
|
pkg.Current = fields[1]
|
|
pkg.Available = fields[3]
|
|
return pkg
|
|
}
|
|
if len(fields) >= 3 && fields[1] == "->" {
|
|
pkg.Available = fields[2]
|
|
return pkg
|
|
}
|
|
return pkg
|
|
}
|
|
|
|
func itoa(n int) string {
|
|
if n == 0 {
|
|
return "0"
|
|
}
|
|
var buf [20]byte
|
|
i := len(buf)
|
|
for n > 0 {
|
|
i--
|
|
buf[i] = byte('0' + n%10)
|
|
n /= 10
|
|
}
|
|
return string(buf[i:])
|
|
}
|