Added remote repository downloading

This commit is contained in:
Arzumify 2024-09-03 19:58:53 +01:00
parent 9952e3bc96
commit 7b0aba2ad7
7 changed files with 1228 additions and 366 deletions

16
.vscode/launch.json vendored Normal file
View 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

Binary file not shown.

View file

@ -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())
} 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,
})
for _, pkg := range skipEpkList {
for _, item := range pkg {
common.PrintWithEvenPadding(color.GreenString(item), maxSize)
}
}
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 _, 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 {
finalisedList[3] = pkg.Repository.Name
}
finalisedList[4] = humanize.BigIBytes(pkg.EPKPreMap.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)
}
fmt.Print("\n")
}
//for _, pkg := range repoPackageList {
// var finalisedList []string
// lib.SearchRepository()
//}
if len(epkList) > 0 {
for range width {
fmt.Print("=")
}
fmt.Print("\n")
if len(epkListLocal) > 0 {
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()
}

View file

@ -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 = &currentInstallPackageList
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 = &currentInstallPackageList
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 = &currentInstallPackageList
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
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(" ")
// 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)
for range (padding - 1) / 2 {
fmt.Print(" ")
}
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
View file

@ -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
View file

@ -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=

File diff suppressed because it is too large Load diff