Skip to content

Commit

Permalink
feat: more info in unmanaged output (time elapsed, connection stages)
Browse files Browse the repository at this point in the history
  • Loading branch information
ZinoKader committed Feb 6, 2023
1 parent 0b02380 commit 6f47bb3
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 39 deletions.
3 changes: 2 additions & 1 deletion ui/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ const (

var QuitKeys = []string{"ctrl+c", "q", "esc"}
var PadText = strings.Repeat(" ", PADDING)
var QuitCommandsHelpText = HelpStyle(fmt.Sprintf("(any of [%s] to abort)", (strings.Join(QuitKeys, ", "))))
var QuitCommandsHelpText = HelpStyle(fmt.Sprintf("(any of [%s] to abort)", strings.Join(QuitKeys, ", ")))

var Progressbar = progress.NewModel(progress.WithGradient(SECONDARY_ELEMENT_COLOR, ELEMENT_COLOR))

var baseStyle = lipgloss.NewStyle()

var InfoStyle = baseStyle.Copy().Foreground(lipgloss.Color(PRIMARY_COLOR)).Render
var HelpStyle = baseStyle.Copy().Foreground(lipgloss.Color(SECONDARY_COLOR)).Render
var ItalicText = baseStyle.Copy().Italic(true).Render
Expand Down
22 changes: 11 additions & 11 deletions ui/receiver/receiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ type model struct {
decompressedPayloadSize int64
version *semver.Version

width int
spinner spinner.Model
progressBar progress.Model
errorMessage string
Expand Down Expand Up @@ -160,6 +161,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil

case tea.WindowSizeMsg:
m.width = msg.Width
m.progressBar.Width = msg.Width - 2*ui.PADDING - 4
if m.progressBar.Width > ui.MAX_WIDTH {
m.progressBar.Width = ui.MAX_WIDTH
Expand All @@ -168,7 +170,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

// FrameMsg is sent when the progress bar wants to animate itself
case progress.FrameMsg:

progressModel, cmd := m.progressBar.Update(msg)
m.progressBar = progressModel.(progress.Model)
return m, cmd
Expand All @@ -185,7 +186,7 @@ func (m model) View() string {
switch m.state {

case showEstablishing:
return "\n" +
return ui.PadText + ui.LogSeparator(m.width) +
ui.PadText + ui.InfoStyle(fmt.Sprintf("%s Establishing connection with sender", m.spinner.View())) + "\n\n" +
ui.PadText + ui.QuitCommandsHelpText + "\n\n"

Expand All @@ -198,16 +199,16 @@ func (m model) View() string {
}

payloadSize := ui.BoldText(ui.ByteCountSI(m.payloadSize))
receivingText := fmt.Sprintf("%s Receiving files (total size %s) using %s transfer", m.spinner.View(), payloadSize, transferType)
return "\n" +
receivingText := fmt.Sprintf("%s Receiving objects (total size %s) using %s transfer", m.spinner.View(), payloadSize, transferType)
return ui.PadText + ui.LogSeparator(m.width) +
ui.PadText + ui.InfoStyle(receivingText) + "\n\n" +
ui.PadText + m.progressBar.View() + "\n\n" +
ui.PadText + ui.QuitCommandsHelpText + "\n\n"

case showDecompressing:
payloadSize := ui.BoldText(ui.ByteCountSI(m.payloadSize))
decompressingText := fmt.Sprintf("%s Decompressing payload (%s compressed) and writing to disk", m.spinner.View(), payloadSize)
return "\n" +
return ui.PadText + ui.LogSeparator(m.width) +
ui.PadText + ui.InfoStyle(decompressingText) + "\n\n" +
ui.PadText + m.progressBar.View() + "\n\n" +
ui.PadText + ui.QuitCommandsHelpText + "\n\n"
Expand All @@ -217,18 +218,17 @@ func (m model) View() string {

var oneOrMoreFiles string
if len(m.receivedFiles) > 1 {
oneOrMoreFiles = "files"
oneOrMoreFiles = "objects"
} else {
oneOrMoreFiles = "file"
oneOrMoreFiles = "object"
}
finishedText := fmt.Sprintf("Received %d %s (%s compressed)\n\n%s", len(m.receivedFiles), oneOrMoreFiles, ui.ByteCountSI(m.payloadSize), indentedWrappedFiles)
return "\n" +
return ui.PadText + ui.LogSeparator(m.width) +
ui.PadText + ui.InfoStyle(finishedText) + "\n\n" +
ui.PadText + m.progressBar.View() + "\n\n" +
ui.PadText + ui.QuitCommandsHelpText + "\n\n"
ui.PadText + m.progressBar.View() + "\n\n"

case showError:
return m.errorMessage
return ui.ErrorText(m.errorMessage)

default:
return ""
Expand Down
73 changes: 47 additions & 26 deletions ui/sender/sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"fmt"
"io"
"os"
"sort"
"strings"
"time"

"github.com/SpatiumPortae/portal/internal/conn"
"github.com/SpatiumPortae/portal/internal/file"
Expand Down Expand Up @@ -79,13 +79,15 @@ type model struct {

rendezvousAddr string

password string
fileNames []string
uncompressedSize int64
payload *os.File
payloadSize int64
version *semver.Version
password string
fileNames []string
uncompressedSize int64
payload *os.File
payloadSize int64
transferStartTime time.Time
version *semver.Version

width int
spinner spinner.Model
progressBar progress.Model
}
Expand Down Expand Up @@ -128,9 +130,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case semver.CompareOldMajor:
return m, ui.ErrorCmd(fmt.Errorf("New major version available (%s -> %s)", m.version, msg.Latest))
case semver.CompareOldMinor:
message = ui.WarningText(fmt.Sprintf("New minor version avaialbe (%s -> %s)", m.version, msg.Latest))
message = ui.WarningText(fmt.Sprintf("New minor version available (%s -> %s)", m.version, msg.Latest))
case semver.CompareOldPatch:
message = ui.WarningText(fmt.Sprintf("New patch avaialbe (%s -> %s)", m.version, msg.Latest))
message = ui.WarningText(fmt.Sprintf("New patch available (%s -> %s)", m.version, msg.Latest))
case semver.CompareEqual:
message = ui.CheckText(fmt.Sprintf("You have the latest version (%s)", m.version))
default:
Expand All @@ -139,9 +141,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

case fileReadMsg:
m.uncompressedSize = msg.size
message := fmt.Sprintf("Read %d files (%s)", len(m.fileNames), ui.ByteCountSI(msg.size))
message := fmt.Sprintf("Read %d objects (%s)", len(m.fileNames), ui.ByteCountSI(msg.size))
if len(m.fileNames) == 1 {
message = fmt.Sprintf("Read %d file (%s)", len(m.fileNames), ui.ByteCountSI(msg.size))
message = fmt.Sprintf("Read %d object (%s)", len(m.fileNames), ui.ByteCountSI(msg.size))
}
return m, ui.TaskCmd(message, compressFilesCmd(msg.files))

Expand All @@ -150,19 +152,27 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.payloadSize = msg.size
m.readyToSend = true
m.resetSpinner()
message := fmt.Sprintf("Compressed files (%s)", ui.ByteCountSI(msg.size))
message := fmt.Sprintf("Compressed objects (%s)", ui.ByteCountSI(msg.size))
if len(m.fileNames) == 1 {
message = fmt.Sprintf("Compressed file (%s)", ui.ByteCountSI(msg.size))
message = fmt.Sprintf("Compressed object (%s)", ui.ByteCountSI(msg.size))
}
return m, ui.TaskCmd(message, spinner.Tick)

case connectMsg:
m.password = msg.password
return m, secureCmd(msg.conn, msg.password)
connectMessage := fmt.Sprintf("Connected to Portal server (%s)", m.rendezvousAddr)
return m, ui.TaskCmd(connectMessage, secureCmd(msg.conn, msg.password))

case ui.TransferTypeMsg:
m.transferType = msg.Type
return m, listenTransferCmd(m.msgs)
message := ""
switch m.transferType {
case transfer.Direct:
message = "Using direct connection to receiver"
case transfer.Relay:
message = "Using relayed connection to receiver"
}
return m, ui.TaskCmd(message, listenTransferCmd(m.msgs))

case ui.SecureMsg:
// In the case we are not ready to send yet we pass on the same message.
Expand All @@ -172,15 +182,23 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
cmd := tea.Batch(
transferCmd(msg.Conn, m.payload, m.payloadSize, m.msgs),
listenTransferCmd(m.msgs))

listenTransferCmd(m.msgs),
transferCmd(msg.Conn, m.payload, m.payloadSize, m.msgs))
return m, cmd

case ui.TransferStateMessage:
var message string
switch msg.State {
case transfer.ReceiverRequestPayload:
message = "Established encrypted connection with receiver"
}
return m, ui.TaskCmd(message, listenTransferCmd(m.msgs))

case ui.ProgressMsg:
cmds := []tea.Cmd{listenTransferCmd(m.msgs)}
if m.state != showSendingProgress {
m.state = showSendingProgress
m.transferStartTime = time.Now()
m.resetSpinner()
cmds = append(cmds, spinner.Tick)
}
Expand All @@ -193,7 +211,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

case transferDoneMsg:
m.state = showFinished
return m, ui.QuitCmd()
message := fmt.Sprintf("Transfer completed in %s", ui.HumanizeDuration(time.Since(m.transferStartTime)))
return m, ui.TaskCmd(message, ui.QuitCmd())

case ui.ErrorMsg:
m.state = showError
Expand All @@ -217,6 +236,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil

case tea.WindowSizeMsg:
m.width = msg.Width
m.progressBar.Width = msg.Width - 2*ui.PADDING - 4
if m.progressBar.Width > ui.MAX_WIDTH {
m.progressBar.Width = ui.MAX_WIDTH
Expand Down Expand Up @@ -249,7 +269,7 @@ func (m model) View() string {
readiness = fmt.Sprintf("%s Sending", m.spinner.View())
}

sort.Strings(m.fileNames)
slices.Sort(m.fileNames)
filesToSend := ui.ItalicText(strings.Join(m.fileNames, ", "))

builder := strings.Builder{}
Expand All @@ -264,7 +284,7 @@ func (m model) View() string {

switch m.transferType {
case transfer.Direct:
builder.WriteString(" directly to receiver")
builder.WriteString(" with a direct connection to receiver")
case transfer.Relay:
builder.WriteString(" to receiver using relay")
case transfer.Unknown:
Expand All @@ -285,25 +305,24 @@ func (m model) View() string {
if m.state == showFailedPasswordCopy {
copyText = "(failed to copy password to clipboard)"
}
return "\n" +
return ui.PadText + ui.LogSeparator(m.width) +
ui.PadText + ui.InfoStyle(fileInfoText) + "\n\n" +
ui.PadText + ui.InfoStyle("On the receiving end, run:") + "\n" +
ui.PadText + ui.InfoStyle(fmt.Sprintf("portal receive %s", m.password)) + "\n\n" +
ui.PadText + ui.HelpStyle(copyText) + "\n\n"

case showSendingProgress:
return "\n" +
return ui.PadText + ui.LogSeparator(m.width) +
ui.PadText + ui.InfoStyle(fileInfoText) + "\n\n" +
ui.PadText + m.progressBar.View() + "\n\n" +
ui.PadText + ui.QuitCommandsHelpText + "\n\n"

case showFinished:
indentedWrappedFiles := indent.String(fmt.Sprintf("Sent: %s", wordwrap.String(ui.ItalicText(ui.TopLevelFilesText(m.fileNames)), ui.MAX_WIDTH)), ui.PADDING)
finishedText := fmt.Sprintf("Sent %d objects (%s compressed)\n\n%s", len(m.fileNames), ui.ByteCountSI(m.payloadSize), indentedWrappedFiles)
return "\n" +
return ui.PadText + ui.LogSeparator(m.width) +
ui.PadText + ui.InfoStyle(finishedText) + "\n\n" +
ui.PadText + m.progressBar.View() + "\n\n" +
ui.PadText + ui.QuitCommandsHelpText + "\n\n"
ui.PadText + m.progressBar.View() + "\n\n"

case showError:
return ui.ErrorText(m.errorMessage)
Expand Down Expand Up @@ -384,6 +403,8 @@ func listenTransferCmd(msgs chan interface{}) tea.Cmd {
switch v := msg.(type) {
case transfer.Type:
return ui.TransferTypeMsg{Type: v}
case transfer.MsgType:
return ui.TransferStateMessage{State: v}
case int:
return ui.ProgressMsg(v)
default:
Expand Down
50 changes: 49 additions & 1 deletion ui/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ui

import (
"fmt"
"math"
"sort"
"strings"
"time"
Expand All @@ -11,6 +12,7 @@ import (
"github.com/SpatiumPortae/portal/protocol/transfer"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)

// ------------------------------------------------- Shared UI Messages ------------------------------------------------
Expand All @@ -26,6 +28,10 @@ type TransferTypeMsg struct {
Type transfer.Type
}

type TransferStateMessage struct {
State transfer.MsgType
}

type VersionMsg struct {
Latest semver.Version
}
Expand Down Expand Up @@ -54,6 +60,14 @@ var ReceivingSpinner = spinner.Spinner{

// --------------------------------------------------- Shared Helpers --------------------------------------------------

func LogSeparator(width int) string {
paddedWidth := math.Max(0, float64(width)-2*PADDING)
return fmt.Sprintf("%s\n\n",
baseStyle.Copy().
Foreground(lipgloss.Color(SECONDARY_COLOR)).
Render(strings.Repeat("─", int(math.Min(MAX_WIDTH, paddedWidth)))))
}

func TopLevelFilesText(fileNames []string) string {
// parse top level file names and attach number of subfiles in them
topLevelFileChildren := make(map[string]int)
Expand Down Expand Up @@ -95,11 +109,45 @@ func ByteCountSI(b int64) string {
float64(b)/float64(div), "kMGTPE"[exp])
}

func HumanizeDuration(duration time.Duration) string {
days := int64(duration.Hours() / 24)
hours := int64(math.Mod(duration.Hours(), 24))
minutes := int64(math.Mod(duration.Minutes(), 60))
seconds := int64(math.Mod(duration.Seconds(), 60))

chunks := []struct {
name string
amount int64
}{
{"d", days},
{"h", hours},
{"m", minutes},
{"s", seconds},
}

parts := []string{}

for _, chunk := range chunks {
switch chunk.amount {
case 0:
continue
default:
parts = append(parts, fmt.Sprintf("%d%s", chunk.amount, chunk.name))
}
}

if len(parts) == 0 {
parts = append(parts, "0s")
}

return strings.Join(parts, "")
}

// -------------------------------------------------- Shared Commands --------------------------------------------------

func TaskCmd(task string, cmd tea.Cmd) tea.Cmd {
msg := PadText + fmt.Sprintf("• %s", task)
return tea.Batch(tea.Println(msg), cmd)
return tea.Sequence(tea.Println(msg), cmd)
}

func QuitCmd() tea.Cmd {
Expand Down

0 comments on commit 6f47bb3

Please sign in to comment.