upm-universalpackagemanager/pkg_manager/pkg_manager.go

463 lines
12 KiB
Go

package pkg_manager
import (
"fmt"
"io"
"os"
"os/exec"
"strings"
"time"
"github.com/briandowns/spinner"
)
// PackageManager interface defines common operations
type PackageManager interface {
Install(packages ...string) error
Remove(packages ...string) error
Search(term string) error
Update() error
Upgrade() error
ListInstalled() error
ShowInfo(pkg string) error
AddRepository(repo string) error
RemoveRepository(repo string) error
ListRepositories() error
CleanCache() error
AutoRemove() error
CheckDependencies(pkg string) error
ShowDependencies(pkg string) error
HandleSource(appName string) error
}
// Manager represents a concrete package manager
type Manager struct {
command string
installCmd []string
removeCmd []string
searchCmd []string
updateCmd []string
upgradeCmd []string
listCmd []string
infoCmd []string
addRepoCmd []string
removeRepoCmd []string
listRepoCmd []string
cleanCacheCmd []string
autoRemoveCmd []string
checkDependsCmd []string
showDependsCmd []string
needsSudo bool
versionSep string
cacheDir string
}
// UniversalManager represents universal package managers like snap/flatpak
type UniversalManager struct {
Manager
isAvailable bool
}
// PackageVersion represents a package with optional version
type PackageVersion struct {
Name string
Version string
}
var universalManagers = map[string]*UniversalManager{
"snap": {
Manager: Manager{
command: "snap",
installCmd: []string{"install"},
removeCmd: []string{"remove"},
searchCmd: []string{"find"},
updateCmd: []string{"refresh"},
upgradeCmd: []string{"refresh"},
needsSudo: true,
},
},
"flatpak": {
Manager: Manager{
command: "flatpak",
installCmd: []string{"install", "-y"},
removeCmd: []string{"uninstall", "-y"},
searchCmd: []string{"search"},
updateCmd: []string{"update", "-y"},
upgradeCmd: []string{"update", "-y"},
needsSudo: true,
},
},
}
var managers = map[string]Manager{
"apt": {
command: "apt",
installCmd: []string{"install", "-y"},
removeCmd: []string{"remove", "-y"},
searchCmd: []string{"search"},
updateCmd: []string{"update"},
upgradeCmd: []string{"upgrade", "-y"},
listCmd: []string{"list", "--installed"},
infoCmd: []string{"show"},
addRepoCmd: []string{"add-apt-repository", "-y"},
removeRepoCmd: []string{"add-apt-repository", "-r", "-y"},
listRepoCmd: []string{"sources"},
cleanCacheCmd: []string{"clean"},
autoRemoveCmd: []string{"autoremove", "-y"},
checkDependsCmd: []string{"check"},
showDependsCmd: []string{"depends"},
needsSudo: true,
versionSep: "=",
cacheDir: "/var/cache/apt",
},
"dnf": {
command: "dnf",
installCmd: []string{"install", "-y"},
removeCmd: []string{"remove", "-y"},
searchCmd: []string{"search"},
updateCmd: []string{"check-update"},
upgradeCmd: []string{"upgrade", "-y"},
listCmd: []string{"list", "installed"},
infoCmd: []string{"info"},
addRepoCmd: []string{"config-manager", "--add-repo"},
removeRepoCmd: []string{"config-manager", "--disable"},
listRepoCmd: []string{"repolist"},
cleanCacheCmd: []string{"clean", "all"},
autoRemoveCmd: []string{"autoremove", "-y"},
checkDependsCmd: []string{"check-dependencies"},
showDependsCmd: []string{"repoquery", "--requires"},
needsSudo: true,
versionSep: "-",
cacheDir: "/var/cache/dnf",
},
"yum": {
command: "yum",
installCmd: []string{"install", "-y"},
removeCmd: []string{"remove", "-y"},
searchCmd: []string{"search"},
updateCmd: []string{"check-update"},
upgradeCmd: []string{"upgrade", "-y"},
needsSudo: true,
versionSep: "-",
cacheDir: "/var/cache/yum",
},
"pacman": {
command: "pacman",
installCmd: []string{"-S", "--noconfirm"},
removeCmd: []string{"-R", "--noconfirm"},
searchCmd: []string{"-Ss"},
updateCmd: []string{"-Sy"},
upgradeCmd: []string{"-Syu", "--noconfirm"},
needsSudo: true,
versionSep: "=",
cacheDir: "/var/cache/pacman",
},
"zypper": {
command: "zypper",
installCmd: []string{"install", "-y"},
removeCmd: []string{"remove", "-y"},
searchCmd: []string{"search"},
updateCmd: []string{"refresh"},
upgradeCmd: []string{"update", "-y"},
needsSudo: true,
versionSep: "=",
cacheDir: "/var/cache/zypp",
},
"apk": {
command: "apk",
installCmd: []string{"add"},
removeCmd: []string{"del"},
searchCmd: []string{"search"},
updateCmd: []string{"update"},
upgradeCmd: []string{"upgrade"},
needsSudo: true,
versionSep: "=",
cacheDir: "/var/cache/apk",
},
"xbps": {
command: "xbps-install",
installCmd: []string{"-y"},
removeCmd: []string{"-R"},
searchCmd: []string{"-Rs"},
updateCmd: []string{"-S"},
upgradeCmd: []string{"-Su"},
listCmd: []string{"-l"},
infoCmd: []string{"-S"},
addRepoCmd: []string{"-R", "add"},
removeRepoCmd: []string{"-R", "remove"},
listRepoCmd: []string{"-R", "list"},
cleanCacheCmd: []string{"-Op"},
autoRemoveCmd: []string{"-Oo"},
checkDependsCmd: []string{"-C"},
showDependsCmd: []string{"-x"},
needsSudo: true,
versionSep: "-",
cacheDir: "/var/cache/xbps",
},
"emerge": {
command: "emerge",
installCmd: []string{"--ask", "n"},
removeCmd: []string{"--unmerge", "--ask", "n"},
searchCmd: []string{"--search"},
updateCmd: []string{"--sync"},
upgradeCmd: []string{"--update", "--deep", "--ask", "n", "@world"},
listCmd: []string{"--list-sets"},
infoCmd: []string{"--info"},
addRepoCmd: []string{"--repos-conf", "add"},
removeRepoCmd: []string{"--repos-conf", "remove"},
listRepoCmd: []string{"--repos-conf", "list"},
cleanCacheCmd: []string{"--depclean"},
autoRemoveCmd: []string{"--depclean"},
checkDependsCmd: []string{"--depclean", "-p"},
showDependsCmd: []string{"--deps"},
needsSudo: true,
versionSep: "-",
cacheDir: "/var/cache/portage",
},
}
// ParsePackageVersion parses package name and version
func ParsePackageVersion(pkg string) PackageVersion {
parts := strings.Split(pkg, "@")
if len(parts) == 2 {
return PackageVersion{
Name: parts[0],
Version: parts[1],
}
}
return PackageVersion{Name: pkg}
}
// formatPackageWithVersion formats package with version using manager's separator
func (m *Manager) formatPackageWithVersion(pv PackageVersion) string {
if pv.Version == "" {
return pv.Name
}
return fmt.Sprintf("%s%s%s", pv.Name, m.versionSep, pv.Version)
}
// CheckPackageExists verifies if a package exists in the repositories
func (m *Manager) CheckPackageExists(pkg string) bool {
cmd := exec.Command(m.command, append(m.searchCmd, pkg)...)
output, err := cmd.Output()
if err != nil {
return false
}
// Different package managers have different output formats
// This is a simple check that should work for most
return len(output) > 0
}
// Install packages with version support
func (m *Manager) Install(packages ...string) error {
if len(packages) == 0 {
return fmt.Errorf("no packages specified")
}
// Validate packages first
for _, pkg := range packages {
pv := ParsePackageVersion(pkg)
if !m.CheckPackageExists(pv.Name) {
return fmt.Errorf("package not found: %s", pv.Name)
}
}
formattedPkgs := make([]string, len(packages))
for i, pkg := range packages {
pv := ParsePackageVersion(pkg)
formattedPkgs[i] = m.formatPackageWithVersion(pv)
}
return m.execCommand(m.installCmd, formattedPkgs...)
}
// InitializeUniversalManagers checks which universal package managers are available
func InitializeUniversalManagers() {
for _, mgr := range universalManagers {
_, err := exec.LookPath(mgr.command)
mgr.isAvailable = err == nil
}
}
// GetUniversalManager returns a universal package manager if available
func GetUniversalManager(name string) (*UniversalManager, bool) {
if mgr, exists := universalManagers[name]; exists && mgr.isAvailable {
return mgr, true
}
return nil, false
}
type OutputMode int
const (
OutputQuiet OutputMode = iota
OutputVerbose
)
var outputMode = OutputQuiet
// SetOutputMode sets the output verbosity
func SetOutputMode(mode OutputMode) {
outputMode = mode
}
// execCommandWithSudo executes a command with sudo
func (m *Manager) execCommandWithSudo(baseCmd []string, args ...string) error {
cmdArgs := append(baseCmd, args...)
cmdArgs = append([]string{m.command}, cmdArgs...)
cmd := exec.Command("sudo", cmdArgs...)
// Always show sudo password prompt
cmd.Stdin = os.Stdin
if outputMode == OutputVerbose {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
} else {
cmd.Stdout = io.Discard
cmd.Stderr = io.Discard
}
return cmd.Run()
}
func (m *Manager) execCommand(baseCmd []string, args ...string) error {
cmdArgs := append(baseCmd, args...)
var cmd *exec.Cmd
if m.needsSudo && os.Geteuid() != 0 {
return m.execCommandWithSudo(baseCmd, args...)
} else {
cmd = exec.Command(m.command, cmdArgs...)
if outputMode == OutputVerbose {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
} else {
cmd.Stdout = io.Discard
cmd.Stderr = io.Discard
}
cmd.Stdin = os.Stdin
return cmd.Run()
}
}
// Remove packages
func (m *Manager) Remove(packages ...string) error {
if len(packages) == 0 {
return fmt.Errorf("no packages specified")
}
return m.execCommand(m.removeCmd, packages...)
}
// Search for packages
func (m *Manager) Search(term string) error {
if term == "" {
return fmt.Errorf("no search term specified")
}
return m.execCommand(m.searchCmd, term)
}
// Update package lists
func (m *Manager) Update() error {
return m.execCommand(m.updateCmd)
}
// Upgrade installed packages
func (m *Manager) Upgrade() error {
return m.execCommand(m.upgradeCmd)
}
// ListInstalled lists all installed packages
func (m *Manager) ListInstalled() error {
return m.execCommand(m.listCmd)
}
// ShowInfo shows detailed information about a package
func (m *Manager) ShowInfo(pkg string) error {
return m.execCommand(m.infoCmd, pkg)
}
// AddRepository adds a new repository
func (m *Manager) AddRepository(repo string) error {
return m.execCommand(m.addRepoCmd, repo)
}
// RemoveRepository removes a repository
func (m *Manager) RemoveRepository(repo string) error {
return m.execCommand(m.removeRepoCmd, repo)
}
// ListRepositories lists all configured repositories
func (m *Manager) ListRepositories() error {
return m.execCommand(m.listRepoCmd)
}
// CleanCache cleans the package manager cache
func (m *Manager) CleanCache() error {
return m.execCommand(m.cleanCacheCmd)
}
// AutoRemove removes unused dependencies
func (m *Manager) AutoRemove() error {
return m.execCommand(m.autoRemoveCmd)
}
// CheckDependencies checks for dependency issues
func (m *Manager) CheckDependencies(pkg string) error {
if pkg == "" {
return m.execCommand(m.checkDependsCmd)
}
return m.execCommand(m.checkDependsCmd, pkg)
}
// ShowDependencies shows dependencies for a package
func (m *Manager) ShowDependencies(pkg string) error {
return m.execCommand(m.showDependsCmd, pkg)
}
// DetectPackageManager identifies the system's package manager
func DetectPackageManager() (*Manager, error) {
for _, mgr := range managers {
if _, err := exec.LookPath(mgr.command); err == nil {
return &mgr, nil
}
}
return nil, fmt.Errorf("no supported package manager found")
}
// NeedsSudo returns whether this package manager needs sudo
func (m *Manager) NeedsSudo() bool {
return m.needsSudo && os.Geteuid() != 0
}
func (m *Manager) HandleSource(appName string) error {
fmt.Printf("Searching source code for %s...\n", appName)
s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
s.Start()
repos, err := SearchGitHubSource(appName)
s.Stop()
if err != nil {
return fmt.Errorf("error searching GitHub: %w", err)
}
if len(repos) == 0 {
fmt.Printf("No source code repositories found for '%s'\n", appName)
return nil
}
fmt.Printf("\nFound %d source code repositories:\n\n", len(repos))
for i, repo := range repos {
fmt.Printf("%d. %s\n", i+1, repo.FullName)
if repo.Description != "" {
fmt.Printf(" Description: %s\n", repo.Description)
}
fmt.Printf(" Language: %s\n", repo.Language)
fmt.Printf(" Stars: %d\n", repo.Stars)
fmt.Printf(" URL: %s\n\n", repo.HTMLURL)
}
return nil
}