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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
|
@ -52,7 +53,7 @@ func printHelp() {
|
|||
}
|
||||
|
||||
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.
|
||||
|
||||
|
|
@ -74,6 +75,9 @@ func printHelp() {
|
|||
%supdate%s (%sup%s) update all installed packages
|
||||
%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
|
||||
%s-d, --download-only%s download packages without installing
|
||||
%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, hd, d,
|
||||
hd, d, hd, d, he, d, hd, d,
|
||||
hd, d, hd, d, he, d)
|
||||
|
||||
|
|
@ -125,10 +130,14 @@ func isInstallCommand(cmd string) bool {
|
|||
return cmd == "install" || cmd == "in"
|
||||
}
|
||||
|
||||
func isSearchCommand(cmd string) bool {
|
||||
return cmd == "search" || cmd == "se"
|
||||
}
|
||||
|
||||
func isKnownCommand(cmd string) bool {
|
||||
switch cmd {
|
||||
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 false
|
||||
|
|
@ -142,6 +151,15 @@ func parseArgsInto(args []string) (*config, error) {
|
|||
|
||||
for i < len(args) {
|
||||
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
|
||||
|
||||
switch {
|
||||
|
|
@ -348,6 +366,15 @@ func main() {
|
|||
if err := handleUpdate(ctx, tmpDir); err != nil {
|
||||
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":
|
||||
if err := handleInstall(ctx, tmpDir); err != nil {
|
||||
exitCode = 1
|
||||
|
|
|
|||
129
zypper.go
129
zypper.go
|
|
@ -10,7 +10,9 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
|
@ -795,6 +797,133 @@ func handleUpdate(ctx context.Context, tmpDir string) error {
|
|||
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 {
|
||||
if len(cfg.packages) == 0 {
|
||||
errorf("%s", colorText(colorError, "No packages specified for installation"))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue