Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion internal/providers/archlinuxpkgs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ Find, install and delete packages. Including AUR.
- find official packages
- find AUR packages
- install packages
- list all exclusively installed packages
- list installed packages (explicitly or not).
- detect and update outdated packages.
- remove packages
- clear all done items

#### Requirements

- `yay` or `paru` for AUR
- [`checkupadtes`](https://man.archlinux.org/man/extra/pacman-contrib/checkupdates.8.en) (optional) to safely check for official repository updates without requiring a database sync.
1 change: 1 addition & 0 deletions internal/providers/archlinuxpkgs/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type Package struct {
Repository string
Version string
Installed bool
Outdated bool
FullInfo string
URL string
URLPath string
Expand Down
35 changes: 30 additions & 5 deletions internal/providers/archlinuxpkgs/package_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

129 changes: 113 additions & 16 deletions internal/providers/archlinuxpkgs/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,21 @@ import (
"github.com/tinylib/msgp/msgp"
)

type Filter int

const (
All Filter = iota
Installed
Outdated
)

var (
Name = "archlinuxpkgs"
NamePretty = "Arch Linux Packages"
config *Config
installed = []string{}
installedOnly = false
outdated = []string{}
filter Filter = All
cacheFile = common.CacheFile("archlinuxpkgs.json")
cachedData = newCachedData()
)
Expand All @@ -41,15 +50,19 @@ const (
ActionVisitURL = "visit_url"
ActionRefresh = "refresh"
ActionRemove = "remove"
ActionUpdate = "update"
ActionShowInstalled = "show_installed"
ActionShowOutdated = "show_outdated"
ActionShowAll = "show_all"
)

type Config struct {
common.Config `koanf:",squash"`
CommandInstall string `koanf:"command_install" desc:"default command for AUR packages to install. supports %VALUE%." default:"yay -S %VALUE%"`
CommandRemove string `koanf:"command_remove" desc:"default command to remove packages. supports %VALUE%." default:"sudo pacman -R %VALUE%"`
CommandUpdate string `koanf:"command_update" desc:"default command to update outdated packages." default:"yay -Suy"`
AutoWrapWithTerminal bool `koanf:"auto_wrap_with_terminal" desc:"automatically wraps the command with terminal" default:"true"`
ExplicitOnly bool `koanf:"explicit_only" desc:"when filtering installed packages, show only explicitly installed packages, not dependencies" default:"true"`
}

type AURPackage struct {
Expand Down Expand Up @@ -123,7 +136,9 @@ func LoadConfig() {
},
CommandInstall: fmt.Sprintf("%s -S %s", helper, "%VALUE%"),
CommandRemove: fmt.Sprintf("%s -R %s", helper, "%VALUE%"),
CommandUpdate: fmt.Sprintf("%s -Suy", helper),
AutoWrapWithTerminal: true,
ExplicitOnly: true,
}

common.LoadConfig(Name, config)
Expand All @@ -142,6 +157,7 @@ func Setup() {

func setup() {
getInstalled()
getOutdated()
getOfficialPkgs()
setupAURPkgs()

Expand Down Expand Up @@ -196,11 +212,14 @@ func Activate(single bool, identifier, action string, query string, args string,
setup()
return
case ActionShowAll:
installedOnly = false
filter = All
return
case ActionShowInstalled:
installedOnly = true
filter = Installed
return
case ActionShowOutdated:
filter = Outdated
return
}

name := cachedData.Packages[identifier].Name
Expand All @@ -211,6 +230,8 @@ func Activate(single bool, identifier, action string, query string, args string,
pkgcmd = config.CommandInstall
case ActionRemove:
pkgcmd = config.CommandRemove
case ActionUpdate:
pkgcmd = config.CommandUpdate
default:
slog.Error(Name, "activate", fmt.Sprintf("unknown action: %s", action))
return
Expand Down Expand Up @@ -249,9 +270,9 @@ func Query(conn net.Conn, query string, single bool, exact bool, _ uint8) []*pb.
}

for k, v := range cachedData.Packages {
if installedOnly && !v.Installed {
continue
}
if (filter == Installed && !v.Installed) || (filter == Outdated && !v.Outdated) {
continue
}

state := []string{}
a := []string{}
Expand All @@ -268,9 +289,15 @@ func Query(conn net.Conn, query string, single bool, exact bool, _ uint8) []*pb.
a = append(a, "visit_url")
}

subtext := fmt.Sprintf("[%s]", strings.ToLower(v.Repository))
if v.Installed {
subtext = fmt.Sprintf("[%s] [installed]", strings.ToLower(v.Repository))
repo := fmt.Sprintf("[%s]", strings.ToLower(v.Repository))
var subtext string
switch {
case v.Installed && v.Outdated:
subtext = repo + " [installed] [outdated]"
case v.Installed:
subtext = repo + " [installed]"
default:
subtext = repo
}

e := &pb.QueryResponse_Item{
Expand Down Expand Up @@ -320,17 +347,28 @@ func HideFromProviderlist() bool {
}

func State(provider string) *pb.ProviderStateResponse {
actions := []string{ActionRefresh}
actions := []string{ActionRefresh}

if installedOnly {
outdatedIsEmpty := len(outdated) != 0

switch filter {
case Installed:
actions = append(actions, ActionShowAll)
} else {
case Outdated:
actions = append(actions, ActionShowInstalled, ActionShowAll)
default: // All
actions = append(actions, ActionShowInstalled)
}

return &pb.ProviderStateResponse{
Actions: actions,
if outdatedIsEmpty && filter != Outdated {
actions = append(actions, ActionShowOutdated)
}
if outdatedIsEmpty {
actions = append(actions, ActionUpdate)
}

return &pb.ProviderStateResponse{
Actions: actions,
}
}

func getOfficialPkgs() {
Expand Down Expand Up @@ -363,6 +401,7 @@ func getOfficialPkgs() {
case strings.HasPrefix(line, "Name"):
e.Name = strings.TrimSpace(strings.Split(line, ":")[1])
e.Installed = slices.Contains(installed, e.Name)
e.Outdated = slices.Contains(outdated, e.Name)
case strings.HasPrefix(line, "Description"):
e.Description = strings.TrimSpace(strings.Split(line, ":")[1])
case strings.HasPrefix(line, "Version"):
Expand Down Expand Up @@ -412,6 +451,7 @@ func setupAURPkgs() {
Version: pkg.Version,
Repository: "aur",
Installed: slices.Contains(installed, pkg.Name),
Outdated: slices.Contains(outdated, pkg.Name),
URL: pkg.URL,
FullInfo: pkg.toFullInfo(),
}
Expand All @@ -420,8 +460,15 @@ func setupAURPkgs() {

func getInstalled() {
installed = []string{}

var cmd *exec.Cmd

if config.ExplicitOnly {
cmd = exec.Command("pacman", "-Qe")
} else {
cmd = exec.Command("pacman", "-Q")
}

cmd := exec.Command("pacman", "-Qe")
out, err := cmd.CombinedOutput()
if err != nil {
slog.Error(Name, "installed", err)
Expand All @@ -434,3 +481,53 @@ func getInstalled() {
}
}
}

func getOutdated() {
outdated = []string{}

var offCmd *exec.Cmd
if _, err := exec.LookPath("checkupdates"); err == nil {
offCmd = exec.Command("checkupdates")
} else {
offCmd = exec.Command("pacman", "-Qu")
}

offOut, offErr := offCmd.CombinedOutput()
if offErr != nil {
// checkupdates return 2 when no updates are required
if exitErr, ok := offErr.(*exec.ExitError); ok && exitErr.ExitCode() != 2 {
slog.Error(Name, "outdated", offErr)
}
}


aurCmd := exec.Command(detectHelper(), "-Qu", "--aur")
aurOut, aurErr := aurCmd.CombinedOutput()
if aurErr != nil {
// yay -Qu --aur return 1 when no updates are required
if exitErr, ok := aurErr.(*exec.ExitError); ok && exitErr.ExitCode() != 1 {
slog.Error(Name, "outdated", offErr)
}
}

var installedSet map[string]struct{}
if config.ExplicitOnly {
installedSet = make(map[string]struct{}, len(installed))
for _, pkg := range installed {
installedSet[pkg] = struct{}{}
}
}

for _, out := range [][]byte{offOut, aurOut} {
for line := range strings.Lines(string(out)) {
fields := strings.Fields(line)
if len(fields) > 0 {
name := fields[0]
_, inInstalled := installedSet[name]
if !config.ExplicitOnly || inInstalled {
outdated = append(outdated, name)
}
}
}
}
}