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 [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 ") 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/remove/list)") fmt.Println("- add : adds a repository.") fmt.Println("- remove : 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 ") 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 ") 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 ") 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) } }