diff --git a/cmd/fml/fml.go b/cmd/fml/fml.go index f15b522..9769bc5 100644 --- a/cmd/fml/fml.go +++ b/cmd/fml/fml.go @@ -14,9 +14,10 @@ import ( ) type config struct { - modsdir, infile, outfile string - mods modlist.Modlist - clobber, addmissing, missingstate bool + modsdir, infile, outfile string + mods modlist.Modlist + clobber, addmissing bool + missingstate, ignoreuninstalled bool } var cfg config @@ -85,6 +86,12 @@ func main() { Required: false, Usage: "Missing mods are added in enabled state", }, + &cli.BoolFlag{ + Name: "ignore-uninstalled", + Aliases: []string{"u"}, + Required: false, + Usage: "Missing mod archives are ignored", + }, }, Before: func(ctx *cli.Context) error { // Setup logger @@ -100,6 +107,7 @@ func main() { cfg.clobber = ctx.Bool("clobber") cfg.addmissing = !ctx.Bool("no-add-missing") cfg.missingstate = ctx.Bool("enable-missing") + cfg.ignoreuninstalled = ctx.Bool("ignore-uninstalled") // Deal with in/out file var in = ctx.String("infile") @@ -125,13 +133,20 @@ func main() { Action: func(ctx *cli.Context) error { var err error - cfg.mods, err = modlist.ReadFromFile(cfg.infile) + cfg.mods, err = modlist.ReadFromFile(cfg.infile, cfg.modsdir) if err != nil { return err } if cfg.addmissing { - err = modlist.AddModsNotInList(cfg.modsdir, &cfg.mods, cfg.missingstate) + err = cfg.mods.AddModsNotInList(cfg.missingstate) + if err != nil { + return err + } + } + + if !cfg.ignoreuninstalled { + err = cfg.mods.RemoveModsNotInFolder() if err != nil { return err } diff --git a/internal/modlist/modlist.go b/internal/modlist/modlist.go index 22e55df..2d7a2ba 100644 --- a/internal/modlist/modlist.go +++ b/internal/modlist/modlist.go @@ -22,6 +22,7 @@ type Mod struct { // Modlist holds a slice of mods. type Modlist struct { + dir string Mods []Mod `json:"mods"` } @@ -119,8 +120,73 @@ func (modlist *Modlist) String() { modlist.Print("mods") } +// AddModsNotInList finds mod archives in the mod folder that aren't in the modlist, and adds them. +func (modlist *Modlist) AddModsNotInList(state bool) error { + r := regexp.MustCompile(fileRegex) + + files, err := os.ReadDir(modlist.dir) + if err != nil { + return err + } + + for _, file := range files { + if strings.HasSuffix(file.Name(), ".zip") && !file.IsDir() { + groups := r.FindAllStringSubmatch(file.Name(), -1) + name := groups[0][1] + version := groups[0][2] + + if !modlist.HasMod(name) { + log.Infof("Found %s not in modlist, adding v%s in disabled state.", name, version) + modlist.Mods = append(modlist.Mods, Mod{ + Name: name, + Enabled: state, + }) + } + } + } + + return nil +} + +// RemoveModsNotInFolder removes mods from the modlist if a corresponding +// zip file cannot be found in the mods dir. +func (modlist *Modlist) RemoveModsNotInFolder() error { + for i, mod := range modlist.Mods { + in, err := isModInFolder(mod.Name, modlist.dir) + if !in && err == nil { + log.Warnf("Zip file for %s could not be found, removing!", mod.Name) + // mods.Mods[i] = nil + modlist.Mods = append(modlist.Mods[:i], modlist.Mods[i+1:]...) + } else if err != nil { + return err + } + } + + return nil +} + /// Functions related to the modlist but that shouldn't be attached to the types +func isModInFolder(modname, modsdir string) (bool, error) { + if modname == "base" { + return true, nil + } + + // TODO: try for less O(n) + files, err := os.ReadDir(modsdir) + if err != nil { + return false, err + } + + for _, file := range files { + if strings.HasPrefix(file.Name(), modname) { + return true, nil + } + } + + return false, nil +} + // WriteToFile writes the modlist to the given filename. func WriteToFile(filename string, mods *Modlist) error { data, err := json.MarshalIndent(&mods, "", " ") @@ -134,8 +200,10 @@ func WriteToFile(filename string, mods *Modlist) error { } // ReadFromFile reads the modlist from the given filename. -func ReadFromFile(filename string) (Modlist, error) { - var mods Modlist = Modlist{} +func ReadFromFile(filename, modsdir string) (Modlist, error) { + var mods Modlist = Modlist{ + dir: modsdir, + } data, err := os.ReadFile(filename) if err != nil { @@ -150,31 +218,3 @@ func ReadFromFile(filename string) (Modlist, error) { log.Infof("Read modlist from file %s.", filename) return mods, nil } - -// AddModsNotInList finds mod archives in the mod folder that aren't in the modlist, and adds them. -func AddModsNotInList(modsdir string, mods *Modlist, state bool) error { - r := regexp.MustCompile(fileRegex) - - files, err := os.ReadDir(modsdir) - if err != nil { - return err - } - - for _, file := range files { - if strings.HasSuffix(file.Name(), ".zip") && !file.IsDir() { - groups := r.FindAllStringSubmatch(file.Name(), -1) - name := groups[0][1] - version := groups[0][2] - - if !mods.HasMod(name) { - log.Infof("Found %s not in modlist, adding v%s in disabled state.", name, version) - mods.Mods = append(mods.Mods, Mod{ - Name: name, - Enabled: state, - }) - } - } - } - - return nil -}