Added search passtrough with ref
This commit is contained in:
parent
ecdf09c63b
commit
a585b576b1
2 changed files with 158 additions and 2 deletions
31
main.go
31
main.go
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
@ -52,7 +53,7 @@ func printHelp() {
|
||||||
}
|
}
|
||||||
|
|
||||||
usage := fmt.Sprintf(`%susage%s: vlcro [-h] [-v] [-y] [-j N] [--debug] [--no-color]
|
usage := fmt.Sprintf(`%susage%s: vlcro [-h] [-v] [-y] [-j N] [--debug] [--no-color]
|
||||||
%s {refresh,ref,dist-upgrade,dup,update,up,install,in,install-new-recommends,inr} ...
|
%s {refresh,ref,dist-upgrade,dup,update,up,install,in,install-new-recommends,inr,search,se} ...
|
||||||
|
|
||||||
%s%s%s (%sv%s%s) makes zypper faster by running slow operations in parallel.
|
%s%s%s (%sv%s%s) makes zypper faster by running slow operations in parallel.
|
||||||
|
|
||||||
|
|
@ -74,6 +75,9 @@ func printHelp() {
|
||||||
%supdate%s (%sup%s) update all installed packages
|
%supdate%s (%sup%s) update all installed packages
|
||||||
%s-d, --download-only%s download packages without installing
|
%s-d, --download-only%s download packages without installing
|
||||||
|
|
||||||
|
%ssearch%s (%sse%s) search for packages matching pattern
|
||||||
|
%s<pattern>%s pattern(s) to search for
|
||||||
|
|
||||||
%sinstall%s (%sin%s) install one or more packages
|
%sinstall%s (%sin%s) install one or more packages
|
||||||
%s-d, --download-only%s download packages without installing
|
%s-d, --download-only%s download packages without installing
|
||||||
%s<package>%s package name(s) to install
|
%s<package>%s package name(s) to install
|
||||||
|
|
@ -90,6 +94,7 @@ func printHelp() {
|
||||||
hd, d, hd, d, he, d,
|
hd, d, hd, d, he, d,
|
||||||
hd, d, hd, d, he, d,
|
hd, d, hd, d, he, d,
|
||||||
hd, d, hd, d, he, d,
|
hd, d, hd, d, he, d,
|
||||||
|
hd, d, hd, d, hd, d,
|
||||||
hd, d, hd, d, he, d, hd, d,
|
hd, d, hd, d, he, d, hd, d,
|
||||||
hd, d, hd, d, he, d)
|
hd, d, hd, d, he, d)
|
||||||
|
|
||||||
|
|
@ -125,10 +130,14 @@ func isInstallCommand(cmd string) bool {
|
||||||
return cmd == "install" || cmd == "in"
|
return cmd == "install" || cmd == "in"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isSearchCommand(cmd string) bool {
|
||||||
|
return cmd == "search" || cmd == "se"
|
||||||
|
}
|
||||||
|
|
||||||
func isKnownCommand(cmd string) bool {
|
func isKnownCommand(cmd string) bool {
|
||||||
switch cmd {
|
switch cmd {
|
||||||
case "refresh", "ref", "dist-upgrade", "dup", "update", "up",
|
case "refresh", "ref", "dist-upgrade", "dup", "update", "up",
|
||||||
"install", "in", "install-new-recommends", "inr":
|
"install", "in", "install-new-recommends", "inr", "search", "se":
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
@ -142,6 +151,15 @@ func parseArgsInto(args []string) (*config, error) {
|
||||||
|
|
||||||
for i < len(args) {
|
for i < len(args) {
|
||||||
arg := args[i]
|
arg := args[i]
|
||||||
|
|
||||||
|
// search/se passes all remaining patterns and zypper search
|
||||||
|
// options verbatim to zypper.
|
||||||
|
if cmdFound && isSearchCommand(c.command) {
|
||||||
|
c.packages = append(c.packages, arg)
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
consumedNext := false
|
consumedNext := false
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|
@ -348,6 +366,15 @@ func main() {
|
||||||
if err := handleUpdate(ctx, tmpDir); err != nil {
|
if err := handleUpdate(ctx, tmpDir); err != nil {
|
||||||
exitCode = 1
|
exitCode = 1
|
||||||
}
|
}
|
||||||
|
case "search", "se":
|
||||||
|
if err := handleSearch(ctx, tmpDir); err != nil {
|
||||||
|
var see *searchExitError
|
||||||
|
if errors.As(err, &see) {
|
||||||
|
exitCode = see.code
|
||||||
|
} else {
|
||||||
|
exitCode = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
case "install", "in":
|
case "install", "in":
|
||||||
if err := handleInstall(ctx, tmpDir); err != nil {
|
if err := handleInstall(ctx, tmpDir); err != nil {
|
||||||
exitCode = 1
|
exitCode = 1
|
||||||
|
|
|
||||||
129
zypper.go
129
zypper.go
|
|
@ -10,7 +10,9 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
@ -795,6 +797,133 @@ func handleUpdate(ctx context.Context, tmpDir string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// search / se
|
||||||
|
|
||||||
|
type searchExitError struct {
|
||||||
|
code int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *searchExitError) Error() string {
|
||||||
|
return fmt.Sprintf("zypper search failed with exit code %d", e.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// repoRefreshDelayMinutes returns the configured repo.refresh.delay (minutes)
|
||||||
|
// from zypp.conf, or the default of 10 when unset.
|
||||||
|
func repoRefreshDelayMinutes() int {
|
||||||
|
delay := -1
|
||||||
|
parse := func(path string) {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
re := regexp.MustCompile(`^\s*repo\.refresh\.delay\s*[:=]\s*(\d+)\s*$`)
|
||||||
|
for _, line := range strings.Split(string(data), "\n") {
|
||||||
|
if m := re.FindStringSubmatch(line); m != nil {
|
||||||
|
if n, err := strconv.Atoi(m[1]); err == nil {
|
||||||
|
delay = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parse("/etc/zypp/zypp.conf")
|
||||||
|
if entries, err := filepath.Glob("/etc/zypp/zypp.conf.d/*.conf"); err == nil {
|
||||||
|
sort.Strings(entries)
|
||||||
|
for _, e := range entries {
|
||||||
|
parse(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if delay < 0 {
|
||||||
|
delay = 10
|
||||||
|
}
|
||||||
|
return delay
|
||||||
|
}
|
||||||
|
|
||||||
|
// repoNeedsRefresh mirrors zypper's auto-refresh policy: a repo needs
|
||||||
|
// refreshing when its cached index file timestamp is older than
|
||||||
|
// repo.refresh.delay. Repos never refreshed must be refreshed.
|
||||||
|
func repoNeedsRefresh(alias string) bool {
|
||||||
|
delay := repoRefreshDelayMinutes()
|
||||||
|
rawPath := filepath.Join("/var/cache/zypp/raw", alias)
|
||||||
|
repomd := filepath.Join(rawPath, "repodata", "repomd.xml")
|
||||||
|
|
||||||
|
var last time.Time
|
||||||
|
if st, err := os.Stat(repomd); err == nil {
|
||||||
|
last = st.ModTime()
|
||||||
|
} else if info, err := os.Stat(rawPath); err == nil && info.IsDir() {
|
||||||
|
filepath.Walk(rawPath, func(_ string, fi os.FileInfo, err error) error {
|
||||||
|
if err == nil && fi.ModTime().After(last) {
|
||||||
|
last = fi.ModTime()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if last.IsZero() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return time.Since(last) > time.Duration(delay)*time.Minute
|
||||||
|
}
|
||||||
|
|
||||||
|
// runSearch runs the real zypper search with terminal passthrough so the
|
||||||
|
// output is identical to stock zypper.
|
||||||
|
func runSearch(args []string) int {
|
||||||
|
cmd := exec.Command("zypper", append([]string{"--no-refresh", "search"}, args...)...)
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||||
|
return exitErr.ExitCode()
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSearch(ctx context.Context, tmpDir string) error {
|
||||||
|
if len(cfg.packages) == 0 {
|
||||||
|
errorf("%s", colorText(colorError, "Search must have at least one search pattern"))
|
||||||
|
return fmt.Errorf("no search pattern specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
statusMsg("Reading repository list")
|
||||||
|
xmlOutput, _ := zypperDryRun([]string{"repos"})
|
||||||
|
repos := findElements(xmlOutput, "repo")
|
||||||
|
|
||||||
|
var staleRepos []string
|
||||||
|
for _, r := range repos {
|
||||||
|
if r["enabled"] != "1" || r["autorefresh"] != "1" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if repoNeedsRefresh(r["alias"]) {
|
||||||
|
staleRepos = append(staleRepos, r["alias"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debugf("Stale repos: %v", staleRepos)
|
||||||
|
|
||||||
|
if len(staleRepos) > 0 {
|
||||||
|
tracker := &MountTracker{}
|
||||||
|
statusMsg(fmt.Sprintf("Refreshing %d stale repo(s)", len(staleRepos)))
|
||||||
|
getZyppLock()
|
||||||
|
err := mainTask(ctx, staleRepos, tmpDir, true, tracker)
|
||||||
|
if err == context.Canceled {
|
||||||
|
vlcroCleanup(tmpDir, tracker)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
warningf("%s", colorText(colorWarning, fmt.Sprintf("Parallel refresh failed (%v). Falling back to standard zypper...", err)))
|
||||||
|
}
|
||||||
|
vlcroCleanup(tmpDir, tracker)
|
||||||
|
}
|
||||||
|
|
||||||
|
code := runSearch(cfg.packages)
|
||||||
|
if code != 0 {
|
||||||
|
return &searchExitError{code: code}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func handleInstall(ctx context.Context, tmpDir string) error {
|
func handleInstall(ctx context.Context, tmpDir string) error {
|
||||||
if len(cfg.packages) == 0 {
|
if len(cfg.packages) == 0 {
|
||||||
errorf("%s", colorText(colorError, "No packages specified for installation"))
|
errorf("%s", colorText(colorError, "No packages specified for installation"))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue