Actually write mod files

but aaa it doesn't use the struct tags properly so things are bad
This commit is contained in:
comp500 2019-05-11 14:32:14 +01:00
parent 3fdac51d22
commit ef98591d02
No known key found for this signature in database
GPG Key ID: 214C822FFEC586B5
4 changed files with 100 additions and 36 deletions

View File

@ -1,23 +1,27 @@
package core package core
import ( import (
"errors" "errors"
"os"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
) )
// Mod stores metadata about a mod. This is written to a TOML file for each mod. // Mod stores metadata about a mod. This is written to a TOML file for each mod.
type Mod struct { type Mod struct {
metaFilename string // The filename for the metadata file, used as an ID metaFile string // The file for the metadata file, used as an ID
Name string `toml:"name"` Name string `toml:"name"`
FileName string `toml:"filename"` FileName string `toml:"filename"`
Side string `toml:"side,omitempty"` Side string `toml:"side,omitempty"`
Optional bool `toml:"optional,omitempty"` Optional bool `toml:"optional,omitempty"`
Download struct { Download ModDownload `toml:"download"`
Update map[string]interface{} `toml:"update"`
}
// ModDownload specifies how to download the mod file
type ModDownload struct {
URL string `toml:"url"` URL string `toml:"url"`
HashFormat string `toml:"hash-format"` HashFormat string `toml:"hash-format"`
Hash string `toml:"hash"` Hash string `toml:"hash"`
} `toml:"download"`
Update map[string]interface{} `toml:"update"`
} }
// The three possible values of Side (the side that the mod is on) are "server", "client", and "both". // The three possible values of Side (the side that the mod is on) are "server", "client", and "both".
@ -46,10 +50,26 @@ func LoadMod(modFile string) (Mod, error) {
return mod, errors.New("Update plugin " + k + " not found!") return mod, errors.New("Update plugin " + k + " not found!")
} }
} }
mod.metaFile = modFile
return mod, nil return mod, nil
} }
func (m Mod) Write() { // SetMetaName sets the mod metadata file from a given file name (to be put in the mods folder)
func (m *Mod) SetMetaName(metaName string, flags Flags) {
m.metaFile = ResolveMod(metaName, flags)
}
// Write saves the mod file
func (m Mod) Write() error {
f, err := os.Create(m.metaFile)
if err != nil {
return err
}
defer f.Close()
enc := toml.NewEncoder(f)
// Disable indentation
enc.Indent = ""
return enc.Encode(m)
} }

View File

@ -94,7 +94,7 @@ func (pack Pack) Write() error {
func (pack Pack) GetMCVersion() (string, error) { func (pack Pack) GetMCVersion() (string, error) {
mcVersion, ok := pack.Versions["minecraft"] mcVersion, ok := pack.Versions["minecraft"]
if !ok { if !ok {
return "", errors.New("No Minecraft version specified in modpack!") return "", errors.New("no minecraft version specified in modpack")
} }
return mcVersion, nil return mcVersion, nil
} }

View File

@ -1,8 +1,10 @@
package curseforge package curseforge
import ( import (
"errors"
"fmt" "fmt"
"regexp" "regexp"
"strconv" "strconv"
"strings"
"github.com/comp500/packwiz/core" "github.com/comp500/packwiz/core"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
@ -18,7 +20,7 @@ func init() {
Usage: "Install a mod from a curseforge URL, slug or ID", Usage: "Install a mod from a curseforge URL, slug or ID",
Aliases: []string{"add", "get"}, Aliases: []string{"add", "get"},
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
return cmdInstall(core.FlagsFromContext(c), c.Args().Get(0)) return cmdInstall(core.FlagsFromContext(c), c.Args().Get(0), c.Args().Tail())
}, },
}, { }, {
Name: "import", Name: "import",
@ -33,8 +35,8 @@ func init() {
} }
var fileIDRegexes = [...]*regexp.Regexp{ var fileIDRegexes = [...]*regexp.Regexp{
regexp.MustCompile("https?:\\/\\/minecraft\\.curseforge\\.com\\/projects\\/(.+)\\/files\\/(\\d+)"), regexp.MustCompile("^https?:\\/\\/minecraft\\.curseforge\\.com\\/projects\\/(.+)\\/files\\/(\\d+)$"),
regexp.MustCompile("https?:\\/\\/(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/(.+)\\/download\\/(\\d+)"), regexp.MustCompile("^https?:\\/\\/(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/(.+)\\/download\\/(\\d+)$"),
} }
func getFileIDsFromString(mod string) (bool, int, int, error) { func getFileIDsFromString(mod string) (bool, int, int, error) {
@ -53,10 +55,10 @@ func getFileIDsFromString(mod string) (bool, int, int, error) {
} }
var modSlugRegexes = [...]*regexp.Regexp{ var modSlugRegexes = [...]*regexp.Regexp{
regexp.MustCompile("https?:\\/\\/minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)"), regexp.MustCompile("^https?:\\/\\/minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)$"),
regexp.MustCompile("https?:\\/\\/(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/([^\\/]+)"), regexp.MustCompile("^https?:\\/\\/(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/([^\\/]+)$"),
// Exact slug matcher // Exact slug matcher
regexp.MustCompile("[a-z][\\da-z\\-]{0,127}"), regexp.MustCompile("^[a-z][\\da-z\\-]{0,127}$"),
} }
func getModIDFromString(mod string) (bool, int, error) { func getModIDFromString(mod string) (bool, int, error) {
@ -87,7 +89,7 @@ func getModIDFromString(mod string) (bool, int, error) {
return false, 0, nil return false, 0, nil
} }
func cmdInstall(flags core.Flags, mod 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)
} }
@ -111,26 +113,67 @@ func cmdInstall(flags core.Flags, mod string) error {
if !done { if !done {
done, modID, err = getModIDFromString(mod) done, modID, err = getModIDFromString(mod)
if err != nil { // Handle error later (e.g. lowercase to search instead of as a slug)
return cli.NewExitError(err, 1)
}
} }
// TODO: fallback to CurseMeta search if !done {
modArgs := append([]string{mod}, modArgsTail...)
searchTerm := strings.Join(modArgs, " ")
// TODO: CurseMeta search
// TODO: how to do interactive choices? automatically assume version? ask mod from list? choose first? // TODO: how to do interactive choices? automatically assume version? ask mod from list? choose first?
fmt.Println(searchTerm)
}
if !done {
if err == nil {
err = errors.New("Mod not found")
}
return cli.NewExitError(err, 1)
}
fmt.Printf("ids: %d %d %v\n", modID, fileID, done) fmt.Printf("ids: %d %d %v\n", modID, fileID, done)
if done { // TODO: get FileID if it isn't there
fmt.Println(mcVersion) fmt.Println(mcVersion)
info, err := getModInfo(modID) modInfo, err := getModInfo(modID)
fmt.Println(err) fmt.Println(err)
fmt.Println(info) fmt.Println(modInfo)
_ = index _ = index
}
if fileID == 0 {
return nil return nil
} }
fileInfo, err := getFileInfo(modID, fileID)
updateMap := make(map[string]interface{})
updateMap["curseforge"] = cfUpdater{
ProjectID: modID,
FileID: fileID,
// TODO: determine update channel
ReleaseChannel: "release",
}
modMeta := core.Mod{
Name: modInfo.Name,
FileName: fileInfo.FileName,
Side: core.UniversalSide,
Download: core.ModDownload{
URL: fileInfo.DownloadURL,
HashFormat: "murmur2",
Hash: strconv.Itoa(fileInfo.Fingerprint),
},
Update: updateMap,
}
modMeta.SetMetaName(modInfo.Slug, flags)
fmt.Printf("%#v\n", modMeta)
return modMeta.Write()
}
type cfUpdateParser struct{} type cfUpdateParser struct{}
func (u cfUpdateParser) ParseUpdate(updateUnparsed interface{}) (core.Updater, error) { func (u cfUpdateParser) ParseUpdate(updateUnparsed interface{}) (core.Updater, error) {
@ -140,9 +183,9 @@ func (u cfUpdateParser) ParseUpdate(updateUnparsed interface{}) (core.Updater, e
} }
type cfUpdater struct { type cfUpdater struct {
ProjectID int `mapstructure:"project-id"` ProjectID int `mapstructure:"project-id" toml:"project-id"`
FileID int `mapstructure:"file-id"` FileID int `mapstructure:"file-id" toml:"file-id"`
ReleaseChannel string `mapstructure:"release-channel"` ReleaseChannel string `mapstructure:"release-channel" toml:"release-channel"`
} }
func (u cfUpdater) DoUpdate(mod core.Mod) (bool, error) { func (u cfUpdater) DoUpdate(mod core.Mod) (bool, error) {

View File

@ -188,6 +188,7 @@ type modFileInfo struct {
Length int `json:"fileLength"` Length int `json:"fileLength"`
FileType int `json:"releaseType"` FileType int `json:"releaseType"`
// fileStatus? means latest/preferred? // fileStatus? means latest/preferred?
DownloadURL string `json:"downloadUrl"`
GameVersions []string `json:"gameVersion"` GameVersions []string `json:"gameVersion"`
Fingerprint int `json:"packageFingerprint"` Fingerprint int `json:"packageFingerprint"`
Dependencies []struct { Dependencies []struct {