165 lines
3.8 KiB
Go
165 lines
3.8 KiB
Go
package app
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"proxyctl/internal/profile"
|
|
)
|
|
|
|
type IntentKind int
|
|
|
|
const (
|
|
CreateProfile IntentKind = iota + 1
|
|
UpdateProfile
|
|
DeleteProfile
|
|
ActivateProfile
|
|
)
|
|
|
|
type Intent struct {
|
|
Kind IntentKind
|
|
Name string
|
|
Profile profile.Profile
|
|
Confirm bool
|
|
}
|
|
|
|
type Snapshot struct {
|
|
Profiles []profile.Profile
|
|
ActiveProfile string
|
|
}
|
|
|
|
type Result struct {
|
|
Snapshot Snapshot
|
|
Notice string
|
|
Err error
|
|
}
|
|
|
|
type Module interface {
|
|
Snapshot() Snapshot
|
|
Dispatch(Intent) Result
|
|
}
|
|
|
|
type Application struct {
|
|
store profile.Store
|
|
state profile.State
|
|
}
|
|
|
|
func New(store profile.Store) (*Application, error) {
|
|
state, err := store.Load()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Application{store: store, state: state}, nil
|
|
}
|
|
|
|
func (a *Application) Snapshot() Snapshot {
|
|
profiles := append([]profile.Profile(nil), a.state.Profiles...)
|
|
return Snapshot{Profiles: profiles, ActiveProfile: a.state.ActiveProfile}
|
|
}
|
|
|
|
func (a *Application) Dispatch(intent Intent) Result {
|
|
var notice string
|
|
var err error
|
|
switch intent.Kind {
|
|
case CreateProfile:
|
|
notice, err = a.create(intent.Profile)
|
|
case ActivateProfile:
|
|
notice, err = a.activate(intent.Name)
|
|
case UpdateProfile:
|
|
notice, err = a.update(intent.Name, intent.Profile)
|
|
case DeleteProfile:
|
|
notice, err = a.delete(intent.Name, intent.Confirm)
|
|
default:
|
|
err = errors.New("unsupported Profile action")
|
|
}
|
|
return Result{Snapshot: a.Snapshot(), Notice: notice, Err: err}
|
|
}
|
|
|
|
func (a *Application) create(input profile.Profile) (string, error) {
|
|
p, err := profile.ValidateAndNormalize(input)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if a.index(p.Name) >= 0 {
|
|
return "", fmt.Errorf("a Profile named %q already exists", p.Name)
|
|
}
|
|
next := cloneState(a.state)
|
|
next.Profiles = append(next.Profiles, p)
|
|
if err := a.store.Save(next); err != nil {
|
|
return "", err
|
|
}
|
|
a.state = next
|
|
return fmt.Sprintf("Created Profile %q", p.Name), nil
|
|
}
|
|
|
|
func (a *Application) activate(name string) (string, error) {
|
|
if a.index(name) < 0 {
|
|
return "", fmt.Errorf("Profile %q does not exist", name)
|
|
}
|
|
next := cloneState(a.state)
|
|
next.ActiveProfile = name
|
|
if err := a.store.Save(next); err != nil {
|
|
return "", err
|
|
}
|
|
a.state = next
|
|
return fmt.Sprintf("Activated Profile %q", name), nil
|
|
}
|
|
|
|
func (a *Application) update(originalName string, input profile.Profile) (string, error) {
|
|
index := a.index(originalName)
|
|
if index < 0 {
|
|
return "", fmt.Errorf("Profile %q does not exist", originalName)
|
|
}
|
|
p, err := profile.ValidateAndNormalize(input)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if other := a.index(p.Name); other >= 0 && other != index {
|
|
return "", fmt.Errorf("a Profile named %q already exists", p.Name)
|
|
}
|
|
next := cloneState(a.state)
|
|
next.Profiles[index] = p
|
|
if next.ActiveProfile == originalName {
|
|
next.ActiveProfile = p.Name
|
|
}
|
|
if err := a.store.Save(next); err != nil {
|
|
return "", err
|
|
}
|
|
a.state = next
|
|
return fmt.Sprintf("Updated Profile %q", p.Name), nil
|
|
}
|
|
|
|
func (a *Application) delete(name string, confirm bool) (string, error) {
|
|
index := a.index(name)
|
|
if index < 0 {
|
|
return "", fmt.Errorf("Profile %q does not exist", name)
|
|
}
|
|
if len(a.state.Profiles) == 1 && !confirm {
|
|
return "", errors.New("deleting the final Profile requires confirmation")
|
|
}
|
|
next := cloneState(a.state)
|
|
next.Profiles = append(next.Profiles[:index], next.Profiles[index+1:]...)
|
|
if next.ActiveProfile == name {
|
|
next.ActiveProfile = ""
|
|
}
|
|
if err := a.store.Save(next); err != nil {
|
|
return "", err
|
|
}
|
|
a.state = next
|
|
return fmt.Sprintf("Deleted Profile %q", name), nil
|
|
}
|
|
|
|
func (a *Application) index(name string) int {
|
|
for i := range a.state.Profiles {
|
|
if a.state.Profiles[i].Name == name {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func cloneState(state profile.State) profile.State {
|
|
state.Profiles = append([]profile.Profile(nil), state.Profiles...)
|
|
return state
|
|
}
|