Automagically install dependencies

This commit is contained in:
comp500 2019-06-18 18:20:23 +01:00
parent c8359ca794
commit 8dd47e2cbb
No known key found for this signature in database
GPG Key ID: 214C822FFEC586B5
2 changed files with 157 additions and 28 deletions

View File

@ -1,7 +1,10 @@
package curseforge package curseforge
import ( import (
"bufio"
"errors"
"fmt" "fmt"
"os"
"sort" "sort"
"strings" "strings"
@ -11,6 +14,13 @@ import (
"gopkg.in/dixonwille/wmenu.v4" "gopkg.in/dixonwille/wmenu.v4"
) )
const maxCycles = 20
type installableDep struct {
modInfo
fileInfo modFileInfo
}
func cmdInstall(flags core.Flags, mod string, modArgsTail []string) error { func cmdInstall(flags core.Flags, mod string, modArgsTail []string) error {
if len(mod) == 0 { if len(mod) == 0 {
return cli.NewExitError("You must specify a mod.", 1) return cli.NewExitError("You must specify a mod.", 1)
@ -53,8 +63,6 @@ func cmdInstall(flags core.Flags, mod string, modArgsTail []string) error {
fmt.Println("Searching CurseForge...") fmt.Println("Searching CurseForge...")
modArgs := append([]string{mod}, modArgsTail...) modArgs := append([]string{mod}, modArgsTail...)
searchTerm := strings.Join(modArgs, " ") searchTerm := strings.Join(modArgs, " ")
// TODO: Curse search
// TODO: how to do interactive choices? automatically assume version? ask mod from list? choose first?
results, err := getSearch(searchTerm, mcVersion) results, err := getSearch(searchTerm, mcVersion)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
@ -122,34 +130,122 @@ func cmdInstall(flags core.Flags, mod string, modArgsTail []string) error {
} }
} }
fileInfoObtained := false
var fileInfoData modFileInfo var fileInfoData modFileInfo
if fileID == 0 { fileInfoData, err = getLatestFile(modInfoData, mcVersion, fileID)
// TODO: change to timestamp-based comparison?? if err != nil {
for _, v := range modInfoData.GameVersionLatestFiles { return cli.NewExitError(err, 1)
// Choose "newest" version by largest ID
if v.GameVersion == mcVersion && v.ID > fileID {
fileID = v.ID
}
}
if fileID == 0 {
return cli.NewExitError("No files available for current Minecraft version!", 1)
}
// The API also provides some files inline, because that's efficient!
for _, v := range modInfoData.LatestFiles {
if v.ID == fileID {
fileInfoObtained = true
fileInfoData = v
}
}
} }
if !fileInfoObtained { if len(fileInfoData.Dependencies) > 0 {
fileInfoData, err = getFileInfo(modID, fileID) var depsInstallable []installableDep
if err != nil { var depIDPendingQueue []int
return cli.NewExitError(err, 1) for _, dep := range fileInfoData.Dependencies {
if dep.Type == dependencyTypeRequired {
depIDPendingQueue = append(depIDPendingQueue, dep.ModID)
}
}
if len(depIDPendingQueue) > 0 {
fmt.Println("Finding dependencies...")
cycles := 0
var installedIDList []int
for len(depIDPendingQueue) > 0 && cycles < maxCycles {
if installedIDList == nil {
// Get modids of all mods
for _, modPath := range index.GetAllMods() {
mod, err := core.LoadMod(modPath)
if err == nil {
data, ok := mod.GetParsedUpdateData("curseforge")
if ok {
updateData, ok := data.(cfUpdateData)
if ok {
if updateData.ProjectID > 0 {
installedIDList = append(installedIDList, updateData.ProjectID)
}
}
}
}
}
}
// Remove installed IDs from dep queue
i := 0
for _, id := range depIDPendingQueue {
contains := false
for _, id2 := range installedIDList {
if id == id2 {
contains = true
break
}
}
for _, data := range depsInstallable {
if id == data.ID {
contains = true
break
}
}
if !contains {
depIDPendingQueue[i] = id
i++
}
}
depIDPendingQueue = depIDPendingQueue[:i]
depInfoData, err := getModInfoMultiple(depIDPendingQueue)
if err != nil {
fmt.Printf("Error retrieving dependency data: %s\n", err.Error())
}
depIDPendingQueue = depIDPendingQueue[:0]
for _, currData := range depInfoData {
depFileInfo, err := getLatestFile(currData, mcVersion, 0)
if err != nil {
fmt.Printf("Error retrieving dependency data: %s\n", err.Error())
}
for _, dep := range depFileInfo.Dependencies {
if dep.Type == dependencyTypeRequired {
depIDPendingQueue = append(depIDPendingQueue, dep.ModID)
}
}
depsInstallable = append(depsInstallable, installableDep{
currData, depFileInfo,
})
}
cycles++
}
if cycles >= maxCycles {
return cli.NewExitError("Dependencies recurse too deeply! Try increasing maxCycles.", 1)
}
if len(depsInstallable) > 0 {
fmt.Println("Dependencies found:")
for _, v := range depsInstallable {
fmt.Println(v.Name)
}
fmt.Print("Would you like to install them? [Y/n]: ")
answer, err := bufio.NewReader(os.Stdin).ReadString('\n')
if err != nil {
return cli.NewExitError(err, 1)
}
ansNormal := strings.ToLower(strings.TrimSpace(answer))
if !(len(ansNormal) > 0 && ansNormal[0] == 'n') {
for _, v := range depsInstallable {
err = createModFile(flags, v.modInfo, v.fileInfo, &index)
if err != nil {
return cli.NewExitError(err, 1)
}
fmt.Printf("Dependency \"%s\" successfully installed! (%s)\n", v.modInfo.Name, v.fileInfo.FileName)
}
}
} else {
fmt.Println("All dependencies are already installed!")
}
} }
} }
@ -175,3 +271,32 @@ func cmdInstall(flags core.Flags, mod string, modArgsTail []string) error {
return nil return nil
} }
func getLatestFile(modInfoData modInfo, mcVersion string, fileID int) (modFileInfo, error) {
if fileID == 0 {
// TODO: change to timestamp-based comparison??
for _, v := range modInfoData.GameVersionLatestFiles {
// Choose "newest" version by largest ID
if v.GameVersion == mcVersion && v.ID > fileID {
fileID = v.ID
}
}
}
if fileID == 0 {
return modFileInfo{}, errors.New("mod not available for this minecraft version")
}
// The API also provides some files inline, because that's efficient!
for _, v := range modInfoData.LatestFiles {
if v.ID == fileID {
return v, nil
}
}
fileInfoData, err := getFileInfo(modInfoData.ID, fileID)
if err != nil {
return modFileInfo{}, err
}
return fileInfoData, nil
}

View File

@ -96,8 +96,12 @@ const (
) )
const ( const (
dependencyTypeRequired int = iota + 1 dependencyTypeEmbedded int = iota + 1
dependencyTypeOptional dependencyTypeOptional
dependencyTypeRequired
dependencyTypeTool
dependencyTypeIncompatible
dependencyTypeInclude
) )
// modInfo is a subset of the deserialised JSON response from the Curse API for mods (addons) // modInfo is a subset of the deserialised JSON response from the Curse API for mods (addons)