diff --git a/core/interfaces.go b/core/interfaces.go index ce2d25f..a8e4301 100644 --- a/core/interfaces.go +++ b/core/interfaces.go @@ -8,9 +8,9 @@ type Updater interface { // 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, + // CheckUpdate checks whether there is an update for each of the mods in the given slice, for the given MC version, // called for all of the mods that this updater handles - CheckUpdate([]Mod) ([]UpdateCheck, error) + CheckUpdate([]Mod, string) ([]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 @@ -31,7 +31,6 @@ type UpdateCheck struct { Error error } -// TODO: new docs // to carry out updating: // go through all metafiles in index diff --git a/curseforge/curseforge.go b/curseforge/curseforge.go index 7ccaf5e..081971f 100644 --- a/curseforge/curseforge.go +++ b/curseforge/curseforge.go @@ -1,6 +1,7 @@ package curseforge import ( + "errors" "fmt" "regexp" "strconv" @@ -198,16 +199,91 @@ func (u cfUpdater) ParseUpdate(updateUnparsed map[string]interface{}) (interface return updateData, err } -func (u cfUpdater) CheckUpdate(mod []core.Mod) ([]core.UpdateCheck, error) { - return nil, nil +type cachedStateStore struct { + modInfo + fileInfo modFileInfo } -func (u cfUpdater) DoUpdate(mod []*core.Mod, cachedState []interface{}) error { - // TODO: implement updating - // modInfoData, err := getModInfo(u.ProjectID) - // if err != nil { - // return false, err - // } +func (u cfUpdater) CheckUpdate(mods []core.Mod, mcVersion string) ([]core.UpdateCheck, error) { + results := make([]core.UpdateCheck, len(mods)) + + // TODO: make this batched + for i, v := range mods { + projectRaw, ok := v.GetParsedUpdateData("curseforge") + if !ok { + results[i] = core.UpdateCheck{Error: errors.New("couldn't parse mod data")} + continue + } + project := projectRaw.(cfUpdateData) + modInfoData, err := getModInfo(project.ProjectID) + if err != nil { + results[i] = core.UpdateCheck{Error: err} + continue + } + + updateAvailable := false + fileID := project.FileID + fileInfoObtained := false + var fileInfoData modFileInfo + + for _, file := range modInfoData.GameVersionLatestFiles { + // TODO: change to timestamp-based comparison?? + // TODO: manage alpha/beta/release correctly, check update channel? + // Choose "newest" version by largest ID + if file.GameVersion == mcVersion && file.ID > fileID { + updateAvailable = true + fileID = file.ID + } + } + + if !updateAvailable { + results[i] = core.UpdateCheck{UpdateAvailable: false} + continue + } + + // The API also provides some files inline, because that's efficient! + for _, file := range modInfoData.LatestFiles { + if file.ID == fileID { + fileInfoObtained = true + fileInfoData = file + } + } + + if !fileInfoObtained { + fileInfoData, err = getFileInfo(project.ProjectID, fileID) + if err != nil { + results[i] = core.UpdateCheck{Error: err} + continue + } + } + + results[i] = core.UpdateCheck{ + UpdateAvailable: true, + UpdateString: v.FileName + " -> " + fileInfoData.FileName, + CachedState: cachedStateStore{modInfoData, fileInfoData}, + } + } + return results, nil +} + +func (u cfUpdater) DoUpdate(mods []*core.Mod, cachedState []interface{}) error { + // "Do" isn't really that accurate, more like "Apply", because all the work is done in CheckUpdate! + for i, v := range mods { + modState := cachedState[i].(cachedStateStore) + + v.FileName = modState.fileInfo.FileName + v.Name = modState.Name + v.Download = core.ModDownload{ + URL: modState.fileInfo.DownloadURL, + // TODO: murmur2 hashing may be unstable in curse api, calculate the hash manually? + // TODO: check if the hash is invalid (e.g. 0) + HashFormat: "murmur2", + Hash: strconv.Itoa(modState.fileInfo.Fingerprint), + } + + v.Update["curseforge"]["ProjectID"] = modState.ID + v.Update["curseforge"]["FileID"] = modState.fileInfo.ID + } return nil } diff --git a/curseforge/install.go b/curseforge/install.go index 3291bce..479efc3 100644 --- a/curseforge/install.go +++ b/curseforge/install.go @@ -125,7 +125,7 @@ func cmdInstall(flags core.Flags, mod string, modArgsTail []string) error { fileInfoObtained := false var fileInfoData modFileInfo if fileID == 0 { - // TODO: how do we decide which version to use? + // TODO: change to timestamp-based comparison?? for _, v := range modInfoData.GameVersionLatestFiles { // Choose "newest" version by largest ID if v.GameVersion == mcVersion && v.ID > fileID {