refactor: further breaking up of model code

This commit is contained in:
decentral1se 2023-07-23 13:12:55 +02:00
parent cf93ff24d8
commit 119a65b9fe
Signed by: decentral1se
GPG Key ID: 03789458B3D0C410
5 changed files with 224 additions and 184 deletions

119
conn.go Normal file
View File

@ -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,
}
}

View File

@ -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))
}
}
}

View File

@ -22,6 +22,10 @@ type cmdMsg struct {
output string
}
type offlineMsg struct{}
type stillConnectingMsg struct{}
type shutdownMsg struct{}
type createProfileMsg struct {

215
model.go
View File

@ -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

35
profile.go Normal file
View File

@ -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
}