Added remote repository downloading
This commit is contained in:
parent
9952e3bc96
commit
7b0aba2ad7
7 changed files with 1228 additions and 366 deletions
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Package",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${fileDirname}",
|
||||
"args": [],
|
||||
}
|
||||
]
|
||||
}
|
BIN
cmd/cmd
BIN
cmd/cmd
Binary file not shown.
476
cmd/main.go
476
cmd/main.go
|
@ -1,19 +1,20 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"eon/common"
|
||||
"eon/lib"
|
||||
|
||||
"bufio"
|
||||
"bytes"
|
||||
"eon/common"
|
||||
"eon/lib"
|
||||
"fmt"
|
||||
"golang.org/x/term"
|
||||
"io"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/term"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
@ -80,7 +81,7 @@ func main() {
|
|||
// 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))
|
||||
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)
|
||||
|
@ -111,9 +112,269 @@ func main() {
|
|||
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.
|
||||
// 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 {
|
||||
fmt.Println("The following packages will be installed:")
|
||||
var epkList []common.InstallPackage
|
||||
var skipEpkList [][]string
|
||||
var dependencies int
|
||||
for _, pkg := range localPackageList {
|
||||
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{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."))
|
||||
}
|
||||
displayData, 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)
|
||||
}
|
||||
|
||||
var installPackage common.InstallPackage
|
||||
installPackage.EPKPreMap = &displayData
|
||||
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"}
|
||||
displayData.IsUpgrade = true
|
||||
|
||||
version, exists, err := common.DefaultCheckEPKInDB(displayData.Name)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Failed to check for package in database: " + err.Error()))
|
||||
}
|
||||
|
||||
if exists {
|
||||
if version.Compare(&displayData.Version) != -1 {
|
||||
if !forceMode {
|
||||
skipEpk := []string{displayData.Name, displayData.Architecture, displayData.Version.String(), "Local file", humanize.BigIBytes(displayData.DecompressedSize), "Skip"}
|
||||
skipEpkList = append(skipEpkList, skipEpk)
|
||||
} else {
|
||||
installPackage.IsForced = true
|
||||
addedDeps, err := common.HandleDependencies(dependencies, installPackage, 0, []lib.RemoteEPK{}, common.DefaultListRemotePackagesInDB, &epkList, 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 {
|
||||
addedDeps, err := common.HandleDependencies(dependencies, installPackage, 0, []lib.RemoteEPK{}, common.DefaultListRemotePackagesInDB, &epkList, 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 {
|
||||
displayData.IsUpgrade = false
|
||||
addedDeps, err := common.HandleDependencies(dependencies, installPackage, 0, []lib.RemoteEPK{}, common.DefaultListRemotePackagesInDB, &epkList, logger)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Failed to handle dependencies: " + err.Error()))
|
||||
os.Exit(1)
|
||||
} else {
|
||||
dependencies += addedDeps
|
||||
}
|
||||
epkList = append(epkList, installPackage)
|
||||
}
|
||||
}
|
||||
|
||||
for _, pkg := range repoPackageList {
|
||||
// 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)
|
||||
}
|
||||
|
||||
for _, epk := range remoteEpkList {
|
||||
if epk.Name == pkg {
|
||||
remoteEPK = epk
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
displayData, err, vagueErr := lib.PreMapRemoteEPK(remoteEPK, logger)
|
||||
if err != nil || vagueErr != nil {
|
||||
common.PreMapEPKHandleError(err, vagueErr, logger)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Map the data we have
|
||||
epkEntry.Priority = 0
|
||||
epkEntry.EPKPreMap = &displayData
|
||||
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: " + 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(&displayData.Version) != -1 {
|
||||
// If the version is the same or newer, skip the package.
|
||||
if !forceMode {
|
||||
skipEpkList = append(skipEpkList, []string{displayData.Name, displayData.Architecture, displayData.Version.String(), remoteEPK.Repository.Name, humanize.BigIBytes(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.
|
||||
addedDeps, err := common.HandleDependencies(dependencies, epkEntry, 0, remoteEpkList, common.DefaultListRemotePackagesInDB, &epkList, 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.
|
||||
addedDeps, err := common.HandleDependencies(dependencies, epkEntry, 0, remoteEpkList, common.DefaultListRemotePackagesInDB, &epkList, 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.
|
||||
addedDeps, err := common.HandleDependencies(dependencies, epkEntry, 0, remoteEpkList, common.DefaultListRemotePackagesInDB, &epkList, logger)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString("Failed to handle dependencies: " + err.Error()))
|
||||
os.Exit(1)
|
||||
} else {
|
||||
dependencies += addedDeps
|
||||
}
|
||||
epkList = append(epkList, epkEntry)
|
||||
}
|
||||
}
|
||||
|
||||
// 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()))
|
||||
|
@ -126,161 +387,55 @@ func main() {
|
|||
for range width {
|
||||
fmt.Print("=")
|
||||
}
|
||||
fmt.Print("\n ")
|
||||
tableList := []string{"Package", "Architecture", "Version", "Repository", "Size", "Action"}
|
||||
maxSize := (width - 6) / 6
|
||||
fmt.Println()
|
||||
tableList := []string{"Package", "Architecture", "Version", "Repository", "Installed Size", "Action"}
|
||||
maxSize := width / 6
|
||||
for _, item := range tableList {
|
||||
common.PrintWithEvenPadding(item, maxSize)
|
||||
}
|
||||
fmt.Print("\n")
|
||||
fmt.Println()
|
||||
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)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
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())
|
||||
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.Name
|
||||
finalisedList[1] = pkg.EPKPreMap.Architecture
|
||||
finalisedList[2] = pkg.EPKPreMap.Version.String()
|
||||
if !pkg.IsRemote {
|
||||
finalisedList[3] = "Local file"
|
||||
} 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())
|
||||
finalisedList[3] = pkg.Repository.Name
|
||||
}
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
finalisedList[4] = humanize.BigIBytes(pkg.EPKPreMap.DecompressedSize)
|
||||
if pkg.IsForced {
|
||||
finalisedList[5] = "Forced installation"
|
||||
} else if pkg.EPKPreMap.IsUpgrade {
|
||||
finalisedList[5] = "Upgrade"
|
||||
} 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[5] = "Install"
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
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")
|
||||
response := logger.LogFunc(lib.Log{
|
||||
Level: "INFO",
|
||||
Content: "Proceed with installation (y/n)?",
|
||||
|
@ -294,30 +449,33 @@ func main() {
|
|||
// - Arzumify
|
||||
fmt.Print("\033[?25l")
|
||||
// Time to install things.
|
||||
for _, streamOrBytesAndPreMap := range epkListLocal {
|
||||
metadata, err, vagueErr := lib.FullyMapMetadata(streamOrBytesAndPreMap.StreamOrBytes,
|
||||
streamOrBytesAndPreMap.EPKPreMap, common.DefaultGetFingerprintFromDB,
|
||||
for _, installPackage := range epkList {
|
||||
// Map the EPK metadata.
|
||||
metadata, err, vagueErr := lib.FullyMapMetadata(installPackage.StreamOrBytes,
|
||||
installPackage.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)
|
||||
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: " + streamOrBytesAndPreMap.EPKPreMap.Name)
|
||||
fmt.Println("Installed package: " + installPackage.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 {
|
||||
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)
|
||||
|
@ -343,9 +501,11 @@ func main() {
|
|||
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)
|
||||
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 "remove":
|
||||
|
@ -358,7 +518,7 @@ func main() {
|
|||
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)
|
||||
err, vagueErr := lib.RemoveRepository(os.Args[3], common.DefaultRemoveRepositoryFromDB, common.DefaultCheckRepositoryInDB, logger)
|
||||
if err != nil || vagueErr != nil {
|
||||
common.RemoveRepositoryHandleError(err, vagueErr, logger)
|
||||
}
|
||||
|
@ -379,7 +539,7 @@ func main() {
|
|||
} else {
|
||||
fmt.Println("Repositories:")
|
||||
for _, repo := range repos {
|
||||
fmt.Println("\n" + repo.Name + ":\n" + " URL: " + repo.URL + "\n Owner: " + repo.Owner)
|
||||
fmt.Println("\n" + repo.Name + ":\n" + " " + repo.Description + "\n URL: " + repo.URL + "\n Owner: " + repo.Owner)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
|
452
common/main.go
452
common/main.go
|
@ -3,6 +3,9 @@ package common
|
|||
import (
|
||||
"encoding/binary"
|
||||
"eon/lib"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"bytes"
|
||||
"errors"
|
||||
|
@ -21,15 +24,19 @@ import (
|
|||
"golang.org/x/term"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/fatih/color"
|
||||
"modernc.org/sqlite"
|
||||
)
|
||||
|
||||
type InstallPackage struct {
|
||||
LocalFile bool
|
||||
IsRemote bool
|
||||
IsForced bool
|
||||
Url string
|
||||
Priority int
|
||||
StreamOrBytes lib.StreamOrBytes
|
||||
EPKPreMap *lib.EPKPreMap
|
||||
Repository lib.Repository
|
||||
}
|
||||
|
||||
type PluginInfo struct {
|
||||
|
@ -46,7 +53,7 @@ type Plugin struct {
|
|||
}
|
||||
|
||||
var conn *sql.DB
|
||||
var dbVersion = semver.MustParse("1.0.0-beta.2")
|
||||
var dbVersion = semver.MustParse("1.0.0-beta.3")
|
||||
|
||||
var DefaultLogger = lib.Logger{
|
||||
LogFunc: func(log lib.Log) string {
|
||||
|
@ -131,6 +138,220 @@ var DefaultLogger = lib.Logger{
|
|||
ProgressSupported: true,
|
||||
}
|
||||
|
||||
var TimeMagnitudes = []humanize.RelTimeMagnitude{
|
||||
{0, "now", 1},
|
||||
{2 * time.Millisecond, "1 millisecond", 1},
|
||||
{time.Second, "%d milliseconds", time.Millisecond},
|
||||
// The following code is from an Expat / MIT licensed project.
|
||||
// Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
// <http://www.opensource.org/licenses/mit-license.php>
|
||||
|
||||
// It is sublicensed under GPLv3.
|
||||
{2 * time.Second, "1 second", 1},
|
||||
{time.Minute, "%d seconds", time.Second},
|
||||
{2 * time.Minute, "1 minute", 1},
|
||||
{time.Hour, "%d minutes", time.Minute},
|
||||
{2 * time.Hour, "1 hour", 1},
|
||||
{humanize.Day, "%d hours", time.Hour},
|
||||
{2 * humanize.Day, "1 day", 1},
|
||||
{humanize.Week, "%d days", humanize.Day},
|
||||
{2 * humanize.Week, "1 week", 1},
|
||||
{humanize.Month, "%d weeks", humanize.Week},
|
||||
{2 * humanize.Month, "1 month", 1},
|
||||
{humanize.Year, "%d months", humanize.Month},
|
||||
{18 * humanize.Month, "1 year", 1},
|
||||
{2 * humanize.Year, "2 years", 1},
|
||||
{humanize.Year, "%d years", humanize.Year},
|
||||
{math.MaxInt64, "a long while", 1},
|
||||
// End of Expat / MIT licensed code
|
||||
}
|
||||
|
||||
func HandleDependencies(previousDeps int, targetEPK InstallPackage, parentPriority int, epkList []lib.RemoteEPK, ListRemotePackagesInDB func() ([]lib.RemoteEPK, error), InstallPackageList *[]InstallPackage, logger *lib.Logger) (int, error) {
|
||||
// Iterate through the dependencies of the target EPK.
|
||||
dependencyLoop:
|
||||
for _, dependency := range targetEPK.EPKPreMap.Dependencies {
|
||||
// Check if the dependency is already in the list of EPKs to install.
|
||||
for iterator, epk := range *InstallPackageList {
|
||||
if epk.EPKPreMap.Name == dependency {
|
||||
// The dependency is already in the list of EPKs to install, check for its dependencies.
|
||||
if len(epk.EPKPreMap.Dependencies) == 0 || epk.EPKPreMap.Dependencies == nil {
|
||||
// All dependencies are handled - change the priority and continue with the next dependency.
|
||||
epk.Priority = parentPriority + 1
|
||||
currentInstallPackageList := *InstallPackageList
|
||||
currentInstallPackageList[iterator] = epk
|
||||
InstallPackageList = ¤tInstallPackageList
|
||||
continue dependencyLoop
|
||||
} else {
|
||||
// Check if it's a circular dependency.
|
||||
for _, epk := range *InstallPackageList {
|
||||
if epk.EPKPreMap.Name == targetEPK.EPKPreMap.Name {
|
||||
// We have a circular dependency. Crash immediately.
|
||||
logger.LogFunc(lib.Log{
|
||||
Level: "FATAL",
|
||||
Content: "Circular dependency detected: " + targetEPK.EPKPreMap.Name + " -> " + dependency + " -> " + targetEPK.EPKPreMap.Name,
|
||||
Prompt: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
// Recursively handle dependencies.
|
||||
addedDeps, err := HandleDependencies(previousDeps, epk, parentPriority+1, epkList, ListRemotePackagesInDB, InstallPackageList, logger)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
// Add the dependencies to the total number of dependencies.
|
||||
previousDeps += addedDeps
|
||||
// All dependencies are now handled - change the priority and continue with the next dependency.
|
||||
epk.Priority = parentPriority + 1
|
||||
currentInstallPackageList := *InstallPackageList
|
||||
currentInstallPackageList[iterator] = epk
|
||||
InstallPackageList = ¤tInstallPackageList
|
||||
continue dependencyLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we already have a list of remote EPKs to use, if so use it.
|
||||
if len(epkList) == 0 || epkList == nil {
|
||||
var err error
|
||||
epkList, err = ListRemotePackagesInDB()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we already have the EPK installed.
|
||||
version, exists, err := DefaultCheckEPKInDB(dependency)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var remoteEPK lib.RemoteEPK
|
||||
var epkEntry InstallPackage
|
||||
var dependencyExists bool
|
||||
// Iterate through the list of remote EPKs to find the dependency.
|
||||
for _, epk := range epkList {
|
||||
if epk.Name == dependency {
|
||||
dependencyExists = true
|
||||
if !exists {
|
||||
remoteEPK = epk
|
||||
break
|
||||
} else {
|
||||
// Check if the installed version is outdated.
|
||||
if version.LessThan(&epk.Version) {
|
||||
// Yes it is: Install the new version as an upgrade.
|
||||
remoteEPK = epk
|
||||
break
|
||||
} else {
|
||||
// The installed version is up-to-date.
|
||||
continue dependencyLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the dependency doesn't exist, crash.
|
||||
if !dependencyExists {
|
||||
return 0, errors.New("dependency " + dependency + " does not exist")
|
||||
}
|
||||
// Increase the dependency's priority.
|
||||
epkEntry.Priority = parentPriority + 1
|
||||
// Add the dependency to the list of EPKs to install.
|
||||
epkDownloadUrl, err := url.JoinPath(remoteEPK.Repository.URL, remoteEPK.Path)
|
||||
if err != nil {
|
||||
logger.LogFunc(lib.Log{
|
||||
Level: "FATAL",
|
||||
Content: "Failed to join URL path: " + err.Error(),
|
||||
Prompt: false,
|
||||
})
|
||||
}
|
||||
// Set the URL and the fact that it is remote.
|
||||
epkEntry.IsRemote = true
|
||||
epkEntry.Url = epkDownloadUrl
|
||||
// Map the EPKs display data.
|
||||
epkPreMap, err, vagueErr := lib.PreMapRemoteEPK(remoteEPK, logger)
|
||||
if err != nil || vagueErr != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Set the EPKs display data.
|
||||
epkEntry.EPKPreMap = &epkPreMap
|
||||
if exists {
|
||||
// Set it as upgrade if it already exists.
|
||||
epkEntry.EPKPreMap.IsUpgrade = true
|
||||
}
|
||||
// Set the repository.
|
||||
epkEntry.Repository = remoteEPK.Repository
|
||||
// Make it noted that this is not a forced installation.
|
||||
epkEntry.IsForced = false
|
||||
// Check if the dependency has dependencies, and if so, recursively handle them.
|
||||
if !(len(remoteEPK.Dependencies) == 0 || remoteEPK.Dependencies == nil) {
|
||||
// Check if it's a circular dependency.
|
||||
for _, epk := range *InstallPackageList {
|
||||
if epk.EPKPreMap.Name == targetEPK.EPKPreMap.Name {
|
||||
// We have a circular dependency. Crash immediately.
|
||||
logger.LogFunc(lib.Log{
|
||||
Level: "FATAL",
|
||||
Content: "Circular dependency detected: " + targetEPK.EPKPreMap.Name + " -> " + dependency + " -> " + targetEPK.EPKPreMap.Name,
|
||||
Prompt: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
// Recursively handle dependencies.
|
||||
addedDeps, err := HandleDependencies(previousDeps, epkEntry, epkEntry.Priority+1, epkList, ListRemotePackagesInDB, InstallPackageList, logger)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Add the dependencies to the total number of dependencies.
|
||||
previousDeps += addedDeps
|
||||
}
|
||||
// All dependencies are now handled - continue with the next dependency.
|
||||
currentInstallPackageList := append(*InstallPackageList, epkEntry)
|
||||
InstallPackageList = ¤tInstallPackageList
|
||||
previousDeps++
|
||||
continue
|
||||
}
|
||||
|
||||
// If we reach this point, all dependencies have been handled.
|
||||
return previousDeps, nil
|
||||
}
|
||||
|
||||
func GetTotalSize(InstallPackageList []InstallPackage) *big.Int {
|
||||
totalSize := new(big.Int)
|
||||
for _, epk := range InstallPackageList {
|
||||
totalSize.Add(totalSize, big.NewInt(epk.EPKPreMap.Size))
|
||||
}
|
||||
return totalSize
|
||||
}
|
||||
|
||||
func GetTotalInstalledSize(InstallPackageList []InstallPackage) *big.Int {
|
||||
totalSize := new(big.Int)
|
||||
for _, epk := range InstallPackageList {
|
||||
if !epk.IsRemote {
|
||||
totalSize.Add(totalSize, epk.EPKPreMap.DecompressedSize)
|
||||
}
|
||||
}
|
||||
return totalSize
|
||||
}
|
||||
|
||||
func LoadPlugin(pluginName string, logger *lib.Logger) (Plugin, error) {
|
||||
// Check the plugin's signature
|
||||
// Load the plugin file
|
||||
|
@ -424,7 +645,7 @@ func EstablishDBConnection(logger *lib.Logger) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func DefaultAddEPKToDB(metadata *lib.Metadata, removeScript []byte, dependency bool, hasRemoveScript bool, size int64) error {
|
||||
func DefaultAddEPKToDB(metadata *lib.Metadata, removeScript []byte, dependency bool, hasRemoveScript bool, size int64, repository ...string) error {
|
||||
// If it already exists, delete it. This may happen in a force-install scenario.
|
||||
_, err := conn.Exec("DELETE FROM packages WHERE name = ?", metadata.Name)
|
||||
if err != nil {
|
||||
|
@ -449,9 +670,16 @@ func DefaultAddEPKToDB(metadata *lib.Metadata, removeScript []byte, dependency b
|
|||
|
||||
// Not that I'm complaining or anything.
|
||||
// - Arzumify
|
||||
_, err = conn.Exec("INSERT INTO packages (name, description, longDescription, version, author, license, architecture, size, dependencies, removeScript, hasRemoveScript, isDependency) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
metadata.Name, metadata.Description, metadata.LongDescription, metadata.Version.String(), metadata.Author,
|
||||
metadata.License, metadata.Architecture, size, dependencies, string(removeScript), hasRemoveScript, dependency)
|
||||
if len(repository) > 0 {
|
||||
_, err = conn.Exec("INSERT INTO packages (name, description, longDescription, version, author, license, architecture, size, dependencies, removeScript, hasRemoveScript, isDependency, repository) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
metadata.Name, metadata.Description, metadata.LongDescription, metadata.Version.String(), metadata.Author,
|
||||
metadata.License, metadata.Architecture, size, dependencies, string(removeScript), hasRemoveScript, dependency,
|
||||
repository[0])
|
||||
} else {
|
||||
_, err = conn.Exec("INSERT INTO packages (name, description, longDescription, version, author, license, architecture, size, dependencies, removeScript, hasRemoveScript, isDependency) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
metadata.Name, metadata.Description, metadata.LongDescription, metadata.Version.String(), metadata.Author,
|
||||
metadata.License, metadata.Architecture, size, dependencies, string(removeScript), hasRemoveScript, dependency)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -550,7 +778,14 @@ func DefaultGetFingerprintFromDB(fingerprint []byte, author string) (bool, bool,
|
|||
}
|
||||
}
|
||||
|
||||
func DefaultAddRepositoryToDB(repository lib.Repository) error {
|
||||
func DefaultAddRepositoryToDB(repository lib.Repository, forceReplace bool) error {
|
||||
if forceReplace {
|
||||
// Delete the repository if it already exists. This may happen in a force-install scenario.
|
||||
_, err := conn.Exec("DELETE FROM repositories WHERE name = ?", repository.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := conn.Exec("INSERT INTO repositories (url, name, owner, description) VALUES (?, ?, ?, ?)",
|
||||
repository.URL, repository.Name, repository.Owner, repository.Description)
|
||||
if err != nil {
|
||||
|
@ -572,24 +807,41 @@ func DefaultRemoveRepositoryFromDB(repository string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func DefaultCheckRepositoryInDB(repository string) (bool, error) {
|
||||
var name string
|
||||
err := conn.QueryRow("SELECT name FROM repositories WHERE name = ?", repository).Scan(&name)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func DefaultListRepositoriesInDB() ([]lib.Repository, error) {
|
||||
rows, err := conn.Query("SELECT url, name, owner FROM repositories")
|
||||
rows, err := conn.Query("SELECT url, name, owner, description FROM repositories")
|
||||
if err != nil {
|
||||
return []lib.Repository{}, err
|
||||
}
|
||||
var repositories []lib.Repository
|
||||
for rows.Next() {
|
||||
var url, name, owner string
|
||||
err := rows.Scan(&url, &name, &owner)
|
||||
var repoUrl, name, owner, description string
|
||||
err := rows.Scan(&repoUrl, &name, &owner, &description)
|
||||
if err != nil {
|
||||
return []lib.Repository{}, err
|
||||
}
|
||||
repositories = append(repositories, lib.Repository{Name: name, URL: url, Owner: owner})
|
||||
repositories = append(repositories, lib.Repository{Name: name, URL: repoUrl, Owner: owner, Description: description})
|
||||
}
|
||||
return repositories, nil
|
||||
}
|
||||
|
||||
func DefaultAddRemotePackageToDB(repository string, remoteEPK lib.RemoteEPK) error {
|
||||
func DefaultAddRemotePackageToDB(remoteEPK lib.RemoteEPK) error {
|
||||
// Delete the package if it already exists. This may happen in a force-install scenario.
|
||||
_, err := conn.Exec("DELETE FROM remotePackages WHERE name = ?", remoteEPK.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var dependencies string
|
||||
// This is the world's most basic JSON marshaller :P
|
||||
// - Arzumify
|
||||
|
@ -605,9 +857,9 @@ func DefaultAddRemotePackageToDB(repository string, remoteEPK lib.RemoteEPK) err
|
|||
hashBytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(hashBytes, remoteEPK.EPKHash)
|
||||
|
||||
_, err := conn.Exec("INSERT INTO remotePackages (name, author, description, version, architecture, size, dependencies, path, arch, hash, repository) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
_, err = conn.Exec("INSERT INTO remotePackages (name, author, description, version, architecture, size, dependencies, path, arch, hash, repository) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
remoteEPK.Name, remoteEPK.Author, remoteEPK.Description, remoteEPK.Version.String(), remoteEPK.Architecture,
|
||||
remoteEPK.CompressedSize, dependencies, remoteEPK.Path, remoteEPK.Arch, hashBytes, repository)
|
||||
remoteEPK.CompressedSize, dependencies, remoteEPK.Path, remoteEPK.Arch, hashBytes, remoteEPK.Repository.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -615,41 +867,157 @@ func DefaultAddRemotePackageToDB(repository string, remoteEPK lib.RemoteEPK) err
|
|||
return nil
|
||||
}
|
||||
|
||||
func DefaultRemoveRemotePackageFromDB(name string) error {
|
||||
_, err := conn.Exec("DELETE FROM remotePackages WHERE name = ?", name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DefaultListRemotePackagesInDB() ([]lib.RemoteEPK, error) {
|
||||
rows, err := conn.Query("SELECT name, author, description, version, architecture, size, dependencies, path, arch, hash, repository FROM remotePackages")
|
||||
if err != nil {
|
||||
return []lib.RemoteEPK{}, err
|
||||
}
|
||||
var remotePackages []lib.RemoteEPK
|
||||
for rows.Next() {
|
||||
var name, author, description, version, architecture, dependencies, path, arch, repository string
|
||||
var hashBytes []byte
|
||||
var size int64
|
||||
err := rows.Scan(&name, &author, &description, &version, &architecture, &size, &dependencies, &path, &arch,
|
||||
&hashBytes, &repository)
|
||||
if err != nil {
|
||||
return []lib.RemoteEPK{}, err
|
||||
}
|
||||
|
||||
var repositoryStruct lib.Repository
|
||||
repositoryStruct.Name = repository
|
||||
err = conn.QueryRow("SELECT url, owner, description FROM repositories WHERE name = ?", repository).Scan(
|
||||
&repositoryStruct.URL, &repositoryStruct.Owner, &repositoryStruct.Description)
|
||||
if err != nil {
|
||||
return []lib.RemoteEPK{}, err
|
||||
}
|
||||
|
||||
// This is the world's most basic JSON unmarshaller :P
|
||||
// - Arzumify
|
||||
dependenciesList := strings.Split(strings.TrimSuffix(strings.TrimPrefix(dependencies, "["), "]"), ", ")
|
||||
|
||||
// We use little-endian because big-endianness is never used on 99% of systems. Also, byte order is a myth.
|
||||
hash := binary.LittleEndian.Uint64(hashBytes)
|
||||
|
||||
// We use MustParse because we know the version is valid. If it isn't, someone was messing with the database,
|
||||
// and that's no longer our problem.
|
||||
remotePackages = append(remotePackages, lib.RemoteEPK{
|
||||
Name: name,
|
||||
Author: author,
|
||||
Description: description,
|
||||
Version: *semver.MustParse(version),
|
||||
Architecture: architecture,
|
||||
CompressedSize: size,
|
||||
Dependencies: dependenciesList,
|
||||
EPKHash: hash,
|
||||
Repository: repositoryStruct,
|
||||
Path: path,
|
||||
Arch: arch,
|
||||
})
|
||||
}
|
||||
|
||||
return remotePackages, nil
|
||||
}
|
||||
|
||||
func PrintWithEvenPadding(item string, maxSize int) {
|
||||
var printItem string
|
||||
// Make sure green escape sequences are not counted in the length by using a regex.
|
||||
noGreenEscape := regexp.MustCompile(`\x1b\[32m|\x1b\[0m`)
|
||||
// Minus 2 to ensure we have space for padding.
|
||||
if len(noGreenEscape.ReplaceAllString(item, "")) > maxSize-2 {
|
||||
// We have already checked that the terminal is wide enough, so we shouldn't have to worry about
|
||||
// maxSize being less than 6.
|
||||
// It's minus 5 because we need to add "..." to the end of the string and have space for padding.
|
||||
printItem = item[:maxSize-5] + "..."
|
||||
} else {
|
||||
printItem = item
|
||||
}
|
||||
// Now we need to check how much space we have left for padding.
|
||||
padding := maxSize - len(noGreenEscape.ReplaceAllString(printItem, ""))
|
||||
// Check if padding is odd or even.
|
||||
// Now we can count the length of the string without the green escape sequences.
|
||||
itemLength := len(noGreenEscape.ReplaceAllString(item, ""))
|
||||
// Let's calculate how much padding we need to fit itemLength into maxSize.
|
||||
padding := maxSize - itemLength
|
||||
// The padding will always be positive due to an earlier check.
|
||||
// Check if the padding is even or odd.
|
||||
if padding%2 == 0 {
|
||||
// Padding is even.
|
||||
for range padding / 2 {
|
||||
fmt.Print(" ")
|
||||
}
|
||||
fmt.Print(printItem)
|
||||
for range padding / 2 {
|
||||
fmt.Print(" ")
|
||||
}
|
||||
// If it's even, we can just divide it by 2 and add that amount of padding to the left and right.
|
||||
leftPadding := strings.Repeat(" ", padding/2)
|
||||
rightPadding := strings.Repeat(" ", padding/2)
|
||||
printItem = leftPadding + item + rightPadding
|
||||
} else {
|
||||
// Padding is odd.
|
||||
for range (padding + 1) / 2 {
|
||||
fmt.Print(" ")
|
||||
}
|
||||
fmt.Print(printItem)
|
||||
for range (padding - 1) / 2 {
|
||||
fmt.Print(" ")
|
||||
// If it's odd, we add one more space to the right padding.
|
||||
leftPadding := strings.Repeat(" ", padding/2)
|
||||
rightPadding := strings.Repeat(" ", padding/2+1)
|
||||
printItem = leftPadding + item + rightPadding
|
||||
}
|
||||
// Print the string.
|
||||
fmt.Print(printItem)
|
||||
}
|
||||
|
||||
func RefreshPackageList(listRepositoriesInDB func() ([]lib.Repository, error), addRemotePackageToDB func(lib.RemoteEPK) error, getFingerprintFromDB func([]byte, string) (bool, bool, bool, error), addFingerprintToDB func([]byte, string, bool) error, checkRepositoryInDB func(string) (bool, error), addRepositoryToDB func(lib.Repository, bool) error, listRemotePackagesInDB func() ([]lib.RemoteEPK, error), removeRemotePackageFromDB func(string) error, logger *lib.Logger) error {
|
||||
logger.LogFunc(lib.Log{
|
||||
Level: "INFO",
|
||||
Content: "Refreshing package list...",
|
||||
})
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
// Fetch new packages from repositories, that's easy.
|
||||
repositories, err := listRepositoriesInDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repositoryMap := make(map[string]lib.Repository)
|
||||
for _, repository := range repositories {
|
||||
repositoryMap[repository.Name] = repository
|
||||
_, err, vagueErr := lib.AddRepository(repository.URL, addRepositoryToDB, getFingerprintFromDB, addFingerprintToDB,
|
||||
addRemotePackageToDB, checkRepositoryInDB, true, logger)
|
||||
if err != nil || vagueErr != nil {
|
||||
AddRepositoryHandleError(err, vagueErr, logger)
|
||||
}
|
||||
}
|
||||
|
||||
// We need to check if there are any orphaned packages with URLs that no longer exist.
|
||||
packages, err := listRemotePackagesInDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
removedOrphanedPackages := 0
|
||||
for _, remoteEpk := range packages {
|
||||
testUrl, err := url.JoinPath(remoteEpk.Repository.URL, remoteEpk.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := http.Head(testUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if response.StatusCode != 200 && response.StatusCode != 404 {
|
||||
// We don't delete unless it's 404 because the server may be down.
|
||||
logger.LogFunc(lib.Log{
|
||||
Level: "WARN",
|
||||
Content: "The package " + remoteEpk.Name + " has returned a status code of " + strconv.Itoa(response.StatusCode) + ". This may indicate a problem with the repository.",
|
||||
Prompt: false,
|
||||
})
|
||||
} else if response.StatusCode == 404 {
|
||||
// Delete the package.
|
||||
err := removeRemotePackageFromDB(remoteEpk.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
removedOrphanedPackages++
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogFunc(lib.Log{
|
||||
Level: "INFO",
|
||||
Content: "Refreshed package list in " + humanize.CustomRelTime(startTime, time.Now(), "", "",
|
||||
TimeMagnitudes) + ". Removed " + strconv.Itoa(removedOrphanedPackages) + " orphaned packages.",
|
||||
Prompt: false,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// I hate the vagueError system - it feels too much like a hack, but there's no chance in hell I'm going to find every
|
||||
|
@ -930,7 +1298,7 @@ func RemoveRepositoryHandleError(err error, vagueErr error, logger *lib.Logger)
|
|||
Content: "Repository does not exist",
|
||||
Prompt: false,
|
||||
})
|
||||
case errors.Is(vagueErr, lib.ErrRemoveRepositoryCannotStatRepositoryError):
|
||||
case errors.Is(vagueErr, lib.ErrRemoveRepositoryCannotFindRepositoryError):
|
||||
logger.LogFunc(lib.Log{
|
||||
Level: "FATAL",
|
||||
Content: "Failed to get file information about repository: " + err.Error(),
|
||||
|
|
7
go.mod
7
go.mod
|
@ -8,7 +8,6 @@ require (
|
|||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/fatih/color v1.17.0
|
||||
github.com/klauspost/compress v1.17.9
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
golang.org/x/term v0.23.0
|
||||
modernc.org/sqlite v1.32.0
|
||||
)
|
||||
|
@ -20,9 +19,9 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
golang.org/x/sys v0.23.0 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||
modernc.org/libc v1.55.3 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a // indirect
|
||||
modernc.org/libc v1.60.1 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/strutil v1.2.0 // indirect
|
||||
|
|
12
go.sum
12
go.sum
|
@ -19,8 +19,6 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
|||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
@ -29,26 +27,36 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
|
|||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
|
||||
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
|
||||
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||
modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a h1:CfbpOLEo2IwNzJdMvE8aiRbPMxoTpgAJeyePh0SmO8M=
|
||||
modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
|
||||
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
|
||||
modernc.org/libc v1.60.1 h1:at373l8IFRTkJIkAU85BIuUoBM4T1b51ds0E1ovPG2s=
|
||||
modernc.org/libc v1.60.1/go.mod h1:xJuobKuNxKH3RUatS7GjR+suWj+5c2K7bi4m/S5arOY=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
|
|
631
lib/main.go
631
lib/main.go
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue