diff --git a/cmd/portal/main.go b/cmd/portal/main.go index d6969f9..5cafe18 100644 --- a/cmd/portal/main.go +++ b/cmd/portal/main.go @@ -10,7 +10,6 @@ import ( "path/filepath" "unicode/utf8" - "github.com/SpatiumPortae/portal/internal/semver" tea "github.com/charmbracelet/bubbletea" homedir "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" @@ -30,6 +29,14 @@ var rootCmd = &cobra.Command{ }, } +var versionCmd = &cobra.Command{ + Use: "version", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(version) + os.Exit(0) + }, +} + // Entry point of the application. func main() { if err := rootCmd.Execute(); err != nil { @@ -48,6 +55,7 @@ func init() { rootCmd.AddCommand(receiveCmd) rootCmd.AddCommand(serveCmd) rootCmd.AddCommand(addCompletionsCmd) + rootCmd.AddCommand(versionCmd) } // HELPER FUNCTIONS diff --git a/cmd/portal/send.go b/cmd/portal/send.go index c4431b1..e041d16 100644 --- a/cmd/portal/send.go +++ b/cmd/portal/send.go @@ -57,12 +57,11 @@ func init() { func handleSendCommand(fileNames []string) { addr := viper.GetString("rendezvousAddress") port := viper.GetInt("rendezvousPort") - var opts []senderui.Option ver, err := semver.Parse(version) - if err == nil { - opts = append(opts, senderui.WithVersion(ver)) + if err != nil { + panic(err) } - sender := senderui.New(fileNames, fmt.Sprintf("%s:%d", addr, port), opts...) + sender := senderui.New(fileNames, fmt.Sprintf("%s:%d", addr, port), ver) if err := sender.Start(); err != nil { fmt.Println("Error initializing UI", err) os.Exit(1) diff --git a/ui/sender/sender.go b/ui/sender/sender.go index a0d8242..baac4af 100644 --- a/ui/sender/sender.go +++ b/ui/sender/sender.go @@ -27,7 +27,8 @@ const ( copyPasswordKey = "c" ) -// -------------------- UI STATE -------------------------------- +// ------------------------------------------------------ Ui State ----------------------------------------------------- + type uiState int // flows from the top down. @@ -40,6 +41,8 @@ const ( showError ) +// ------------------------------------------------------ Messages ----------------------------------------------------- + type connectMsg struct { password string conn conn.Rendezvous @@ -56,16 +59,10 @@ type compressedMsg struct { } type transferDoneMsg struct{} -// -------------------- MODEL ------------------------------------- +// ------------------------------------------------------- Model ------------------------------------------------------- type Option func(m *model) -func WithVersion(version semver.Version) Option { - return func(m *model) { - m.version = &version - } -} - type model struct { state uiState // defaults to 0 (showPasswordWithCopy) transferType transfer.Type // defaults to 0 (Unknown) @@ -81,19 +78,20 @@ type model struct { uncompressedSize int64 payload *os.File payloadSize int64 - version *semver.Version + version semver.Version spinner spinner.Model progressBar progress.Model } // New creates a new receiver program. -func New(filenames []string, addr string, opts ...Option) *tea.Program { +func New(filenames []string, addr string, version semver.Version, opts ...Option) *tea.Program { m := model{ progressBar: ui.Progressbar, fileNames: filenames, rendezvousAddr: addr, msgs: make(chan interface{}, 10), + version: version, } for i := range opts { opts[i](&m) @@ -103,26 +101,51 @@ func New(filenames []string, addr string, opts ...Option) *tea.Program { } func (m model) Init() tea.Cmd { - cmds := []tea.Cmd{spinner.Tick, readFilesCmd(m.fileNames), connectCmd(m.rendezvousAddr)} - if m.version != nil { - cmds = append(cmds, ui.VersionCmd(*m.version)) - } - return tea.Batch(cmds...) + return ui.VersionCmd(m.version) } +// ------------------------------------------------------- Update ------------------------------------------------------ + func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { + case ui.VersionMsg: + var message string + switch m.version.Compare(msg.Latest) { + case semver.CompareNewMajor, + semver.CompareNewMinor, + semver.CompareNewPatch: + return m, ui.ErrorCmd(fmt.Errorf("Your version is (%s) is incompatible with the latest version (%s)", m.version, msg.Latest)) + 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)) + case semver.CompareOldPatch: + message = ui.WarningText(fmt.Sprintf("New patch avaialbe (%s -> %s)", m.version, msg.Latest)) + case semver.CompareEqual: + message = ui.CheckText(fmt.Sprintf("You have the latest version (%s)", m.version)) + default: + } + return m, ui.TaskCmd(message, tea.Batch(spinner.Tick, readFilesCmd(m.fileNames), connectCmd(m.rendezvousAddr))) + case fileReadMsg: m.uncompressedSize = msg.size - return m, compressFilesCmd(msg.files) + message := fmt.Sprintf("Read %d files (%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)) + } + return m, ui.TaskCmd(message, compressFilesCmd(msg.files)) case compressedMsg: m.payload = msg.payload m.payloadSize = msg.size m.readyToSend = true m.resetSpinner() - return m, spinner.Tick + message := fmt.Sprintf("Compressed files (%s)", ui.ByteCountSI(msg.size)) + if len(m.fileNames) == 1 { + message = fmt.Sprintf("Compressed file (%s)", ui.ByteCountSI(msg.size)) + } + return m, ui.TaskCmd(message, spinner.Tick) case connectMsg: m.password = msg.password @@ -139,12 +162,11 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return msg } } - cmds := []tea.Cmd{ + cmd := tea.Batch( transferCmd(msg.Conn, m.payload, m.payloadSize, m.msgs), - listenTransferCmd(m.msgs), - } + listenTransferCmd(m.msgs)) - return m, tea.Batch(cmds...) + return m, cmd case ui.ProgressMsg: cmds := []tea.Cmd{listenTransferCmd(m.msgs)} @@ -205,6 +227,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } +// -------------------------------------------------------- View ------------------------------------------------------- + func (m model) View() string { // Setup strings to use in view. uncompressed := ui.BoldText(ui.ByteCountSI(m.uncompressedSize)) @@ -243,7 +267,6 @@ func (m model) View() string { fileInfoText := builder.String() switch m.state { - case showPassword, showPasswordWithCopy, showFailedPasswordCopy: copyText := "(password copied to clipboard)" @@ -274,14 +297,14 @@ func (m model) View() string { ui.PadText + ui.QuitCommandsHelpText + "\n\n" case showError: - return m.errorMessage + return ui.ErrorText(m.errorMessage) default: return "" } } -// -------------------- UI COMMANDS --------------------------- +// ------------------------------------------------------ Commands ----------------------------------------------------- // connectCmd command that connects to the rendezvous server. func connectCmd(addr string) tea.Cmd {