summaryrefslogtreecommitdiff
path: root/internal/fsm.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/fsm.go')
-rw-r--r--internal/fsm.go187
1 files changed, 187 insertions, 0 deletions
diff --git a/internal/fsm.go b/internal/fsm.go
new file mode 100644
index 0000000..fadc7c9
--- /dev/null
+++ b/internal/fsm.go
@@ -0,0 +1,187 @@
+package internal
+
+import (
+ "fmt"
+ "os"
+ "sort"
+)
+
+type (
+ FSMStateID int8
+ FSMStateIDSlice []FSMStateID
+)
+
+func (v FSMStateIDSlice) Len() int {
+ return len(v)
+}
+
+func (v FSMStateIDSlice) Less(i, j int) bool {
+ return v[i] < v[j]
+}
+
+func (v FSMStateIDSlice) Swap(i, j int) {
+ v[i], v[j] = v[j], v[i]
+}
+
+const (
+ // Deregistered means the app is not registered with the wrapper
+ DEREGISTERED FSMStateID = iota
+
+ // No Server means the user has not chosen a server yet
+ NO_SERVER
+
+ // Chosen Server means the user has chosen a server to connect to
+ CHOSEN_SERVER
+
+ // OAuth Started means the OAuth process has started
+ OAUTH_STARTED
+
+ // Authenticated means the OAuth process has finished and the user is now authenticated with the server
+ AUTHENTICATED
+
+ // Requested config means the user has requested a config for connecting
+ REQUEST_CONFIG
+
+ // Has config means the user has gotten a config
+ HAS_CONFIG
+
+ // Ask profile means the go code is asking for a profile selection from the ui
+ ASK_PROFILE
+
+ // Connected means the user has been connected to the server
+ CONNECTED
+)
+
+func (s FSMStateID) String() string {
+ switch s {
+ case DEREGISTERED:
+ return "Deregistered"
+ case NO_SERVER:
+ return "No_Server"
+ case CHOSEN_SERVER:
+ return "Chosen_Server"
+ case OAUTH_STARTED:
+ return "OAuth_Started"
+ case HAS_CONFIG:
+ return "Has_Config"
+ case REQUEST_CONFIG:
+ return "Request_Config"
+ case ASK_PROFILE:
+ return "Ask_Profile"
+ case AUTHENTICATED:
+ return "Authenticated"
+ case CONNECTED:
+ return "Connected"
+ default:
+ panic("unknown conversion of state to string")
+ }
+}
+
+type FSMTransition struct {
+ To FSMStateID
+ Description string
+}
+
+type (
+ FSMTransitions []FSMTransition
+ FSMStates map[FSMStateID]FSMTransitions
+)
+
+type FSM struct {
+ States FSMStates
+ Current FSMStateID
+
+ // Info to be passed from the parent state
+ StateCallback func(string, string, string)
+ Logger *FileLogger
+ Debug bool
+}
+
+func (fsm *FSM) Init(callback func(string, string, string), logger *FileLogger, debug bool) {
+ fsm.States = FSMStates{
+ DEREGISTERED: {{NO_SERVER, "Client registers"}},
+ NO_SERVER: {{CHOSEN_SERVER, "User chooses a server"}},
+ CHOSEN_SERVER: {{AUTHENTICATED, "Found tokens in config"}, {OAUTH_STARTED, "No tokens found in config"}},
+ OAUTH_STARTED: {{AUTHENTICATED, "User authorizes with browser"}},
+ AUTHENTICATED: {{OAUTH_STARTED, "Re-authenticate with OAuth"}, {REQUEST_CONFIG, "Client requests a config"}},
+ REQUEST_CONFIG: {{ASK_PROFILE, "Multiple profiles found"}, {HAS_CONFIG, "Success, only one profile"}},
+ ASK_PROFILE: {{HAS_CONFIG, "User chooses profile and success"}},
+ HAS_CONFIG: {{CONNECTED, "OS reports connected"}},
+ CONNECTED: {{AUTHENTICATED, "OS reports disconnected"}},
+ }
+ fsm.Current = DEREGISTERED
+ fsm.StateCallback = callback
+ fsm.Logger = logger
+ fsm.Debug = debug
+}
+
+func (fsm *FSM) InState(check FSMStateID) bool {
+ return check == fsm.Current
+}
+
+func (fsm *FSM) HasTransition(check FSMStateID) bool {
+ for _, transition_state := range fsm.States[fsm.Current] {
+ if transition_state.To == check {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (fsm *FSM) writeGraph() {
+ graph := fsm.GenerateGraph()
+
+ f, err := os.Create("debug.graph")
+ if err != nil {
+ fsm.Logger.Log(LOG_INFO, fmt.Sprintf("Failed to write debug fsm graph with error %v", err))
+ }
+
+ defer f.Close()
+
+ f.WriteString(graph)
+}
+
+func (fsm *FSM) GoTransitionWithData(newState FSMStateID, data string) bool {
+ ok := fsm.HasTransition(newState)
+
+ if ok {
+ oldState := fsm.Current
+ fsm.Current = newState
+ if fsm.Debug {
+ fsm.writeGraph()
+ }
+ fsm.StateCallback(oldState.String(), newState.String(), data)
+ }
+
+ return ok
+}
+
+func (fsm *FSM) GoTransition(newState FSMStateID) bool {
+ return fsm.GoTransitionWithData(newState, "")
+}
+
+func (fsm *FSM) generateMermaidGraph() string {
+ graph := "graph TD\n"
+ sorted_fsm := make(FSMStateIDSlice, 0, len(fsm.States))
+ for state_id := range fsm.States {
+ sorted_fsm = append(sorted_fsm, state_id)
+ }
+ sort.Sort(sorted_fsm)
+ for _, state := range sorted_fsm {
+ transitions := fsm.States[state]
+ for _, transition := range transitions {
+ if state == fsm.Current {
+ graph += "\nstyle " + state.String() + " fill:cyan\n"
+ } else {
+ graph += "\nstyle " + state.String() + " fill:white\n"
+ }
+ graph += state.String() + "(" + state.String() + ") " + "-->|" + transition.Description + "| " + transition.To.String() + "\n"
+ }
+ }
+ return graph
+}
+
+func (fsm *FSM) GenerateGraph() string {
+ return fsm.generateMermaidGraph()
+}