From b77e2080c70cd0a5366ca9fd3aa4ef1b56642d99 Mon Sep 17 00:00:00 2001 From: comp500 Date: Sun, 16 Jun 2019 14:25:54 +0100 Subject: [PATCH] Redo update system to work with batched updates --- core/index.go | 7 +++--- core/interfaces.go | 47 +++++++++++++++++++++++++++++++--------- core/mod.go | 20 ++++++++--------- curseforge/curseforge.go | 46 +++++++++++++++++++++------------------ 4 files changed, 76 insertions(+), 44 deletions(-) diff --git a/core/index.go b/core/index.go index e53e1d5..b7e0805 100644 --- a/core/index.go +++ b/core/index.go @@ -7,8 +7,8 @@ import ( "os" "path/filepath" "sort" - "time" "strings" + "time" "github.com/BurntSushi/toml" "github.com/vbauerster/mpb/v4" @@ -192,6 +192,7 @@ func (in *Index) Refresh() error { } start := time.Now() defer func() { + // TODO: this is stupid, traverse the file tree first *then* read all the files if progressCurrent >= progressLength { progressLength++ progress.SetTotal(int64(progressLength), false) @@ -272,7 +273,7 @@ func (in *Index) RefreshFileWithHash(path, format, hash string, mod bool) error func (in Index) FindMod(modName string) (string, bool) { for _, v := range in.Files { if v.MetaFile { - _, file := filepath.Split(v.File); + _, file := filepath.Split(v.File) fileTrimmed := strings.TrimSuffix(file, ModExtension) if fileTrimmed == modName { return v.File, true @@ -280,4 +281,4 @@ func (in Index) FindMod(modName string) (string, bool) { } } return "", false -} \ No newline at end of file +} diff --git a/core/interfaces.go b/core/interfaces.go index 9ca5bf6..ce2d25f 100644 --- a/core/interfaces.go +++ b/core/interfaces.go @@ -1,16 +1,43 @@ package core -// UpdateParsers stores all the update parsers that packwiz can use. Add your own update systems to this map. -var UpdateParsers = make(map[string]UpdateParser) -// UpdateParser takes an unparsed interface{} (as a map[string]interface{}), and returns an Updater for a mod file. -// This can be done using the mapstructure library or your own parsing methods. -type UpdateParser interface { - ParseUpdate(map[string]interface{}) (Updater, error) -} +// Updaters stores all the updaters that packwiz can use. Add your own update systems to this map, keyed by the configuration name. +var Updaters = make(map[string]Updater) -// Updater checks for and does updates on a mod +// Updater is used to process updates on mods type Updater interface { - // DoUpdate returns true if an update was done, false otherwise - DoUpdate(Mod) (bool, error) + // ParseUpdate takes an unparsed interface{} (as a map[string]interface{}), and returns an Updater for a mod file. + // This can be done using the mapstructure library or your own parsing methods. + ParseUpdate(map[string]interface{}) (interface{}, error) + // CheckUpdate checks whether there is an update for each of the mods in the given slice, + // called for all of the mods that this updater handles + CheckUpdate([]Mod) ([]UpdateCheck, error) + // DoUpdate carries out the update previously queried in CheckUpdate, on each Mod's metadata, + // given pointers to Mods and the value of CachedState for each mod + DoUpdate([]*Mod, []interface{}) error } +// UpdateCheck represents the data returned from CheckUpdate for each mod +type UpdateCheck struct { + // UpdateAvailable is true if an update is available for this mod + UpdateAvailable bool + // UpdateString is a string that details the update in some way to the user. Usually this will be in the form of + // a version change (1.0.0 -> 1.0.1), or a file name change (thanos-skin-1.0.0.jar -> thanos-skin-1.0.1.jar). + UpdateString string + // CachedState can be used to preserve per-mod state between CheckUpdate and DoUpdate (e.g. file metadata) + CachedState interface{} + // Error stores an error for this specific mod + // Errors can also be returned from CheckUpdate directly, if the whole operation failed completely (so only 1 error is printed) + // If an error is returned for a mod, or from CheckUpdate, DoUpdate is not called on that mod / at all + Error error +} + +// TODO: new docs +// to carry out updating: + +// go through all metafiles in index +// make a []Mod for each updater, so map[string][]Mod +// for each Mod, check the "first" updater, then give the Mod to the map + +// go through the map, call CheckUpdate with the []Mod +// print to user, if interactive mode +// call doupdate with the mods and interfaces!! diff --git a/core/mod.go b/core/mod.go index 81b561a..83e4fd0 100644 --- a/core/mod.go +++ b/core/mod.go @@ -19,8 +19,8 @@ type Mod struct { Optional bool `toml:"optional,omitempty"` Download ModDownload `toml:"download"` // Update is a map of map of stuff, so you can store arbitrary values on string keys to define updating - Update map[string]map[string]interface{} `toml:"update"` - updaters map[string]Updater + Update map[string]map[string]interface{} `toml:"update"` + updateData map[string]interface{} } // ModDownload specifies how to download the mod file @@ -43,16 +43,16 @@ func LoadMod(modFile string) (Mod, error) { if _, err := toml.DecodeFile(modFile, &mod); err != nil { return Mod{}, err } - mod.updaters = make(map[string]Updater) - // Horrible reflection library to convert to Updaters + mod.updateData = make(map[string]interface{}) + // Horrible reflection library to convert map[string]interface to proper struct for k, v := range mod.Update { - updateParser, ok := UpdateParsers[k] + updater, ok := Updaters[k] if ok { - updater, err := updateParser.ParseUpdate(v) + updateData, err := updater.ParseUpdate(v) if err != nil { return mod, err } - mod.updaters[k] = updater + mod.updateData[k] = updateData } else { return mod, errors.New("Update plugin " + k + " not found!") } @@ -86,8 +86,8 @@ func (m Mod) Write() (string, string, error) { return "sha256", hashString, err } -// GetParsedUpdater can be used to retrieve updater-specific information after parsing a mod file -func (m Mod) GetParsedUpdater(updaterName string) (Updater, bool) { - upd, ok := m.updaters[updaterName] +// GetParsedUpdateData can be used to retrieve updater-specific information after parsing a mod file +func (m Mod) GetParsedUpdateData(updaterName string) (interface{}, bool) { + upd, ok := m.updateData[updaterName] return upd, ok } diff --git a/curseforge/curseforge.go b/curseforge/curseforge.go index 945801f..7ccaf5e 100644 --- a/curseforge/curseforge.go +++ b/curseforge/curseforge.go @@ -38,7 +38,7 @@ func init() { }, }}, }) - core.UpdateParsers["curseforge"] = cfUpdateParser{} + core.Updaters["curseforge"] = cfUpdater{} } var fileIDRegexes = [...]*regexp.Regexp{ @@ -100,7 +100,7 @@ func createModFile(flags core.Flags, modInfo modInfo, fileInfo modFileInfo, inde updateMap := make(map[string]map[string]interface{}) var err error - updateMap["curseforge"], err = cfUpdater{ + updateMap["curseforge"], err = cfUpdateData{ ProjectID: modInfo.ID, FileID: fileInfo.ID, // TODO: determine update channel @@ -162,11 +162,11 @@ func cmdDoc(flags core.Flags, mod string) error { if err != nil { return cli.NewExitError(err, 1) } - updateData, ok := modData.GetParsedUpdater("curseforge") + updateData, ok := modData.GetParsedUpdateData("curseforge") if !ok { return cli.NewExitError("This mod doesn't seem to be a curseforge mod!", 1) } - cfUpdateData := updateData.(cfUpdater) + cfUpdateData := updateData.(cfUpdateData) fmt.Println("Opening browser...") url := "https://minecraft.curseforge.com/projects/" + strconv.Itoa(cfUpdateData.ProjectID) err = open.Start(url) @@ -178,32 +178,36 @@ func cmdDoc(flags core.Flags, mod string) error { return nil } -type cfUpdateParser struct{} - -func (u cfUpdateParser) ParseUpdate(updateUnparsed map[string]interface{}) (core.Updater, error) { - var updater cfUpdater - err := mapstructure.Decode(updateUnparsed, &updater) - return updater, err -} - -type cfUpdater struct { +type cfUpdateData struct { ProjectID int `mapstructure:"project-id"` FileID int `mapstructure:"file-id"` ReleaseChannel string `mapstructure:"release-channel"` } -func (u cfUpdater) DoUpdate(mod core.Mod) (bool, error) { +func (u cfUpdateData) ToMap() (map[string]interface{}, error) { + newMap := make(map[string]interface{}) + err := mapstructure.Decode(u, &newMap) + return newMap, err +} + +type cfUpdater struct{} + +func (u cfUpdater) ParseUpdate(updateUnparsed map[string]interface{}) (interface{}, error) { + var updateData cfUpdateData + err := mapstructure.Decode(updateUnparsed, &updateData) + return updateData, err +} + +func (u cfUpdater) CheckUpdate(mod []core.Mod) ([]core.UpdateCheck, error) { + return nil, nil +} + +func (u cfUpdater) DoUpdate(mod []*core.Mod, cachedState []interface{}) error { // TODO: implement updating // modInfoData, err := getModInfo(u.ProjectID) // if err != nil { // return false, err // } - return false, nil -} - -func (u cfUpdater) ToMap() (map[string]interface{}, error) { - newMap := make(map[string]interface{}) - err := mapstructure.Decode(u, &newMap) - return newMap, err + return nil }