ficsit-cli-flake/cli/profiles.go
2021-12-04 20:02:05 +02:00

219 lines
5 KiB
Go

package cli
import (
"encoding/json"
"fmt"
"os"
"path"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/satisfactorymodding/ficsit-cli/utils"
"github.com/spf13/viper"
)
const DefaultProfileName = "Default"
var defaultProfile = Profile{
Name: DefaultProfileName,
}
type ProfilesVersion int
const (
InitialProfilesVersion = ProfilesVersion(iota)
// Always last
nextProfilesVersion
)
type Profiles struct {
Version ProfilesVersion `json:"version"`
Profiles map[string]*Profile `json:"profiles"`
SelectedProfile string `json:"selected_profile"`
}
type Profile struct {
Name string `json:"name"`
Mods map[string]ProfileMod `json:"mods"`
}
type ProfileMod struct {
Version string `json:"version"`
InstalledVersion string `json:"installed_version"`
}
func InitProfiles() (*Profiles, error) {
cacheDir := viper.GetString("cache-dir")
profilesFile := path.Join(cacheDir, viper.GetString("profiles-file"))
_, err := os.Stat(profilesFile)
if err != nil {
if !os.IsNotExist(err) {
return nil, errors.Wrap(err, "failed to stat profiles file")
}
_, err := os.Stat(cacheDir)
if err != nil {
if !os.IsNotExist(err) {
return nil, errors.Wrap(err, "failed to read cache directory")
}
err = os.MkdirAll(cacheDir, 0755)
if err != nil {
return nil, errors.Wrap(err, "failed to create cache directory")
}
}
emptyProfiles := Profiles{
Version: nextProfilesVersion - 1,
Profiles: map[string]*Profile{
DefaultProfileName: &defaultProfile,
},
}
if err := emptyProfiles.Save(); err != nil {
return nil, errors.Wrap(err, "failed to save empty profiles")
}
}
profilesData, err := os.ReadFile(profilesFile)
if err != nil {
return nil, errors.Wrap(err, "failed to read profiles")
}
var profiles Profiles
if err := json.Unmarshal(profilesData, &profiles); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal profiles")
}
if profiles.Version >= nextProfilesVersion {
return nil, fmt.Errorf("unknown profiles version: %d", profiles.Version)
}
if len(profiles.Profiles) == 0 {
profiles.Profiles = map[string]*Profile{
DefaultProfileName: &defaultProfile,
}
profiles.SelectedProfile = DefaultProfileName
}
if profiles.SelectedProfile == "" || profiles.Profiles[profiles.SelectedProfile] == nil {
profiles.SelectedProfile = DefaultProfileName
}
return &profiles, nil
}
// Save the profiles to the profiles file.
func (p *Profiles) Save() error {
if viper.GetBool("dry-run") {
log.Info().Msg("dry-run: skipping profile saving")
return nil
}
profilesFile := path.Join(viper.GetString("cache-dir"), viper.GetString("profiles-file"))
log.Info().Str("path", profilesFile).Msg("saving profiles")
profilesJSON, err := json.MarshalIndent(p, "", " ")
if err != nil {
return errors.Wrap(err, "failed to marshal profiles")
}
if err := os.WriteFile(profilesFile, profilesJSON, 0755); err != nil {
return errors.Wrap(err, "failed to write profiles")
}
return nil
}
// AddProfile adds a new profile with the given name to the profiles list.
func (p *Profiles) AddProfile(name string) (*Profile, error) {
if _, ok := p.Profiles[name]; ok {
return nil, fmt.Errorf("profile with name %s already exists", name)
}
p.Profiles[name] = &Profile{
Name: name,
}
return p.Profiles[name], nil
}
// DeleteProfile deletes the profile with the given name.
func (p *Profiles) DeleteProfile(name string) error {
if _, ok := p.Profiles[name]; ok {
delete(p.Profiles, name)
if p.SelectedProfile == name {
p.SelectedProfile = DefaultProfileName
}
return nil
}
return fmt.Errorf("profile with name %s does not exist", name)
}
// GetProfile returns the profile with the given name or nil if it doesn't exist.
func (p *Profiles) GetProfile(name string) *Profile {
return p.Profiles[name]
}
func (p *Profiles) RenameProfile(oldName string, newName string) error {
if _, ok := p.Profiles[newName]; ok {
return fmt.Errorf("profile with name %s already exists", newName)
}
if _, ok := p.Profiles[oldName]; !ok {
return fmt.Errorf("profile with name %s does not exist", oldName)
}
p.Profiles[oldName].Name = newName
p.Profiles[newName] = p.Profiles[oldName]
delete(p.Profiles, oldName)
if p.SelectedProfile == oldName {
p.SelectedProfile = newName
}
return nil
}
// AddMod adds a mod to the profile with given version.
func (p *Profile) AddMod(reference string, version string) error {
if p.Mods == nil {
p.Mods = make(map[string]ProfileMod)
}
if !utils.SemVerRegex.MatchString(version) {
return errors.New("invalid semver version")
}
p.Mods[reference] = ProfileMod{
Version: version,
}
return nil
}
// RemoveMod removes a mod from the profile.
func (p *Profile) RemoveMod(reference string) {
if p.Mods == nil {
return
}
delete(p.Mods, reference)
}
// HasMod returns true if the profile has a mod with the given reference
func (p *Profile) HasMod(reference string) bool {
if p.Mods == nil {
return false
}
_, ok := p.Mods[reference]
return ok
}