feat: add Arch update helper and web UI
This commit is contained in:
149
internal/updater/updater.go
Normal file
149
internal/updater/updater.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Package struct {
|
||||
Source string
|
||||
Name string
|
||||
Current string
|
||||
Available string
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Packages []Package
|
||||
Warnings []string
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
CheckAUR bool
|
||||
}
|
||||
|
||||
func (r Result) Total() int {
|
||||
return len(r.Packages)
|
||||
}
|
||||
|
||||
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...)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
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:])
|
||||
}
|
||||
Reference in New Issue
Block a user