392 lines
12 KiB
Go
392 lines
12 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"eon/common"
|
||
|
"eon/lib"
|
||
|
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
"regexp"
|
||
|
"strings"
|
||
|
|
||
|
"golang.org/x/term"
|
||
|
|
||
|
"github.com/dustin/go-humanize"
|
||
|
"github.com/fatih/color"
|
||
|
)
|
||
|
|
||
|
var logger = &common.DefaultLogger
|
||
|
|
||
|
func main() {
|
||
|
if len(os.Args) < 2 {
|
||
|
fmt.Println("Usage: eon <list/info/install/remove/clean/repo/help> [args]")
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
|
||
|
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 (remote/local)")
|
||
|
fmt.Println("- remote: lists packages available in the repositories.")
|
||
|
fmt.Println("- local: lists installed packages.")
|
||
|
case "info":
|
||
|
fmt.Println("Usage: eon info <package>")
|
||
|
fmt.Println("Shows information about a package.")
|
||
|
case "install":
|
||
|
fmt.Println("Usage: eon install <package> [<repository>]")
|
||
|
fmt.Println("Installs a package.")
|
||
|
case "remove":
|
||
|
fmt.Println("Usage: eon remove <package>")
|
||
|
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/remove/list)")
|
||
|
fmt.Println("- add <url>: adds a repository.")
|
||
|
fmt.Println("- remove <url>: 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 inMemoryMode bool
|
||
|
if len(os.Args) < 3 {
|
||
|
fmt.Println("Usage: eon install <package>")
|
||
|
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 {
|
||
|
if os.IsNotExist(err) {
|
||
|
// 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 {
|
||
|
localPackageList = append(localPackageList, pkg)
|
||
|
fileInfos[pkg] = fileInfo
|
||
|
}
|
||
|
} else {
|
||
|
switch pkg {
|
||
|
case "--force", "-f":
|
||
|
forceMode = true
|
||
|
case "--optimizeForSpeed", "-O":
|
||
|
inMemoryMode = 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)
|
||
|
}
|
||
|
// Ok. Let's give the user an overview of what we're going to do.
|
||
|
if len(localPackageList) > 0 || len(repoPackageList) > 0 {
|
||
|
fmt.Println("The 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.Print("\n ")
|
||
|
tableList := []string{"Package", "Architecture", "Version", "Repository", "Size", "Action"}
|
||
|
maxSize := (width - 6) / 6
|
||
|
for _, item := range tableList {
|
||
|
common.PrintWithEvenPadding(item, maxSize)
|
||
|
}
|
||
|
fmt.Print("\n")
|
||
|
for range width {
|
||
|
fmt.Print("=")
|
||
|
}
|
||
|
fmt.Print("\n")
|
||
|
var epkListLocal []common.InstallPackage
|
||
|
for _, pkg := range localPackageList {
|
||
|
var finalisedList []string
|
||
|
epkFile, err := os.Open(pkg)
|
||
|
if err != nil {
|
||
|
fmt.Println(color.RedString("Failed to open package: " + err.Error()))
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
|
||
|
var displayData lib.EPKPreMap
|
||
|
var vagueErr error
|
||
|
var epkBytes bytes.Buffer
|
||
|
if !inMemoryMode {
|
||
|
displayData, err, vagueErr = lib.PreMapEPK(lib.StreamOrBytes{Stream: epkFile, IsStream: 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."))
|
||
|
}
|
||
|
displayData, err, vagueErr = lib.PreMapEPK(lib.StreamOrBytes{Bytes: epkBytes.Bytes(), IsStream: false}, fileInfos[pkg].Size())
|
||
|
}
|
||
|
if err != nil || vagueErr != nil {
|
||
|
common.PreMapEPKHandleError(err, vagueErr, logger)
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
|
||
|
version, exists, err := common.DefaultCheckEPKInDB(displayData.Name)
|
||
|
if err != nil {
|
||
|
fmt.Println(color.RedString("Failed to check for package in database: " + err.Error()))
|
||
|
}
|
||
|
|
||
|
// TODO: Implement some dependency management more-or-less here.
|
||
|
|
||
|
name := displayData.Name
|
||
|
architecture := displayData.Architecture
|
||
|
versionDisplay := displayData.Version.String()
|
||
|
repository := "Local file"
|
||
|
size := humanize.Bytes(uint64(displayData.Size))
|
||
|
action := "Install"
|
||
|
if exists {
|
||
|
if version.Compare(&displayData.Version) != -1 {
|
||
|
if !forceMode {
|
||
|
name = color.GreenString(name)
|
||
|
architecture = color.GreenString(architecture)
|
||
|
versionDisplay = color.GreenString(versionDisplay)
|
||
|
repository = color.GreenString(repository)
|
||
|
size = color.GreenString(size)
|
||
|
action = color.GreenString("Skip")
|
||
|
} else {
|
||
|
action = "Forced installation"
|
||
|
displayData.IsUpgrade = true
|
||
|
if !inMemoryMode {
|
||
|
epkListLocal = append(epkListLocal, common.InstallPackage{
|
||
|
StreamOrBytes: lib.StreamOrBytes{
|
||
|
Stream: epkFile,
|
||
|
IsStream: true,
|
||
|
},
|
||
|
EPKPreMap: &displayData,
|
||
|
LocalFile: true,
|
||
|
})
|
||
|
} else {
|
||
|
epkListLocal = append(epkListLocal, common.InstallPackage{
|
||
|
StreamOrBytes: lib.StreamOrBytes{
|
||
|
Bytes: epkBytes.Bytes(),
|
||
|
IsStream: false,
|
||
|
},
|
||
|
EPKPreMap: &displayData,
|
||
|
LocalFile: true,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
action = "Upgrade"
|
||
|
displayData.IsUpgrade = true
|
||
|
if !inMemoryMode {
|
||
|
epkListLocal = append(epkListLocal, common.InstallPackage{
|
||
|
StreamOrBytes: lib.StreamOrBytes{
|
||
|
Stream: epkFile,
|
||
|
IsStream: true,
|
||
|
},
|
||
|
EPKPreMap: &displayData,
|
||
|
LocalFile: true,
|
||
|
})
|
||
|
} else {
|
||
|
epkListLocal = append(epkListLocal, common.InstallPackage{
|
||
|
StreamOrBytes: lib.StreamOrBytes{
|
||
|
Bytes: epkBytes.Bytes(),
|
||
|
IsStream: false,
|
||
|
},
|
||
|
EPKPreMap: &displayData,
|
||
|
LocalFile: true,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
displayData.IsUpgrade = false
|
||
|
if !inMemoryMode {
|
||
|
epkListLocal = append(epkListLocal, common.InstallPackage{
|
||
|
StreamOrBytes: lib.StreamOrBytes{
|
||
|
Stream: epkFile,
|
||
|
IsStream: true,
|
||
|
},
|
||
|
EPKPreMap: &displayData,
|
||
|
LocalFile: true,
|
||
|
})
|
||
|
} else {
|
||
|
epkListLocal = append(epkListLocal, common.InstallPackage{
|
||
|
StreamOrBytes: lib.StreamOrBytes{
|
||
|
Bytes: epkBytes.Bytes(),
|
||
|
IsStream: false,
|
||
|
},
|
||
|
EPKPreMap: &displayData,
|
||
|
LocalFile: true,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
finalisedList = append(finalisedList, name)
|
||
|
finalisedList = append(finalisedList, architecture)
|
||
|
finalisedList = append(finalisedList, versionDisplay)
|
||
|
finalisedList = append(finalisedList, repository)
|
||
|
finalisedList = append(finalisedList, size)
|
||
|
finalisedList = append(finalisedList, action)
|
||
|
fmt.Print(" ")
|
||
|
for _, item := range finalisedList {
|
||
|
common.PrintWithEvenPadding(item, maxSize)
|
||
|
}
|
||
|
fmt.Print("\n")
|
||
|
}
|
||
|
//for _, pkg := range repoPackageList {
|
||
|
// var finalisedList []string
|
||
|
// lib.SearchRepository()
|
||
|
//}
|
||
|
for range width {
|
||
|
fmt.Print("=")
|
||
|
}
|
||
|
fmt.Print("\n")
|
||
|
if len(epkListLocal) > 0 {
|
||
|
response := logger.LogFunc(lib.Log{
|
||
|
Level: "INFO",
|
||
|
Content: "Proceed with installation (y/n)?",
|
||
|
Prompt: true,
|
||
|
PlaySound: true,
|
||
|
})
|
||
|
if strings.ToLower(response) == "y" {
|
||
|
// 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 _, streamOrBytesAndPreMap := range epkListLocal {
|
||
|
metadata, err, vagueErr := lib.FullyMapMetadata(streamOrBytesAndPreMap.StreamOrBytes,
|
||
|
streamOrBytesAndPreMap.EPKPreMap, common.DefaultGetFingerprintFromDB,
|
||
|
common.DefaultAddFingerprintToDB, logger)
|
||
|
if err != nil || vagueErr != nil {
|
||
|
common.FullyMapMetadataHandleError(err, vagueErr, logger)
|
||
|
}
|
||
|
// Install the package.
|
||
|
tempDir, err, vagueErr := lib.InstallEPK(streamOrBytesAndPreMap.StreamOrBytes,
|
||
|
metadata, streamOrBytesAndPreMap.EPKPreMap, common.DefaultAddEPKToDB, logger)
|
||
|
if err != nil || vagueErr != nil {
|
||
|
common.InstallEPKHandleError(tempDir, err, vagueErr, logger)
|
||
|
}
|
||
|
// Done!
|
||
|
fmt.Println("Installed package: " + streamOrBytesAndPreMap.EPKPreMap.Name)
|
||
|
}
|
||
|
// We show the cursor again because we're done with the progress bar.
|
||
|
// 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 {
|
||
|
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/remove/list)")
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
|
||
|
switch os.Args[2] {
|
||
|
case "add":
|
||
|
if len(os.Args) < 4 {
|
||
|
fmt.Println("Usage: eon repo add <url>")
|
||
|
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.AddRepository(os.Args[3], common.DefaultAddRepositoryToDB, common.DefaultGetFingerprintFromDB, common.DefaultAddFingerprintToDB, common.DefaultAddRemotePackageToDB, logger)
|
||
|
if err != nil || vagueErr != nil {
|
||
|
common.AddRepositoryHandleError(err, vagueErr, logger)
|
||
|
}
|
||
|
}
|
||
|
case "remove":
|
||
|
if len(os.Args) < 4 {
|
||
|
fmt.Println("Usage: eon repo remove <name>")
|
||
|
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, 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" + " URL: " + repo.URL + "\n Owner: " + repo.Owner)
|
||
|
}
|
||
|
fmt.Println()
|
||
|
}
|
||
|
}
|
||
|
default:
|
||
|
fmt.Println(color.RedString("Unknown or unimplemented command: " + os.Args[1]))
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
}
|