package main import ( "bufio" "bytes" "eon/common" "eon/lib" "fmt" "io" "os" "regexp" "sort" "strings" "math/big" "net/http" "net/url" "golang.org/x/sys/unix" "golang.org/x/term" "github.com/dustin/go-humanize" "github.com/fatih/color" ) func main() { if len(os.Args) < 2 { fmt.Println("Usage: eon [args]") os.Exit(1) } logger := &common.DefaultLogger for i, arg := range os.Args { if arg == "--help" || arg == "-h" || (i != 1 && arg == "help") { logger.LogFunc(lib.Log{ Level: "FATAL", Content: "To ask for help, use 'eon help ', not --help, -h, or eon help.", }) } } switch os.Args[1] { case "help": if len(os.Args) < 3 { fmt.Println("Usage: eon help (list/info/install/remove/clean/repo/help) [args]") os.Exit(1) } else { switch os.Args[2] { case "list": fmt.Println("Usage: eon list (repo/local)") fmt.Println("- repo: lists packages available in repositories.") fmt.Println("- local: lists installed packages.") case "info": fmt.Println("Usage: eon info [(repo/local)] []") fmt.Println("Shows information about a package.") case "install": fmt.Println("Usage: eon install []") fmt.Println("Installs a package.") case "remove": fmt.Println("Usage: eon remove ") fmt.Println("Removes a package.") case "clean": fmt.Println("Usage: eon clean") fmt.Println("Removes unused dependencies.") case "repo": fmt.Println("Usage: eon repo (add/del/list)") fmt.Println("- add : adds a repository.") fmt.Println("- del : removes a repository.") fmt.Println("- list: lists repositories.") case "help": fmt.Println("Usage: eon help (list/info/install/remove/clean/repo/help) [args]") fmt.Println("Shows help.") default: fmt.Println(color.RedString("Unknown command: " + os.Args[2])) os.Exit(1) } } case "install": var forceMode bool var yesMode bool var inMemoryMode bool if len(os.Args) < 3 { fmt.Println("Usage: eon install ") os.Exit(1) } var localPackageList []string fileInfos := make(map[string]os.FileInfo) var repoPackageList []string for _, pkg := range os.Args[2:] { if !strings.HasPrefix(pkg, "-") { fileInfo, err := os.Stat(pkg) if err != nil || fileInfo.IsDir() { if os.IsNotExist(err) || fileInfo.IsDir() { // This unholy regex is a package name validator. It validates repository-name/package-name/1.0.0-prerelease.1+meta.1 // The regex is so unholy it breaks JetBrains' regex parser, so I had to disable it. //goland:noinspection RegExpUnnecessaryNonCapturingGroup match, err := regexp.Match(`^(?:([a-z0-9_-]+)/)?([a-z0-9_-]+)(?:/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)?$`, []byte(pkg)) if err != nil { fmt.Println(color.RedString("Failed to validate package name: " + err.Error())) os.Exit(1) } if !match { fmt.Println(color.RedString("Invalid package name: " + pkg)) os.Exit(1) } else { repoPackageList = append(repoPackageList, pkg) } } else { fmt.Println(color.RedString("Failed to get file information: " + err.Error())) os.Exit(1) } } else { localPackageList = append(localPackageList, pkg) fileInfos[pkg] = fileInfo } } else { switch pkg { case "--force", "-f": forceMode = true case "--optimizeForSpeed", "-O": inMemoryMode = true case "--yes", "-y": yesMode = true } } } // We contact the database before we print anything as to not break the formatting. err := common.EstablishDBConnection(logger) if err != nil { fmt.Println(color.RedString("Failed to establish a connection to the database: " + err.Error())) os.Exit(1) } // We refresh the package list. This takes a lot of arguments, haha. err = common.RefreshPackageList(common.DefaultListRepositoriesInDB, common.DefaultAddRemotePackageToDB, common.DefaultGetFingerprintFromDB, common.DefaultAddFingerprintToDB, common.DefaultCheckRepositoryInDB, common.DefaultAddRepositoryToDB, common.DefaultListRemotePackagesInDB, common.DefaultRemoveRemotePackageFromDB, logger) if err != nil { fmt.Println(color.RedString("Failed to refresh package list: " + err.Error())) os.Exit(1) } // Let's start printing things. if len(localPackageList) > 0 || len(repoPackageList) > 0 { var epkList []common.InstallPackage var skipEpkList [][]string var dependencies int localPackageListIteration: for _, pkg := range localPackageList { // Check if the package is already in epkList. for _, epk := range epkList { if epk.EPKPreMap.DisplayData.Name == pkg { continue localPackageListIteration } } epkFile, err := os.Open(pkg) if err != nil { fmt.Println(color.RedString("Failed to open package: " + err.Error())) os.Exit(1) } var preMap lib.EPKPreMap var vagueErr error var epkBytes bytes.Buffer if !inMemoryMode { preMap, err, vagueErr = lib.PreMapEPK(lib.StreamOrBytes{FileStream: epkFile, IsFileStream: true}, fileInfos[pkg].Size()) } else { _, err = io.Copy(bufio.NewWriter(&epkBytes), epkFile) if err != nil { fmt.Println(color.RedString("Failed to read package into memory: " + err.Error() + ", have you tried disabling in-memory mode?")) os.Exit(1) } err := epkFile.Close() if err != nil { fmt.Println(color.HiYellowString("Failed to close package file: " + err.Error() + ", memory leak possible.")) } preMap, err, vagueErr = lib.PreMapEPK(lib.StreamOrBytes{Bytes: epkBytes.Bytes()}, fileInfos[pkg].Size()) } if err != nil || vagueErr != nil { common.PreMapEPKHandleError(err, vagueErr, logger) os.Exit(1) } // Check if the architecture is supported. var uts unix.Utsname err = unix.Uname(&uts) if err != nil { fmt.Println(color.RedString("Failed to get system architecture: " + err.Error())) os.Exit(1) } // Check if the architecture is supported. if preMap.DisplayData.Architecture != "noarch" || preMap.DisplayData.Architecture != string(uts.Machine[:]) { fmt.Println(color.RedString("Package architecture not supported: " + preMap.DisplayData.Architecture)) os.Exit(1) } preMap.DisplayData.IsDependency = false var installPackage common.InstallPackage installPackage.EPKPreMap = &preMap installPackage.Url = "" installPackage.Priority = 0 installPackage.IsRemote = false installPackage.IsForced = false if !inMemoryMode { installPackage.StreamOrBytes = lib.StreamOrBytes{ FileStream: epkFile, IsFileStream: true, } } else { installPackage.StreamOrBytes = lib.StreamOrBytes{ Bytes: epkBytes.Bytes(), } } installPackage.Repository = lib.Repository{Name: "Local file"} preMap.IsUpgrade = true version, exists, err := common.DefaultCheckEPKInDB(preMap.DisplayData.Name) if err != nil { fmt.Println(color.RedString("Failed to check for package in database: " + err.Error())) } if exists { if version.Compare(&preMap.DisplayData.Version) != -1 { if !forceMode { skipEpk := []string{preMap.DisplayData.Name, preMap.DisplayData.Architecture, preMap.DisplayData.Version.String(), "Local file", humanize.BigIBytes(preMap.DisplayData.DecompressedSize), "Skip"} skipEpkList = append(skipEpkList, skipEpk) } else { installPackage.IsForced = true var emptyList []string var addedDeps int epkList, addedDeps, err = common.HandleDependencies(dependencies, installPackage, 0, []lib.RemoteEPK{}, common.DefaultListRemotePackagesInDB, epkList, &emptyList, logger) if err != nil { fmt.Println(color.RedString("Failed to handle dependencies: " + err.Error())) os.Exit(1) } else { dependencies += addedDeps } epkList = append(epkList, installPackage) } } else { var emptyList []string var addedDeps int epkList, addedDeps, err = common.HandleDependencies(dependencies, installPackage, 0, []lib.RemoteEPK{}, common.DefaultListRemotePackagesInDB, epkList, &emptyList, logger) if err != nil { fmt.Println(color.RedString("Failed to handle dependencies: " + err.Error())) os.Exit(1) } else { dependencies += addedDeps } epkList = append(epkList, installPackage) } } else { preMap.IsUpgrade = false var emptyList []string var addedDeps int epkList, addedDeps, err = common.HandleDependencies(dependencies, installPackage, 0, []lib.RemoteEPK{}, common.DefaultListRemotePackagesInDB, epkList, &emptyList, logger) if err != nil { fmt.Println(color.RedString("Failed to handle dependencies: " + err.Error())) os.Exit(1) } else { dependencies += addedDeps } epkList = append(epkList, installPackage) } } repoPackageListIteration: for _, pkg := range repoPackageList { // Check if the package is already in epkList. for _, epk := range epkList { if epk.EPKPreMap.DisplayData.Name == pkg { continue repoPackageListIteration } } // Check if the package is already installed. version, exists, err := common.DefaultCheckEPKInDB(pkg) if err != nil { fmt.Println(color.RedString("Failed to check for package in database: " + err.Error())) os.Exit(1) } var remoteEPK lib.RemoteEPK var epkEntry common.InstallPackage remoteEpkList, err := common.DefaultListRemotePackagesInDB() if err != nil { fmt.Println(color.RedString("Failed to list remote packages: " + err.Error())) os.Exit(1) } epkExists := false for _, epk := range remoteEpkList { if epk.Name == pkg { remoteEPK = epk epkExists = true break } } if !epkExists { fmt.Println(color.RedString("Package not found: " + pkg)) os.Exit(1) } // Calculate the download URL epkDownloadUrl, err := url.JoinPath(remoteEPK.Repository.URL, remoteEPK.Path) if err != nil { fmt.Println(color.RedString("Failed to join URL: " + err.Error())) os.Exit(1) } // Pre-map the EPK preMap, err, vagueErr := lib.PreMapRemoteEPK(remoteEPK, logger) if err != nil || vagueErr != nil { common.PreMapRemoteEPKHandleError(err, vagueErr, logger) os.Exit(1) } preMap.DisplayData.IsDependency = false // Map the data we have epkEntry.Priority = 0 epkEntry.EPKPreMap = &preMap epkEntry.Url = epkDownloadUrl epkEntry.IsRemote = true epkEntry.Repository = remoteEPK.Repository if !inMemoryMode { epkEntry.StreamOrBytes.IsURL = true epkEntry.StreamOrBytes.URL = epkDownloadUrl epkEntry.StreamOrBytes.RepositoryName = remoteEPK.Repository.Name } else { // Download the entire EPK into memory epkBytes, err := http.Get(epkDownloadUrl) if err != nil { fmt.Println(color.RedString("Failed to download package: " + err.Error())) os.Exit(1) } if epkBytes.StatusCode != 200 { fmt.Println(color.RedString("Failed to download package: " + epkBytes.Status)) os.Exit(1) } // Calculate the total size of the package contentLength := new(big.Int) contentLength.SetString(epkBytes.Header.Get("Content-Length"), 10) // Print that we are downloading the package fmt.Println("\nDownloading package: " + preMap.DisplayData.Name + " (" + humanize.BigIBytes(contentLength) + ")") // Hide the cursor for reasons explained below. fmt.Print("\033[?25l") // Copy the stream into a buffer var buffer bytes.Buffer _, err = io.Copy(&lib.ProgressWriter{ Logger: logger, Total: contentLength, Writer: &buffer, }, epkBytes.Body) if err != nil { fmt.Println(color.RedString("Failed to read package into memory: " + err.Error())) os.Exit(1) } // Set the progress to 100% logger.LogFunc(lib.Log{ Level: "PROGRESS", Progress: big.NewInt(1), Total: big.NewInt(1), }) // Show the cursor again because we're done with the progress bar. fmt.Print("\033[?25h") // Close the response body err = epkBytes.Body.Close() if err != nil { fmt.Println(color.HiYellowString("Failed to close response body: " + err.Error() + ", memory leak possible.")) } // Set the buffer as the bytes epkEntry.StreamOrBytes.Bytes = buffer.Bytes() epkEntry.StreamOrBytes.IsURL = false epkEntry.StreamOrBytes.IsFileStream = false // Reset the buffer buffer.Reset() } // Make decisions on what happens if the package is already installed. if exists { if version.Compare(&preMap.DisplayData.Version) != -1 { // If the version is the same or newer, skip the package. if !forceMode { skipEpkList = append(skipEpkList, []string{preMap.DisplayData.Name, preMap.DisplayData.Architecture, preMap.DisplayData.Version.String(), remoteEPK.Repository.Name, humanize.BigIBytes(preMap.DisplayData.DecompressedSize), "Skip"}) } else { // If the version is the same or newer, but the user wants to force the installation, install it. epkEntry.IsForced = true // We can let it use our remoteEPKList to save a SQL query. var emptyList []string var addedDeps int epkList, addedDeps, err = common.HandleDependencies(dependencies, epkEntry, 0, remoteEpkList, common.DefaultListRemotePackagesInDB, epkList, &emptyList, logger) if err != nil { fmt.Println(color.RedString("Failed to handle dependencies: " + err.Error())) os.Exit(1) } else { // We add the dependencies to the total count. dependencies += addedDeps } epkList = append(epkList, epkEntry) } } else { // If the version is older, install it. var emptyList []string var addedDeps int epkList, addedDeps, err = common.HandleDependencies(dependencies, epkEntry, 0, remoteEpkList, common.DefaultListRemotePackagesInDB, epkList, &emptyList, logger) if err != nil { fmt.Println(color.RedString("Failed to handle dependencies: " + err.Error())) os.Exit(1) } else { dependencies += addedDeps } epkList = append(epkList, epkEntry) } } else { // If the package is not installed, install it. var emptyList []string var addedDeps int epkList, addedDeps, err = common.HandleDependencies(dependencies, epkEntry, 0, remoteEpkList, common.DefaultListRemotePackagesInDB, epkList, &emptyList, logger) if err != nil { fmt.Println(color.RedString("Failed to handle dependencies: " + err.Error())) os.Exit(1) } else { dependencies += addedDeps } epkList = append(epkList, epkEntry) } } // Sort the list of packages to install by priority, then alphabetically. sort.Slice(epkList, func(i, j int) bool { if epkList[i].Priority == epkList[j].Priority { return epkList[i].EPKPreMap.DisplayData.Name < epkList[j].EPKPreMap.DisplayData.Name } return epkList[i].Priority > epkList[j].Priority }) // Give the summary of the installation. fmt.Println("\nThe following packages will be installed:") width, _, err := term.GetSize(int(os.Stdout.Fd())) if err != nil { fmt.Println(color.RedString("Failed to get terminal width: " + err.Error())) os.Exit(1) } if width < 42 { fmt.Println(color.RedString("Terminal too small. Minimum required width: 42 characters.")) os.Exit(1) } for range width { fmt.Print("=") } fmt.Println() tableList := []string{"Package", "Architecture", "Version", "Repository", "Installed Size", "Action"} maxSize := width / 6 for _, item := range tableList { common.PrintWithEvenPadding(item, maxSize) } fmt.Println() for range width { fmt.Print("=") } fmt.Println() for _, pkg := range skipEpkList { for _, item := range pkg { common.PrintWithEvenPadding(color.GreenString(item), maxSize) } } for _, pkg := range epkList { finalisedList := make([]string, 6) finalisedList[0] = pkg.EPKPreMap.DisplayData.Name finalisedList[1] = pkg.EPKPreMap.DisplayData.Architecture finalisedList[2] = pkg.EPKPreMap.DisplayData.Version.String() if !pkg.IsRemote { finalisedList[3] = "Local file" } else { finalisedList[3] = pkg.Repository.Name } finalisedList[4] = humanize.BigIBytes(pkg.EPKPreMap.DisplayData.DecompressedSize) if pkg.IsForced { finalisedList[5] = "Forced installation" } else if pkg.EPKPreMap.IsUpgrade { finalisedList[5] = "Upgrade" } else { finalisedList[5] = "Install" } for _, item := range finalisedList { common.PrintWithEvenPadding(item, maxSize) } } if len(epkList) > 0 { for range width { fmt.Print("=") } fmt.Println("Transaction Summary") fmt.Println("\nInstalling " + humanize.Comma(int64(len(epkList))) + " packages, of which " + humanize.Comma(int64(dependencies)) + " are dependencies.") fmt.Println("Total download size: " + humanize.BigIBytes(common.GetTotalSize(epkList))) fmt.Println("Total installed size: " + humanize.BigIBytes(common.GetTotalInstalledSize(epkList)) + "\n") var response string if !yesMode { response = logger.LogFunc(lib.Log{ Level: "INFO", Content: "Proceed with installation (y/n)?", Prompt: true, PlaySound: true, }) } if strings.ToLower(response) == "y" || yesMode { // We hide the cursor because it makes the progress bar look weird and other package managers hide // it during installation. For some reason, it builds suspense. Or it does with me anyway, when // a program hides the cursor, it makes me think twice than to Ctrl+C it :P // - Arzumify fmt.Print("\033[?25l") // Time to install things. for _, installPackage := range epkList { if installPackage.IsRemote { if !inMemoryMode { // Set the package stream to the URL. installPackage.StreamOrBytes.IsURL = true installPackage.StreamOrBytes.IsRemote = true installPackage.StreamOrBytes.RepositoryName = installPackage.Repository.Name installPackage.StreamOrBytes.IsFileStream = false installPackage.StreamOrBytes.URL = installPackage.Url } else { // Download the entire EPK into memory epkBytes, err := http.Get(installPackage.Url) if err != nil { fmt.Println(color.RedString("Failed to download package: " + err.Error())) os.Exit(1) } if epkBytes.StatusCode != 200 { fmt.Println(color.RedString("Failed to download package: " + epkBytes.Status)) os.Exit(1) } // Start streaming the package into a buffer contentLength := new(big.Int).SetBytes([]byte(epkBytes.Header.Get("Content-Length"))) fmt.Println("\nDownloading package: " + installPackage.EPKPreMap.DisplayData.Name + " (" + humanize.BigIBytes(contentLength) + ")") var buffer bytes.Buffer _, err = io.Copy(&lib.ProgressWriter{ Logger: logger, Total: contentLength, Writer: &buffer, }, epkBytes.Body) if err != nil { fmt.Println(color.RedString("Failed to read package into memory: " + err.Error())) os.Exit(1) } // Set the progress to 100% logger.LogFunc(lib.Log{ Level: "PROGRESS", Progress: big.NewInt(1), Total: big.NewInt(1), }) // Close the response body err = epkBytes.Body.Close() if err != nil { fmt.Println(color.HiYellowString("Failed to close response body: " + err.Error() + ", memory leak possible.")) } fmt.Println("Package downloaded") // Set the buffer as the bytes installPackage.StreamOrBytes.Bytes = buffer.Bytes() installPackage.StreamOrBytes.IsURL = false installPackage.StreamOrBytes.IsRemote = true installPackage.StreamOrBytes.RepositoryName = installPackage.Repository.Name installPackage.StreamOrBytes.IsFileStream = false } } // Map the EPK metadata. metadata, err, vagueErr := lib.FullyMapMetadata(installPackage.StreamOrBytes, installPackage.EPKPreMap, common.DefaultGetFingerprintFromDB, common.DefaultAddFingerprintToDB, func(*lib.Logger) { logger.LogFunc(lib.Log{ Level: "WARN", Content: "This server does not support range requests. Please use the -O flag to " + "enable in memory mode, or contact the server administrator to use a web server " + "that supports range requests, such as Apache or Nginx. The speed of download " + "is considerably slowed, as we need to discard bytes since we can only read " + "sequentially.", Prompt: false, }) }, logger) if err != nil || vagueErr != nil { common.FullyMapMetadataHandleError(err, vagueErr, logger) } // Install the package. tempDir, err, vagueErr := lib.InstallEPK(installPackage.StreamOrBytes, metadata, installPackage.EPKPreMap, common.DefaultAddEPKToDB, logger) if err != nil || vagueErr != nil { common.InstallEPKHandleError(tempDir, err, vagueErr, logger) } // Done! fmt.Println("Installed package: " + installPackage.EPKPreMap.DisplayData.Name) } // We show the cursor again because we're done with the progress bar. fmt.Print("\033[?25h") } else { fmt.Println("Installation cancelled.") os.Exit(1) } } else { for range width { fmt.Print("=") } fmt.Println("No packages left to install. To force re-install / downgrade, use the --force flag, " + "though this may result in breakage.") os.Exit(0) } } else { fmt.Println("No packages to install.") os.Exit(0) } case "repo": if len(os.Args) < 3 { fmt.Println("Usage: eon repo (add/del/list)") os.Exit(1) } switch os.Args[2] { case "add": if len(os.Args) < 4 { fmt.Println("Usage: eon repo add ") os.Exit(1) } else { err := common.EstablishDBConnection(logger) if err != nil { fmt.Println(color.RedString("Failed to establish a connection to the database: " + err.Error())) os.Exit(1) } repoName, err, vagueErr := lib.AddRepository(os.Args[3], common.DefaultAddRepositoryToDB, common.DefaultGetFingerprintFromDB, common.DefaultAddFingerprintToDB, common.DefaultAddRemotePackageToDB, common.DefaultCheckRepositoryInDB, false, logger) if err != nil || vagueErr != nil { common.AddRepositoryHandleError(err, vagueErr, logger) } else { fmt.Println("Added repository " + repoName + " to the database.") } } case "del": if len(os.Args) < 4 { fmt.Println("Usage: eon repo del ") os.Exit(1) } else { err := common.EstablishDBConnection(logger) if err != nil { fmt.Println(color.RedString("Failed to establish a connection to the database: " + err.Error())) os.Exit(1) } err, vagueErr := lib.RemoveRepository(os.Args[3], common.DefaultRemoveRepositoryFromDB, common.DefaultCheckRepositoryInDB, logger) if err != nil || vagueErr != nil { common.RemoveRepositoryHandleError(err, vagueErr, logger) } } case "list": err := common.EstablishDBConnection(logger) if err != nil { fmt.Println(color.RedString("Failed to establish a connection to the database: " + err.Error())) os.Exit(1) } repos, err := common.DefaultListRepositoriesInDB() if err != nil { fmt.Println(color.RedString("Failed to get repositories: " + err.Error())) os.Exit(1) } if len(repos) == 0 { fmt.Println("No repositories.") } else { fmt.Println("Repositories:") for _, repo := range repos { fmt.Println("\n" + repo.Name + ":\n" + "- " + repo.Description + "\n- URL: " + repo.URL + "\n- Owner: " + repo.Owner) } fmt.Println() } } case "remove": if len(os.Args) < 3 { fmt.Println("Usage: eon remove ") os.Exit(1) } err := common.EstablishDBConnection(logger) if err != nil { fmt.Println(color.RedString("Failed to establish a connection to the database: " + err.Error())) os.Exit(1) } // Create a map of all installed packages, to minimise the database queries later. epkMap, decompressedSizeMap, repositoryMap, _, err := common.DefaultListEPKsInDB() if err != nil { fmt.Println(color.RedString("Failed to list installed packages: " + err.Error())) os.Exit(1) } // Create a list of packages to remove. var packageRemoveList []common.RemovePackage for _, pkg := range os.Args[2:] { var removePackage common.RemovePackage _, exists, err := common.DefaultCheckEPKInDB(pkg) if err != nil { fmt.Println(color.RedString("Failed to check for package in database: " + err.Error())) os.Exit(1) } if !exists { fmt.Println(color.RedString("Package not found: " + pkg)) os.Exit(1) } // Search to see if it's parent is still installed. for _, epk := range epkMap { for _, dep := range epk.Dependencies { if dep == os.Args[2] { // First, check if it's in the list of packages to remove. var found bool for _, epkName := range os.Args[2:] { if epkName == epk.Name { found = true break } } if !found { fmt.Println(color.RedString("Package " + pkg + " is a dependency of another package (" + epk.Name + ") and cannot be removed.")) os.Exit(1) } else { // It is in the list of packages to remove, we need to set this package's priority higher. removePackage.Priority++ } } } } // Add it to the list of packages to remove. removePackage.Name = pkg removePackage.DisplayData = epkMap[pkg] packageRemoveList = append(packageRemoveList, removePackage) } if len(packageRemoveList) > 0 { // First, re-order the packageRemoveList by priority, then alphabetically. sort.Slice(packageRemoveList, func(i, j int) bool { if packageRemoveList[i].Priority == packageRemoveList[j].Priority { return packageRemoveList[i].Name < packageRemoveList[j].Name } return packageRemoveList[i].Priority > packageRemoveList[j].Priority }) // Print the packages that will be removed. fmt.Println("The following packages will be removed:") width, _, err := term.GetSize(int(os.Stdout.Fd())) if err != nil { fmt.Println(color.RedString("Failed to get terminal width: " + err.Error())) os.Exit(1) } if width < 42 { fmt.Println(color.RedString("Terminal too small. Minimum required width: 42 characters.")) os.Exit(1) } for range width { fmt.Print("=") } fmt.Println() tableList := []string{"Package", "Architecture", "Version", "Repository", "Installed Size", "Action"} maxSize := width / 6 for _, item := range tableList { common.PrintWithEvenPadding(item, maxSize) } fmt.Println() for range width { fmt.Print("=") } fmt.Println() for _, pkg := range packageRemoveList { finalisedList := make([]string, 6) finalisedList[0] = pkg.Name finalisedList[1] = pkg.DisplayData.Architecture finalisedList[2] = pkg.DisplayData.Version.String() finalisedList[3] = repositoryMap[pkg.Name].Name finalisedList[4] = humanize.BigIBytes(decompressedSizeMap[pkg.Name]) finalisedList[5] = "Remove" for _, item := range finalisedList { common.PrintWithEvenPadding(item, maxSize) } } for range width { fmt.Print("=") } fmt.Println("Transaction Summary") fmt.Println("\nRemoving " + humanize.Comma(int64(len(packageRemoveList))) + " packages.") space := new(big.Int) for _, pkg := range packageRemoveList { space = new(big.Int).Add(space, decompressedSizeMap[pkg.Name]) } fmt.Println("Total reclaimed space: " + humanize.BigIBytes(space) + "\n") response := logger.LogFunc(lib.Log{ Level: "INFO", Content: "Proceed with removal (y/n)?", Prompt: true, }) if strings.ToLower(response) == "y" { for _, pkg := range packageRemoveList { err, vagueErr := lib.RemoveEPK(pkg.Name, common.DefaultRemoveEPKFromDB, common.DefaultGetEPKRemoveInfoFromDB, logger) if err != nil || vagueErr != nil { common.RemoveEPKHandleError(err, vagueErr, logger) fmt.Println(color.RedString("Failed to remove package: " + vagueErr.Error() + err.Error())) } fmt.Println("Removed package: " + pkg.Name) } } else { fmt.Println("Removal cancelled.") os.Exit(1) } } case "list": if len(os.Args) < 3 { fmt.Println("Usage: eon list (remote/local)") os.Exit(1) } err := common.EstablishDBConnection(logger) if err != nil { fmt.Println(color.RedString("Failed to establish a connection to the database: " + err.Error())) os.Exit(1) } width, _, err := term.GetSize(int(os.Stdout.Fd())) if err != nil { fmt.Println(color.RedString("Failed to get terminal width: " + err.Error())) os.Exit(1) } if width < 15 { fmt.Println(color.RedString("Terminal too small. Minimum required width: 15 characters.")) os.Exit(1) } maxWidth := width / 3 switch os.Args[2] { case "remote": // Refresh the package list. err = common.RefreshPackageList(common.DefaultListRepositoriesInDB, common.DefaultAddRemotePackageToDB, common.DefaultGetFingerprintFromDB, common.DefaultAddFingerprintToDB, common.DefaultCheckRepositoryInDB, common.DefaultAddRepositoryToDB, common.DefaultListRemotePackagesInDB, common.DefaultRemoveRemotePackageFromDB, logger) if err != nil { fmt.Println(color.RedString("Failed to refresh package list: " + err.Error())) os.Exit(1) } remoteEpkList, err := common.DefaultListRemotePackagesInDB() if err != nil { fmt.Println(color.RedString("Failed to list remote packages: " + err.Error())) os.Exit(1) } if len(remoteEpkList) == 0 { fmt.Println("No remote packages.") } else { fmt.Println("\nRemote packages:") for range width { fmt.Print("=") } for _, item := range []string{"Name", "Version", "Repository"} { common.PrintWithEvenPadding(item, maxWidth) } for range width { fmt.Print("=") } for _, epk := range remoteEpkList { var printMap []string printMap = append(printMap, epk.Name, epk.Version.String(), epk.Repository.Name) for _, item := range printMap { common.PrintWithEvenPadding(item, maxWidth) } fmt.Println() } for range width { fmt.Print("=") } } case "local": epkMap, _, repositoryMap, _, err := common.DefaultListEPKsInDB() if err != nil { fmt.Println(color.RedString("Failed to list installed packages: " + err.Error())) os.Exit(1) } if len(epkMap) == 0 { fmt.Println("No local packages.") } else { // Sort the map by name. var epkList []string for epk := range epkMap { epkList = append(epkList, epk) } sort.Strings(epkList) fmt.Println("Local packages:") for range width { fmt.Print("=") } for _, item := range []string{"Name", "Version", "Repository"} { common.PrintWithEvenPadding(item, maxWidth) } for range width { fmt.Print("=") } for _, epk := range epkList { version := epkMap[epk].Version var printMap []string printMap = append(printMap, epk, version.String(), repositoryMap[epk].Name) for _, item := range printMap { common.PrintWithEvenPadding(item, maxWidth) } fmt.Println() } for range width { fmt.Print("=") } } } case "info": if len(os.Args) < 3 { fmt.Println("Usage: eon info ") os.Exit(1) } err := common.EstablishDBConnection(logger) if err != nil { fmt.Println(color.RedString("Failed to establish a connection to the database: " + err.Error())) os.Exit(1) } err = common.RefreshPackageList(common.DefaultListRepositoriesInDB, common.DefaultAddRemotePackageToDB, common.DefaultGetFingerprintFromDB, common.DefaultAddFingerprintToDB, common.DefaultCheckRepositoryInDB, common.DefaultAddRepositoryToDB, common.DefaultListRemotePackagesInDB, common.DefaultRemoveRemotePackageFromDB, logger) if err != nil { fmt.Println(color.RedString("Failed to refresh package list: " + err.Error())) } remoteEpks, err := common.DefaultListRemotePackagesInDB() if err != nil { fmt.Println(color.RedString("Failed to list installed packages: " + err.Error())) os.Exit(1) } var remoteEpk lib.RemoteEPK for _, epk := range remoteEpks { if epk.Name == os.Args[2] { remoteEpk = epk break } } if remoteEpk.Name == "" { fmt.Println(color.RedString("Package not found: " + os.Args[2])) os.Exit(1) } epkPreMap, err, vagueErr := lib.PreMapRemoteEPK(remoteEpk, logger) if err != nil || vagueErr != nil { common.PreMapRemoteEPKHandleError(err, vagueErr, logger) } packageUrl, err := url.JoinPath(remoteEpk.Repository.URL, remoteEpk.Path) if err != nil { fmt.Println(color.RedString("Failed to join URL: " + err.Error())) os.Exit(1) } metadata, err, vagueErr := lib.FullyMapMetadata(lib.StreamOrBytes{ IsURL: true, URL: packageUrl, }, &epkPreMap, common.DefaultGetFingerprintFromDB, common.DefaultAddFingerprintToDB, func(*lib.Logger) { logger.LogFunc(lib.Log{ Level: "WARN", Content: "This server does not support range requests. Please use the -O flag to " + "enable in memory mode, or contact the server administrator to use a web server " + "that supports range requests, such as Apache or Nginx. The speed of download " + "is considerably slowed, as we need to discard bytes since we can only read " + "sequentially.", Prompt: false, }) }, logger) if err != nil || vagueErr != nil { common.FullyMapMetadataHandleError(err, vagueErr, logger) } width, _, err := term.GetSize(int(os.Stdout.Fd())) if err != nil { fmt.Println(color.RedString("Failed to get terminal width: " + err.Error())) os.Exit(1) } if width < 11 { fmt.Println(color.RedString("Terminal too small. Minimum required width: 11 characters.")) os.Exit(1) } fmt.Println() for range width { fmt.Print("=") } common.PrintWithEvenPadding("Information", width/2) common.PrintWithEvenPadding("Content", width/2) for range width { fmt.Print("=") } common.PrintWithEvenPadding("Name", width/2) common.PrintWithEvenPadding(epkPreMap.DisplayData.Name, width/2) common.PrintWithEvenPadding("Version", width/2) common.PrintWithEvenPadding(metadata.Version.String(), width/2) common.PrintWithEvenPadding("Description", width/2) common.PrintWithEvenPadding(metadata.Description, width/2) common.PrintWithEvenPadding("License", width/2) common.PrintWithEvenPadding(metadata.License, width/2) common.PrintWithEvenPadding("Architecture", width/2) common.PrintWithEvenPadding(metadata.Architecture, width/2) common.PrintWithEvenPadding("Installed Size", width/2) common.PrintWithEvenPadding(humanize.BigIBytes(metadata.DecompressedSize), width/2) common.PrintWithEvenPadding("Download Size", width/2) common.PrintWithEvenPadding(humanize.Bytes(uint64(metadata.Size)), width/2) common.PrintWithEvenPadding("Repository", width/2) common.PrintWithEvenPadding(remoteEpk.Repository.Name, width/2) common.PrintWithEvenPadding("Dependencies", width/2) if len(metadata.Dependencies) == 0 { common.PrintWithEvenPadding("None", width/2) } else { common.PrintWithEvenPadding(strings.Join(metadata.Dependencies, ", "), width/2) } // We don't print the long description - it's meant for GUIs. for range width { fmt.Print("=") } case "clean": err := common.EstablishDBConnection(logger) if err != nil { fmt.Println(color.RedString("Failed to establish a connection to the database: " + err.Error())) os.Exit(1) } packages, _, repositoryMap, _, err := common.DefaultListEPKsInDB() if err != nil { fmt.Println(color.RedString("Failed to list installed packages: " + err.Error())) os.Exit(1) } if len(packages) == 0 { fmt.Println("No packages to clean.") os.Exit(0) } else { var removeList []common.RemovePackage for _, pkg := range packages { // Check if it's a dependency of another package. if pkg.IsDependency { // Add it to list of candidates for removal. var removePackage common.RemovePackage removePackage.Name = pkg.Name removePackage.DisplayData = pkg removeList = append(removeList, removePackage) } } for _, pkg := range removeList { // Check if it's parent is still installed. for _, epk := range packages { for _, dep := range epk.Dependencies { if dep == pkg.Name { // First, check if it's in the list of packages to remove. var found bool for _, epkName := range removeList { if epkName.Name == epk.Name { found = true break } } if !found { fmt.Println(color.RedString("Package " + pkg.Name + " is a dependency of another package (" + epk.Name + ") and cannot be removed.")) os.Exit(1) } else { // It is in the list of packages to remove, we need to set this package's priority higher. pkg.Priority++ } } } } } if len(removeList) > 0 { // First, re-order the packageRemoveList by priority, then alphabetically. sort.Slice(removeList, func(i, j int) bool { if removeList[i].Priority == removeList[j].Priority { return removeList[i].Name < removeList[j].Name } return removeList[i].Priority > removeList[j].Priority }) // Print the packages that will be removed. fmt.Println("The following packages will be removed:") width, _, err := term.GetSize(int(os.Stdout.Fd())) if err != nil { fmt.Println(color.RedString("Failed to get terminal width: " + err.Error())) os.Exit(1) } if width < 42 { fmt.Println(color.RedString("Terminal too small. Minimum required width: 42 characters.")) os.Exit(1) } for range width { fmt.Print("=") } tableList := []string{"Package", "Architecture", "Version", "Repository", "Installed Size", "Action"} maxSize := width / 6 for _, item := range tableList { common.PrintWithEvenPadding(item, maxSize) } fmt.Println() for range width { fmt.Print("=") } for _, pkg := range removeList { finalisedList := make([]string, 6) finalisedList[0] = pkg.Name finalisedList[1] = pkg.DisplayData.Architecture finalisedList[2] = pkg.DisplayData.Version.String() finalisedList[3] = repositoryMap[pkg.Name].Name finalisedList[4] = humanize.BigIBytes(pkg.DisplayData.DecompressedSize) finalisedList[5] = "Remove" for _, item := range finalisedList { common.PrintWithEvenPadding(item, maxSize) } fmt.Println() } for range width { fmt.Print("=") } fmt.Println("Transaction Summary") fmt.Println("\nRemoving " + humanize.Comma(int64(len(removeList))) + " packages.") space := new(big.Int) for _, pkg := range removeList { space = new(big.Int).Add(space, pkg.DisplayData.DecompressedSize) } fmt.Println("Total reclaimed space: " + humanize.BigIBytes(space) + "\n") response := logger.LogFunc(lib.Log{ Level: "INFO", Content: "Proceed with removal (y/n)?", Prompt: true, }) if strings.ToLower(response) == "y" { for _, pkg := range removeList { err, vagueErr := lib.RemoveEPK(pkg.Name, common.DefaultRemoveEPKFromDB, common.DefaultGetEPKRemoveInfoFromDB, logger) if err != nil || vagueErr != nil { common.RemoveEPKHandleError(err, vagueErr, logger) fmt.Println(color.RedString("Failed to remove package: " + vagueErr.Error() + err.Error())) } fmt.Println("Removed package: " + pkg.Name) } } else { fmt.Println("Removal cancelled.") os.Exit(1) } } else { fmt.Println("No packages to clean.") os.Exit(0) } } default: fmt.Println(color.RedString("Unknown or unimplemented command: " + os.Args[1])) os.Exit(1) } }