219 lines
5 KiB
Go
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
|
|
}
|