476 lines
16 KiB
Go
476 lines
16 KiB
Go
package utils
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"slices"
|
|
"strings"
|
|
)
|
|
|
|
type BPMOperation struct {
|
|
Actions []OperationAction
|
|
UnresolvedDepends []string
|
|
RootDir string
|
|
ForceInstallationReason InstallationReason
|
|
}
|
|
|
|
func (operation *BPMOperation) ActionsContainPackage(pkg string) bool {
|
|
for _, action := range operation.Actions {
|
|
if action.GetActionType() == "install" {
|
|
if action.(*InstallPackageAction).BpmPackage.PkgInfo.Name == pkg {
|
|
return true
|
|
}
|
|
} else if action.GetActionType() == "fetch" {
|
|
if action.(*FetchPackageAction).RepositoryEntry.Info.Name == pkg {
|
|
return true
|
|
}
|
|
} else if action.GetActionType() == "remove" {
|
|
if action.(*RemovePackageAction).BpmPackage.PkgInfo.Name == pkg {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (operation *BPMOperation) InsertActionAt(index int, action OperationAction) {
|
|
if len(operation.Actions) == index { // nil or empty slice or after last element
|
|
operation.Actions = append(operation.Actions, action)
|
|
}
|
|
operation.Actions = append(operation.Actions[:index+1], operation.Actions[index:]...) // index < len(a)
|
|
operation.Actions[index] = action
|
|
}
|
|
|
|
func (operation *BPMOperation) RemoveAction(pkg, actionType string) {
|
|
operation.Actions = slices.DeleteFunc(operation.Actions, func(a OperationAction) bool {
|
|
if a.GetActionType() != actionType {
|
|
return false
|
|
}
|
|
if a.GetActionType() == "install" {
|
|
return a.(*InstallPackageAction).BpmPackage.PkgInfo.Name == pkg
|
|
} else if a.GetActionType() == "fetch" {
|
|
return a.(*FetchPackageAction).RepositoryEntry.Info.Name == pkg
|
|
} else if a.GetActionType() == "remove" {
|
|
return a.(*RemovePackageAction).BpmPackage.PkgInfo.Name == pkg
|
|
}
|
|
return false
|
|
})
|
|
}
|
|
|
|
func (operation *BPMOperation) GetTotalDownloadSize() uint64 {
|
|
var ret uint64 = 0
|
|
for _, action := range operation.Actions {
|
|
if action.GetActionType() == "fetch" {
|
|
ret += action.(*FetchPackageAction).RepositoryEntry.DownloadSize
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (operation *BPMOperation) GetTotalInstalledSize() uint64 {
|
|
var ret uint64 = 0
|
|
for _, action := range operation.Actions {
|
|
if action.GetActionType() == "install" {
|
|
ret += action.(*InstallPackageAction).BpmPackage.GetInstalledSize()
|
|
} else if action.GetActionType() == "fetch" {
|
|
ret += action.(*FetchPackageAction).RepositoryEntry.InstalledSize
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (operation *BPMOperation) GetFinalActionSize(rootDir string) int64 {
|
|
var ret int64 = 0
|
|
for _, action := range operation.Actions {
|
|
if action.GetActionType() == "install" {
|
|
ret += int64(action.(*InstallPackageAction).BpmPackage.GetInstalledSize())
|
|
if IsPackageInstalled(action.(*InstallPackageAction).BpmPackage.PkgInfo.Name, rootDir) {
|
|
ret -= int64(GetPackage(action.(*InstallPackageAction).BpmPackage.PkgInfo.Name, rootDir).GetInstalledSize())
|
|
}
|
|
} else if action.GetActionType() == "fetch" {
|
|
ret += int64(action.(*FetchPackageAction).RepositoryEntry.InstalledSize)
|
|
} else if action.GetActionType() == "remove" {
|
|
ret -= int64(action.(*RemovePackageAction).BpmPackage.GetInstalledSize())
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (operation *BPMOperation) ResolveDependencies(reinstallDependencies, installOptionalDependencies, verbose bool) error {
|
|
pos := 0
|
|
for _, value := range slices.Clone(operation.Actions) {
|
|
var pkgInfo *PackageInfo
|
|
if value.GetActionType() == "install" {
|
|
action := value.(*InstallPackageAction)
|
|
pkgInfo = action.BpmPackage.PkgInfo
|
|
} else if value.GetActionType() == "fetch" {
|
|
action := value.(*FetchPackageAction)
|
|
pkgInfo = action.RepositoryEntry.Info
|
|
} else {
|
|
pos++
|
|
continue
|
|
}
|
|
|
|
resolved, unresolved := pkgInfo.ResolveDependencies(&[]string{}, &[]string{}, pkgInfo.Type == "source", installOptionalDependencies, !reinstallDependencies, verbose, operation.RootDir)
|
|
|
|
operation.UnresolvedDepends = append(operation.UnresolvedDepends, unresolved...)
|
|
|
|
for _, depend := range resolved {
|
|
if !operation.ActionsContainPackage(depend) && depend != pkgInfo.Name {
|
|
if !reinstallDependencies && IsPackageInstalled(depend, operation.RootDir) {
|
|
continue
|
|
}
|
|
entry, _, err := GetRepositoryEntry(depend)
|
|
if err != nil {
|
|
return errors.New("could not get repository entry for package (" + depend + ")")
|
|
}
|
|
operation.InsertActionAt(pos, &FetchPackageAction{
|
|
IsDependency: true,
|
|
RepositoryEntry: entry,
|
|
})
|
|
pos++
|
|
}
|
|
}
|
|
pos++
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (operation *BPMOperation) RemoveNeededPackages() error {
|
|
removeActions := make(map[string]*RemovePackageAction)
|
|
for _, action := range slices.Clone(operation.Actions) {
|
|
if action.GetActionType() == "remove" {
|
|
removeActions[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = action.(*RemovePackageAction)
|
|
}
|
|
}
|
|
|
|
for pkg, action := range removeActions {
|
|
dependants, err := action.BpmPackage.PkgInfo.GetDependants(operation.RootDir)
|
|
if err != nil {
|
|
return errors.New("could not get dependant packages for package (" + pkg + ")")
|
|
}
|
|
dependants = slices.DeleteFunc(dependants, func(d string) bool {
|
|
if _, ok := removeActions[d]; ok {
|
|
return true
|
|
}
|
|
return false
|
|
})
|
|
if len(dependants) != 0 {
|
|
operation.RemoveAction(pkg, action.GetActionType())
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (operation *BPMOperation) Cleanup(verbose bool) error {
|
|
// Get all installed packages
|
|
installedPackageNames, err := GetInstalledPackages(operation.RootDir)
|
|
if err != nil {
|
|
log.Fatalf("Error: could not get installed packages: %s\n", err)
|
|
}
|
|
installedPackages := make([]*PackageInfo, len(installedPackageNames))
|
|
for i, value := range installedPackageNames {
|
|
bpmpkg := GetPackage(value, operation.RootDir)
|
|
if bpmpkg == nil {
|
|
return errors.New("could not find installed package (" + value + ")")
|
|
}
|
|
installedPackages[i] = bpmpkg.PkgInfo
|
|
}
|
|
|
|
// Get packages to remove
|
|
removeActions := make(map[string]*RemovePackageAction)
|
|
for _, action := range slices.Clone(operation.Actions) {
|
|
if action.GetActionType() == "remove" {
|
|
removeActions[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = action.(*RemovePackageAction)
|
|
}
|
|
}
|
|
|
|
// Get manually installed packages, resolve all their dependencies and add them to the keepPackages slice
|
|
keepPackages := make([]string, 0)
|
|
for _, pkg := range slices.Clone(installedPackages) {
|
|
if GetInstallationReason(pkg.Name, operation.RootDir) != Manual {
|
|
continue
|
|
}
|
|
|
|
// Do not resolve dependencies or add package to keepPackages slice if package removal action exists for it
|
|
if _, ok := removeActions[pkg.Name]; ok {
|
|
continue
|
|
}
|
|
|
|
keepPackages = append(keepPackages, pkg.Name)
|
|
resolved, _ := pkg.ResolveDependencies(&[]string{}, &[]string{}, false, true, false, verbose, operation.RootDir)
|
|
for _, value := range resolved {
|
|
if !slices.Contains(keepPackages, value) && slices.Contains(installedPackageNames, value) {
|
|
keepPackages = append(keepPackages, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get all installed packages that are not in the keepPackages slice and add them to the BPM operation
|
|
for _, pkg := range installedPackageNames {
|
|
// Do not add package removal action if there already is one
|
|
if _, ok := removeActions[pkg]; ok {
|
|
continue
|
|
}
|
|
if !slices.Contains(keepPackages, pkg) {
|
|
bpmpkg := GetPackage(pkg, operation.RootDir)
|
|
if bpmpkg == nil {
|
|
return errors.New("Error: could not find installed package (" + pkg + ")")
|
|
}
|
|
operation.Actions = append(operation.Actions, &RemovePackageAction{BpmPackage: bpmpkg})
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (operation *BPMOperation) ReplaceObsoletePackages() {
|
|
for _, value := range slices.Clone(operation.Actions) {
|
|
var pkgInfo *PackageInfo
|
|
if value.GetActionType() == "install" {
|
|
action := value.(*InstallPackageAction)
|
|
pkgInfo = action.BpmPackage.PkgInfo
|
|
|
|
} else if value.GetActionType() == "fetch" {
|
|
action := value.(*FetchPackageAction)
|
|
pkgInfo = action.RepositoryEntry.Info
|
|
} else {
|
|
continue
|
|
}
|
|
|
|
for _, r := range pkgInfo.Replaces {
|
|
if bpmpkg := GetPackage(r, operation.RootDir); bpmpkg != nil && !operation.ActionsContainPackage(bpmpkg.PkgInfo.Name) {
|
|
operation.InsertActionAt(0, &RemovePackageAction{
|
|
BpmPackage: bpmpkg,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (operation *BPMOperation) CheckForConflicts() (map[string][]string, error) {
|
|
conflicts := make(map[string][]string)
|
|
installedPackages, err := GetInstalledPackages(operation.RootDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
allPackages := make([]*PackageInfo, len(installedPackages))
|
|
for i, value := range installedPackages {
|
|
bpmpkg := GetPackage(value, operation.RootDir)
|
|
if bpmpkg == nil {
|
|
return nil, errors.New(fmt.Sprintf("could not find installed package (%s)", value))
|
|
}
|
|
allPackages[i] = bpmpkg.PkgInfo
|
|
}
|
|
|
|
// Add all new packages to the allPackages slice
|
|
for _, value := range slices.Clone(operation.Actions) {
|
|
if value.GetActionType() == "install" {
|
|
action := value.(*InstallPackageAction)
|
|
pkgInfo := action.BpmPackage.PkgInfo
|
|
allPackages = append(allPackages, pkgInfo)
|
|
} else if value.GetActionType() == "fetch" {
|
|
action := value.(*FetchPackageAction)
|
|
pkgInfo := action.RepositoryEntry.Info
|
|
allPackages = append(allPackages, pkgInfo)
|
|
} else if value.GetActionType() == "remove" {
|
|
action := value.(*RemovePackageAction)
|
|
pkgInfo := action.BpmPackage.PkgInfo
|
|
for i := len(allPackages) - 1; i >= 0; i-- {
|
|
info := allPackages[i]
|
|
if info.Name == pkgInfo.Name {
|
|
allPackages = append(allPackages[:i], allPackages[i+1:]...)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, value := range allPackages {
|
|
for _, conflict := range value.Conflicts {
|
|
if slices.ContainsFunc(allPackages, func(info *PackageInfo) bool {
|
|
return info.Name == conflict
|
|
}) {
|
|
conflicts[value.Name] = append(conflicts[value.Name], conflict)
|
|
}
|
|
}
|
|
}
|
|
|
|
return conflicts, nil
|
|
}
|
|
|
|
func (operation *BPMOperation) ShowOperationSummary() {
|
|
if len(operation.Actions) == 0 {
|
|
fmt.Println("All packages are up to date!")
|
|
os.Exit(0)
|
|
}
|
|
|
|
for _, value := range operation.Actions {
|
|
var pkgInfo *PackageInfo
|
|
if value.GetActionType() == "install" {
|
|
pkgInfo = value.(*InstallPackageAction).BpmPackage.PkgInfo
|
|
} else if value.GetActionType() == "fetch" {
|
|
pkgInfo = value.(*FetchPackageAction).RepositoryEntry.Info
|
|
} else {
|
|
pkgInfo = value.(*RemovePackageAction).BpmPackage.PkgInfo
|
|
fmt.Printf("%s: %s (Remove)\n", pkgInfo.Name, pkgInfo.GetFullVersion())
|
|
continue
|
|
}
|
|
|
|
installedInfo := GetPackageInfo(pkgInfo.Name, operation.RootDir)
|
|
sourceInfo := ""
|
|
if pkgInfo.Type == "source" {
|
|
if operation.RootDir != "/" {
|
|
log.Fatalf("cannot compile and install source packages to a different root directory")
|
|
}
|
|
sourceInfo = "(From Source)"
|
|
}
|
|
|
|
if installedInfo == nil {
|
|
fmt.Printf("%s: %s (Install) %s\n", pkgInfo.Name, pkgInfo.GetFullVersion(), sourceInfo)
|
|
} else {
|
|
comparison := ComparePackageVersions(*pkgInfo, *installedInfo)
|
|
if comparison < 0 {
|
|
fmt.Printf("%s: %s -> %s (Downgrade) %s\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), sourceInfo)
|
|
} else if comparison > 0 {
|
|
fmt.Printf("%s: %s -> %s (Upgrade) %s\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), sourceInfo)
|
|
} else {
|
|
fmt.Printf("%s: %s (Reinstall) %s\n", pkgInfo.Name, pkgInfo.GetFullVersion(), sourceInfo)
|
|
}
|
|
}
|
|
}
|
|
|
|
if operation.RootDir != "/" {
|
|
fmt.Println("Warning: Operating in " + operation.RootDir)
|
|
}
|
|
if operation.GetTotalDownloadSize() > 0 {
|
|
fmt.Printf("%s will be downloaded to complete this operation\n", UnsignedBytesToHumanReadable(operation.GetTotalDownloadSize()))
|
|
}
|
|
if operation.GetFinalActionSize(operation.RootDir) > 0 {
|
|
fmt.Printf("A total of %s will be installed after the operation finishes\n", BytesToHumanReadable(operation.GetFinalActionSize(operation.RootDir)))
|
|
} else if operation.GetFinalActionSize(operation.RootDir) < 0 {
|
|
fmt.Printf("A total of %s will be freed after the operation finishes\n", strings.TrimPrefix(BytesToHumanReadable(operation.GetFinalActionSize(operation.RootDir)), "-"))
|
|
}
|
|
}
|
|
|
|
func (operation *BPMOperation) Execute(verbose, force bool) error {
|
|
// Fetch packages from repositories
|
|
if slices.ContainsFunc(operation.Actions, func(action OperationAction) bool {
|
|
return action.GetActionType() == "fetch"
|
|
}) {
|
|
fmt.Println("Fetching packages from available repositories...")
|
|
for i, action := range operation.Actions {
|
|
if action.GetActionType() != "fetch" {
|
|
continue
|
|
}
|
|
entry := action.(*FetchPackageAction).RepositoryEntry
|
|
fetchedPackage, err := entry.Repository.FetchPackage(entry.Info.Name)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("could not fetch package (%s): %s\n", entry.Info.Name, err))
|
|
}
|
|
bpmpkg, err := ReadPackage(fetchedPackage)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("could not fetch package (%s): %s\n", entry.Info.Name, err))
|
|
}
|
|
fmt.Printf("Package (%s) was successfully fetched!\n", bpmpkg.PkgInfo.Name)
|
|
operation.Actions[i] = &InstallPackageAction{
|
|
File: fetchedPackage,
|
|
IsDependency: action.(*FetchPackageAction).IsDependency,
|
|
BpmPackage: bpmpkg,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine words to be used for the following message
|
|
words := make([]string, 0)
|
|
if slices.ContainsFunc(operation.Actions, func(action OperationAction) bool {
|
|
return action.GetActionType() == "install"
|
|
}) {
|
|
words = append(words, "Installing")
|
|
}
|
|
|
|
if slices.ContainsFunc(operation.Actions, func(action OperationAction) bool {
|
|
return action.GetActionType() == "remove"
|
|
}) {
|
|
words = append(words, "Removing")
|
|
}
|
|
|
|
if len(words) == 0 {
|
|
return nil
|
|
}
|
|
fmt.Printf("%s packages...\n", strings.Join(words, "/"))
|
|
|
|
// Installing/Removing packages from system
|
|
for _, action := range operation.Actions {
|
|
if action.GetActionType() == "remove" {
|
|
pkgInfo := action.(*RemovePackageAction).BpmPackage.PkgInfo
|
|
err := RemovePackage(pkgInfo.Name, verbose, operation.RootDir)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("could not remove package (%s): %s\n", pkgInfo.Name, err))
|
|
}
|
|
} else if action.GetActionType() == "install" {
|
|
value := action.(*InstallPackageAction)
|
|
bpmpkg := value.BpmPackage
|
|
isReinstall := IsPackageInstalled(bpmpkg.PkgInfo.Name, operation.RootDir)
|
|
var err error
|
|
if value.IsDependency {
|
|
err = InstallPackage(value.File, operation.RootDir, verbose, true, false, false, false)
|
|
} else {
|
|
err = InstallPackage(value.File, operation.RootDir, verbose, force, false, false, false)
|
|
}
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("could not install package (%s): %s\n", bpmpkg.PkgInfo.Name, err))
|
|
}
|
|
if operation.ForceInstallationReason != Unknown && !value.IsDependency {
|
|
err := SetInstallationReason(bpmpkg.PkgInfo.Name, operation.ForceInstallationReason, operation.RootDir)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("could not set installation reason for package (%s): %s\n", value.BpmPackage.PkgInfo.Name, err))
|
|
}
|
|
} else if value.IsDependency && !isReinstall {
|
|
err := SetInstallationReason(bpmpkg.PkgInfo.Name, Dependency, operation.RootDir)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("could not set installation reason for package (%s): %s\n", value.BpmPackage.PkgInfo.Name, err))
|
|
}
|
|
}
|
|
fmt.Printf("Package (%s) was successfully installed\n", bpmpkg.PkgInfo.Name)
|
|
}
|
|
}
|
|
fmt.Println("Operation complete!")
|
|
|
|
return nil
|
|
}
|
|
|
|
type OperationAction interface {
|
|
GetActionType() string
|
|
}
|
|
|
|
type InstallPackageAction struct {
|
|
File string
|
|
IsDependency bool
|
|
BpmPackage *BPMPackage
|
|
}
|
|
|
|
func (action *InstallPackageAction) GetActionType() string {
|
|
return "install"
|
|
}
|
|
|
|
type FetchPackageAction struct {
|
|
IsDependency bool
|
|
RepositoryEntry *RepositoryEntry
|
|
}
|
|
|
|
func (action *FetchPackageAction) GetActionType() string {
|
|
return "fetch"
|
|
}
|
|
|
|
type RemovePackageAction struct {
|
|
BpmPackage *BPMPackage
|
|
}
|
|
|
|
func (action *RemovePackageAction) GetActionType() string {
|
|
return "remove"
|
|
}
|