wip: towards channel based status buffer

This commit is contained in:
decentral1se 2023-07-22 22:14:30 +02:00
parent c3373e707b
commit af2d3a19fe
Signed by: decentral1se
GPG Key ID: 03789458B3D0C410
1 changed files with 106 additions and 95 deletions

201
cairde.go
View File

@ -134,7 +134,7 @@ type model struct {
input textinput.Model
hiddenInput string
statusMsgs *[]string
statusBuffer chan string
}
func newModel(username, homeDir string) model {
@ -144,11 +144,11 @@ func newModel(username, homeDir string) model {
input.Focus()
return model{
username: username,
userDir: path.Join(homeDir, "/.cairde/"),
connState: offline,
input: input,
statusMsgs: new([]string),
username: username,
userDir: path.Join(homeDir, "/.cairde/"),
connState: offline,
input: input,
statusBuffer: make(chan string),
}
}
@ -163,82 +163,89 @@ type appInitialisedMsg struct {
acn connectivity.ACN
}
func (m model) statusInfo(msg string) {
*m.statusMsgs = append(*m.statusMsgs, msg)
func (m model) initApp() tea.Cmd {
return func() 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)
app := app.NewApp(acn, m.userDir, settingsFile)
engineHooks := connections.DefaultEngineHooks{}
app.InstallEngineHooks(engineHooks)
m.statusBuffer <- "the Tor ACN is up"
return appInitialisedMsg{
app: app,
acn: acn,
}
}
}
func (m model) initApp() tea.Msg {
mrand.Seed(int64(time.Now().Nanosecond()))
port := mrand.Intn(1000) + 9600
controlPort := port + 1
type statusMsg struct{ msg string }
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.statusInfo("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.statusInfo("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)
app := app.NewApp(acn, m.userDir, settingsFile)
engineHooks := connections.DefaultEngineHooks{}
app.InstallEngineHooks(engineHooks)
m.statusInfo("the Tor ACN is up")
return appInitialisedMsg{
app: app,
acn: acn,
func (m model) handleStatusMessage() tea.Cmd {
return func() tea.Msg {
return statusMsg{msg: <-m.statusBuffer}
}
}
func (m model) Init() tea.Cmd {
return tea.Batch(
textinput.Blink,
m.handleStatusMessage(),
)
}
@ -269,6 +276,7 @@ func (m model) shutdown() bool {
m.app.Shutdown()
m.acn.Close()
close(m.statusBuffer)
return true
}
@ -397,12 +405,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
cmds []tea.Cmd
)
m.input, cmd = m.input.Update(msg)
cmds = append(cmds, cmd)
m.viewport, cmd = m.viewport.Update(msg)
cmds = append(cmds, cmd)
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width = msg.Width - 2
@ -452,7 +454,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case cmdMsg:
m.input.Reset()
m.statusInfo(msg.output)
m.statusBuffer <- msg.output
case shutdownMsg:
m.input.Reset()
@ -465,7 +467,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.input.Reset()
m.app.CreateProfile(msg.name, msg.password, false)
m.statusInfo(fmt.Sprintf("created new profile: %s", msg.name))
m.statusBuffer <- fmt.Sprintf("created new profile: %s", msg.name)
profiles := m.app.ListProfiles() // TODO
m.profiles = append(m.profiles, profile{
@ -479,10 +481,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch m.connState {
case offline:
m.statusInfo("offline, run /connect first?")
m.statusBuffer <- "offline, run /connect first?"
break
case connecting:
m.statusInfo("still connecting, hold on a sec...")
m.statusBuffer <- "still connecting, hold on a sec..."
break
}
@ -500,7 +502,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
for _, p := range unlocked {
names = append(names, p.name)
}
m.statusInfo(fmt.Sprintf("unlocked profile(s): %s", strings.Join(names, " ")))
m.statusBuffer <- fmt.Sprintf("unlocked profile(s): %s", strings.Join(names, " "))
}
m.profiles = unlocked
@ -508,45 +510,54 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case profileInfoMsg:
m.input.Reset()
if m.connState != connected {
m.statusInfo("offline, run /connect first?")
m.statusBuffer <- "offline, run /connect first?"
break
}
if len(m.profiles) == 0 {
m.statusInfo("no profiles loaded")
m.statusBuffer <- "no profiles loaded"
break
}
profile := m.profiles[m.profileState]
m.statusInfo(fmt.Sprintf("profile onion: %s", profile.onion))
m.statusBuffer <- fmt.Sprintf("profile onion: %s", profile.onion)
case statusMsg:
existing := m.viewport.View()
m.viewport.SetContent(existing + "\n" + msg.msg)
m.viewport.GotoBottom()
cmds = append(cmds, m.handleStatusMessage())
case clearMsg:
m.input.Reset()
m.statusMsgs = new([]string)
m.viewport.SetContent("")
m.viewport.GotoBottom()
case connectMsg:
m.input.Reset()
cmds = append(cmds, m.initApp)
cmds = append(cmds, m.initApp())
case appInitialisedMsg:
m.app = msg.app
m.acn = msg.acn
m.connState = connected
m.statusInfo("create or unlock a profile to get started")
m.statusInfo("run /help to see the command listing")
m.statusBuffer <- "create or unlock a profile to get started"
m.statusBuffer <- "run /help to see the command listing"
case errMsg:
m.statusInfo(msg.Error())
m.statusBuffer <- msg.Error()
}
m.input, cmd = m.input.Update(msg)
cmds = append(cmds, cmd)
m.viewport, cmd = m.viewport.Update(msg)
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}
func (m model) View() string {
body := strings.Builder{}
// TODO: adjust when more panes are available
m.viewport.SetContent(strings.Join(*m.statusMsgs, "\n"))
channelBar := channelBarStyle.
Width(m.width).
AlignVertical(lipgloss.Center).