refactor: further breaking up of model code
This commit is contained in:
parent
cf93ff24d8
commit
119a65b9fe
|
@ -0,0 +1,119 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
mrand "math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"cwtch.im/cwtch/app"
|
||||
"cwtch.im/cwtch/protocol/connections"
|
||||
"cwtch.im/cwtch/settings"
|
||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
const (
|
||||
offline = iota
|
||||
connecting
|
||||
connected
|
||||
)
|
||||
|
||||
func ensureConnected(m model) (bool, tea.Msg) {
|
||||
switch m.connState {
|
||||
case offline:
|
||||
return false, offlineMsg{}
|
||||
case connecting:
|
||||
return false, stillConnectingMsg{}
|
||||
default:
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func attemptShutdown(m *model) tea.Msg {
|
||||
if isConnected, msg := ensureConnected(*m); !isConnected {
|
||||
return msg
|
||||
}
|
||||
|
||||
m.app.Shutdown()
|
||||
m.acn.Close()
|
||||
close(m.statusBuffer)
|
||||
|
||||
return shutdownMsg{}
|
||||
}
|
||||
|
||||
func connInit(m *model) tea.Msg {
|
||||
mrand.Seed(int64(time.Now().Nanosecond()))
|
||||
port := mrand.Intn(1000) + 9600
|
||||
controlPort := port + 1
|
||||
|
||||
key := make([]byte, 64)
|
||||
_, err := rand.Read(key)
|
||||
if err != nil {
|
||||
return errMsg{Err: fmt.Errorf("unable to generate control port password: %s", err)}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path.Join(m.userDir, "/.tor", "tor"), 0700); err != nil {
|
||||
return errMsg{Err: fmt.Errorf("unable to create tor directory: %s", err)}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path.Join(m.userDir, "profiles"), 0700); err != nil {
|
||||
return errMsg{Err: fmt.Errorf("unable to create profiles directory: %s", err)}
|
||||
}
|
||||
|
||||
if err := tor.NewTorrc().
|
||||
WithSocksPort(port).
|
||||
WithOnionTrafficOnly().
|
||||
WithControlPort(controlPort).
|
||||
WithHashedPassword(base64.StdEncoding.EncodeToString(key)).
|
||||
Build(filepath.Join(m.userDir, ".tor", "tor", "torrc")); err != nil {
|
||||
return errMsg{Err: fmt.Errorf("unable to initialise torrc builder: %s", err)}
|
||||
}
|
||||
|
||||
m.statusBuffer <- "initialising Tor ACN..."
|
||||
|
||||
acn, err := tor.NewTorACNWithAuth(
|
||||
path.Join(m.userDir, "/.tor"),
|
||||
"",
|
||||
path.Join(m.userDir, "/.tor", "data"),
|
||||
controlPort,
|
||||
tor.HashedPasswordAuthenticator{
|
||||
Password: base64.StdEncoding.EncodeToString(key),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return errMsg{Err: fmt.Errorf("unable to bootstrap tor: %s", err)}
|
||||
}
|
||||
|
||||
m.statusBuffer <- "waiting for Tor ACN to bootstrap..."
|
||||
|
||||
if err := acn.WaitTillBootstrapped(); err != nil {
|
||||
return errMsg{Err: fmt.Errorf("unable to initialise tor: %s", err)}
|
||||
}
|
||||
|
||||
settingsFile, err := settings.InitGlobalSettingsFile(m.userDir, "")
|
||||
if err != nil {
|
||||
return errMsg{Err: fmt.Errorf("unable to initialise settings: %s", err)}
|
||||
}
|
||||
gSettings := settingsFile.ReadGlobalSettings()
|
||||
gSettings.ExperimentsEnabled = false
|
||||
gSettings.DownloadPath = "./"
|
||||
settingsFile.WriteGlobalSettings(gSettings)
|
||||
|
||||
m.statusBuffer <- "creating new Cwtch application"
|
||||
|
||||
app := app.NewApp(acn, m.userDir, settingsFile)
|
||||
|
||||
app.InstallEngineHooks(connections.DefaultEngineHooks{})
|
||||
|
||||
m.statusBuffer <- "the Tor ACN is up"
|
||||
|
||||
return appInitialisedMsg{
|
||||
app: app,
|
||||
acn: acn,
|
||||
}
|
||||
}
|
35
input.go
35
input.go
|
@ -56,6 +56,14 @@ func isClearCmd(cmds []string) bool {
|
|||
}
|
||||
|
||||
func handleCommand(cmd, hiddenInput string) tea.Msg {
|
||||
if len(cmd) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if string(cmd[0]) != "/" {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmds := strings.Split(cmd, " ")
|
||||
|
||||
switch {
|
||||
|
@ -102,3 +110,30 @@ func handleCommand(cmd, hiddenInput string) tea.Msg {
|
|||
return cmdMsg{output: "unknown command"}
|
||||
}
|
||||
}
|
||||
|
||||
func hidePasswordInput(m *model) {
|
||||
val := m.input.Value()
|
||||
|
||||
if len(val) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if string(val[0]) != "/" {
|
||||
return
|
||||
}
|
||||
|
||||
cmds := strings.Split(val[1:], " ")
|
||||
if !(cmds[0] == "profile" && len(cmds) >= 2) {
|
||||
return
|
||||
}
|
||||
|
||||
if cmds[1] == "create" && len(cmds) > 3 || cmds[1] == "unlock" && len(cmds) > 2 {
|
||||
lastChar := string(val[len(val)-1])
|
||||
m.hiddenInput += lastChar
|
||||
if lastChar != " " {
|
||||
newCmd := []rune(val)
|
||||
newCmd[len(val)-1] = '*'
|
||||
m.input.SetValue(string(newCmd))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,10 @@ type cmdMsg struct {
|
|||
output string
|
||||
}
|
||||
|
||||
type offlineMsg struct{}
|
||||
|
||||
type stillConnectingMsg struct{}
|
||||
|
||||
type shutdownMsg struct{}
|
||||
|
||||
type createProfileMsg struct {
|
215
model.go
215
model.go
|
@ -1,40 +1,18 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
mrand "math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cwtch.im/cwtch/app"
|
||||
"cwtch.im/cwtch/model/attr"
|
||||
"cwtch.im/cwtch/model/constants"
|
||||
"cwtch.im/cwtch/protocol/connections"
|
||||
"cwtch.im/cwtch/settings"
|
||||
"git.openprivacy.ca/openprivacy/connectivity"
|
||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
const (
|
||||
offline = iota
|
||||
connecting
|
||||
connected
|
||||
)
|
||||
|
||||
type profile struct {
|
||||
name string
|
||||
onion string
|
||||
}
|
||||
|
||||
type model struct {
|
||||
username string
|
||||
userDir string
|
||||
|
@ -73,78 +51,6 @@ func newModel(username, homeDir string) model {
|
|||
}
|
||||
}
|
||||
|
||||
func (m model) initApp() tea.Msg {
|
||||
mrand.Seed(int64(time.Now().Nanosecond()))
|
||||
port := mrand.Intn(1000) + 9600
|
||||
controlPort := port + 1
|
||||
|
||||
key := make([]byte, 64)
|
||||
_, err := rand.Read(key)
|
||||
if err != nil {
|
||||
return errMsg{Err: fmt.Errorf("unable to generate control port password: %s", err)}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path.Join(m.userDir, "/.tor", "tor"), 0700); err != nil {
|
||||
return errMsg{Err: fmt.Errorf("unable to create tor directory: %s", err)}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path.Join(m.userDir, "profiles"), 0700); err != nil {
|
||||
return errMsg{Err: fmt.Errorf("unable to create profiles directory: %s", err)}
|
||||
}
|
||||
|
||||
if err := tor.NewTorrc().
|
||||
WithSocksPort(port).
|
||||
WithOnionTrafficOnly().
|
||||
WithControlPort(controlPort).
|
||||
WithHashedPassword(base64.StdEncoding.EncodeToString(key)).
|
||||
Build(filepath.Join(m.userDir, ".tor", "tor", "torrc")); err != nil {
|
||||
return errMsg{Err: fmt.Errorf("unable to initialise torrc builder: %s", err)}
|
||||
}
|
||||
|
||||
m.statusBuffer <- "initialising Tor ACN..."
|
||||
|
||||
acn, err := tor.NewTorACNWithAuth(
|
||||
path.Join(m.userDir, "/.tor"),
|
||||
"",
|
||||
path.Join(m.userDir, "/.tor", "data"),
|
||||
controlPort,
|
||||
tor.HashedPasswordAuthenticator{
|
||||
Password: base64.StdEncoding.EncodeToString(key),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return errMsg{Err: fmt.Errorf("unable to bootstrap tor: %s", err)}
|
||||
}
|
||||
|
||||
m.statusBuffer <- "waiting for Tor ACN to bootstrap..."
|
||||
|
||||
if err := acn.WaitTillBootstrapped(); err != nil {
|
||||
return errMsg{Err: fmt.Errorf("unable to initialise tor: %s", err)}
|
||||
}
|
||||
|
||||
settingsFile, err := settings.InitGlobalSettingsFile(m.userDir, "")
|
||||
if err != nil {
|
||||
return errMsg{Err: fmt.Errorf("unable to initialise settings: %s", err)}
|
||||
}
|
||||
gSettings := settingsFile.ReadGlobalSettings()
|
||||
gSettings.ExperimentsEnabled = false
|
||||
gSettings.DownloadPath = "./"
|
||||
settingsFile.WriteGlobalSettings(gSettings)
|
||||
|
||||
m.statusBuffer <- "creating new Cwtch application"
|
||||
|
||||
app := app.NewApp(acn, m.userDir, settingsFile)
|
||||
|
||||
app.InstallEngineHooks(connections.DefaultEngineHooks{})
|
||||
|
||||
m.statusBuffer <- "the Tor ACN is up"
|
||||
|
||||
return appInitialisedMsg{
|
||||
app: app,
|
||||
acn: acn,
|
||||
}
|
||||
}
|
||||
|
||||
func (m model) handleStatusMessage() tea.Msg {
|
||||
return statusMsg{msg: <-m.statusBuffer}
|
||||
}
|
||||
|
@ -156,38 +62,6 @@ func (m model) Init() tea.Cmd {
|
|||
)
|
||||
}
|
||||
|
||||
func (m model) isConnecting() bool {
|
||||
if m.connState == connecting {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (m model) isOffline() bool {
|
||||
if m.connState == offline {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (m model) shutdown() bool {
|
||||
if m.isConnecting() {
|
||||
return false
|
||||
}
|
||||
|
||||
if m.isOffline() {
|
||||
return true
|
||||
}
|
||||
|
||||
m.app.Shutdown()
|
||||
m.acn.Close()
|
||||
close(m.statusBuffer)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var (
|
||||
cmd tea.Cmd
|
||||
|
@ -212,50 +86,35 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "ctrl+c":
|
||||
if m.shutdown() {
|
||||
return m, tea.Quit
|
||||
}
|
||||
cmds = append(cmds, func() tea.Msg {
|
||||
return attemptShutdown(&m)
|
||||
})
|
||||
|
||||
case "enter":
|
||||
val := m.input.Value()
|
||||
if len(val) == 0 {
|
||||
break
|
||||
}
|
||||
cmds = append(cmds, func() tea.Msg {
|
||||
return handleCommand(val[1:], m.hiddenInput)
|
||||
})
|
||||
|
||||
if string(val[0]) == "/" {
|
||||
cmds = append(cmds, func() tea.Msg {
|
||||
return handleCommand(val[1:], m.hiddenInput)
|
||||
})
|
||||
}
|
||||
default:
|
||||
val := m.input.Value()
|
||||
if len(val) != 0 {
|
||||
if string(val[0]) == "/" {
|
||||
cmds := strings.Split(val[1:], " ")
|
||||
if cmds[0] == "profile" && len(cmds) >= 2 {
|
||||
if cmds[1] == "create" && len(cmds) > 3 || cmds[1] == "unlock" && len(cmds) > 2 {
|
||||
lastChar := string(val[len(val)-1])
|
||||
m.hiddenInput += lastChar
|
||||
if lastChar != " " {
|
||||
newCmd := []rune(val)
|
||||
newCmd[len(val)-1] = '*'
|
||||
m.input.SetValue(string(newCmd))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
hidePasswordInput(&m)
|
||||
}
|
||||
|
||||
case cmdMsg:
|
||||
m.input.Reset()
|
||||
m.statusBuffer <- msg.output
|
||||
|
||||
case offlineMsg:
|
||||
m.input.Reset()
|
||||
m.statusBuffer <- "conn: currently offline"
|
||||
|
||||
case stillConnectingMsg:
|
||||
m.input.Reset()
|
||||
m.statusBuffer <- "conn: initialising, please hold"
|
||||
|
||||
case shutdownMsg:
|
||||
m.input.Reset()
|
||||
if m.shutdown() {
|
||||
return m, tea.Quit
|
||||
}
|
||||
return m, tea.Quit
|
||||
|
||||
case createProfileMsg:
|
||||
m.hiddenInput = ""
|
||||
|
@ -274,44 +133,30 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
m.hiddenInput = ""
|
||||
m.input.Reset()
|
||||
|
||||
switch m.connState {
|
||||
case offline:
|
||||
m.statusBuffer <- "offline, run /connect first?"
|
||||
return m, tea.Batch(cmds...)
|
||||
case connecting:
|
||||
m.statusBuffer <- "still connecting, hold on a sec..."
|
||||
return m, tea.Batch(cmds...)
|
||||
if isConnected, msg := ensureConnected(m); !isConnected {
|
||||
cmds = append(cmds, func() tea.Msg { return msg })
|
||||
break
|
||||
}
|
||||
|
||||
m.app.LoadProfiles(msg.password)
|
||||
|
||||
var unlocked []profile
|
||||
for _, onion := range m.app.ListProfiles() {
|
||||
peer := m.app.GetPeer(onion)
|
||||
name, _ := peer.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
|
||||
unlocked = append(unlocked, profile{name: name, onion: onion})
|
||||
profiles := unlockProfiles(m, msg.password)
|
||||
if len(profiles) > 0 {
|
||||
m.statusBuffer <- fmt.Sprintf("unlocked profile(s): %s", profiles.names())
|
||||
}
|
||||
|
||||
if len(unlocked) > 0 {
|
||||
var names []string
|
||||
for _, p := range unlocked {
|
||||
names = append(names, p.name)
|
||||
}
|
||||
m.statusBuffer <- fmt.Sprintf("unlocked profile(s): %s", strings.Join(names, " "))
|
||||
}
|
||||
|
||||
m.profiles = unlocked
|
||||
m.profiles = profiles
|
||||
|
||||
case profileInfoMsg:
|
||||
m.input.Reset()
|
||||
if m.connState != connected {
|
||||
m.statusBuffer <- "offline, run /connect first?"
|
||||
|
||||
if isConnected, msg := ensureConnected(m); !isConnected {
|
||||
cmds = append(cmds, func() tea.Msg { return msg })
|
||||
break
|
||||
}
|
||||
|
||||
if len(m.profiles) == 0 {
|
||||
m.statusBuffer <- "no profiles loaded"
|
||||
break
|
||||
}
|
||||
|
||||
profile := m.profiles[m.profileState]
|
||||
m.statusBuffer <- fmt.Sprintf("profile onion: %s", profile.onion)
|
||||
|
||||
|
@ -328,7 +173,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
|
||||
case connectMsg:
|
||||
m.input.Reset()
|
||||
cmds = append(cmds, m.initApp)
|
||||
cmds = append(cmds, func() tea.Msg {
|
||||
return connInit(&m)
|
||||
})
|
||||
|
||||
case appInitialisedMsg:
|
||||
m.app = msg.app
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"cwtch.im/cwtch/model/attr"
|
||||
"cwtch.im/cwtch/model/constants"
|
||||
)
|
||||
|
||||
type profile struct {
|
||||
name string
|
||||
onion string
|
||||
}
|
||||
|
||||
type profiles []profile
|
||||
|
||||
func (ps profiles) names() []string {
|
||||
var names []string
|
||||
for _, profile := range ps {
|
||||
names = append(names, profile.name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func unlockProfiles(m model, password string) profiles {
|
||||
var unlocked []profile
|
||||
|
||||
m.app.LoadProfiles(password)
|
||||
|
||||
for _, onion := range m.app.ListProfiles() {
|
||||
peer := m.app.GetPeer(onion)
|
||||
name, _ := peer.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name)
|
||||
unlocked = append(unlocked, profile{name: name, onion: onion})
|
||||
}
|
||||
|
||||
return unlocked
|
||||
}
|
Reference in New Issue