Refactor UI state management and improve error handling in the application. Added success and error messages to enhance user feedback during package creation and device connection status updates.

This commit is contained in:
2026-01-29 10:45:12 -06:00
parent a44f433bba
commit f5a688e741
7 changed files with 120 additions and 77 deletions
+1 -18
View File
@@ -2,12 +2,10 @@ package main
import ( import (
"encoding/binary" "encoding/binary"
"fmt"
"io/fs" "io/fs"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"log"
"fmt"
) )
type fileData struct { type fileData struct {
@@ -112,22 +110,11 @@ func (c *cpkg) Create() (bool, error) {
return false, fmt.Errorf("incomplete content write for file %d (%s): wrote %d of %d bytes", i, fileData.name, n, len(fileData.content)) return false, fmt.Errorf("incomplete content write for file %d (%s): wrote %d of %d bytes", i, fileData.name, n, len(fileData.content))
} }
log.Printf("Wrote file %d: %s (%d bytes)", i, fileData.name, len(fileData.content))
} }
return true, nil return true, nil
} }
func (c *cpkg) DebugPrint() {
log.Println("Package Name:", c.packageName)
idx := 0
for _, file := range c.files {
log.Println("File " + strconv.Itoa(idx) + " header")
log.Println(c.createCpkgFileHeader(file))
idx++
}
}
func (c *cpkg) Unpack(fsys fs.FS) error { func (c *cpkg) Unpack(fsys fs.FS) error {
file, err := os.Open(c.packageName + ".cpkg") file, err := os.Open(c.packageName + ".cpkg")
if err != nil { if err != nil {
@@ -156,7 +143,6 @@ func (c *cpkg) Unpack(fsys fs.FS) error {
// Read file count // Read file count
fileCount := binary.LittleEndian.Uint32(header[6:10]) fileCount := binary.LittleEndian.Uint32(header[6:10])
log.Printf("Unpacking %d files from %s.cpkg", fileCount, c.packageName)
// Clear existing files array // Clear existing files array
c.files = make([]fileData, 0, fileCount) c.files = make([]fileData, 0, fileCount)
@@ -206,7 +192,6 @@ func (c *cpkg) Unpack(fsys fs.FS) error {
return fmt.Errorf("incomplete read of content for file %d (%s): got %d bytes, expected %d", i, fileName, n, fileSize) return fmt.Errorf("incomplete read of content for file %d (%s): got %d bytes, expected %d", i, fileName, n, fileSize)
} }
log.Printf("Read file %d: %s (%d bytes)", i, fileName, fileSize)
c.files = append(c.files, fileData{name: fileName, content: fileContent}) c.files = append(c.files, fileData{name: fileName, content: fileContent})
} }
@@ -230,9 +215,7 @@ func (c *cpkg) Unpack(fsys fs.FS) error {
if err != nil { if err != nil {
return fmt.Errorf("failed to write file %s: %w", fileData.name, err) return fmt.Errorf("failed to write file %s: %w", fileData.name, err)
} }
log.Printf("Wrote file: %s", outputPath)
} }
log.Printf("Successfully unpacked %d files to %s/", len(c.files), c.packageName)
return nil return nil
} }
+10 -5
View File
@@ -68,13 +68,18 @@ func (d *Device) Connect() error {
// ExitDevMode sends the command to exit dev mode. // ExitDevMode sends the command to exit dev mode.
func (d *Device) ExitDevMode() error { func (d *Device) ExitDevMode() error {
if !d.Connected || d.Port == nil { if d.Port == nil {
return fmt.Errorf("device not connected") return fmt.Errorf("port not open")
} }
// Send exit dev mode command // disable dev mode
// TODO: Replace with the actual command sequence to exit dev mode _, err := d.Port.Write([]byte{0xAA, 0x05, 0x00, 0x00, 0x00})
// This is currently not implemented on the Cardputer side. if err != nil {
return fmt.Errorf("failed to send exit dev mode command: %w", err)
}
// Allow some time for the device to respond or settle if needed
time.Sleep(100 * time.Millisecond)
return nil return nil
} }
+4 -3
View File
@@ -15,7 +15,7 @@ require (
github.com/creack/goselect v0.1.2 // indirect github.com/creack/goselect v0.1.2 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/gdamore/encoding v1.0.1 // indirect github.com/gdamore/encoding v1.0.1 // indirect
github.com/gdamore/tcell/v2 v2.8.1 // indirect github.com/gdamore/tcell/v2 v2.9.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
@@ -23,11 +23,12 @@ require (
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect github.com/muesli/termenv v0.16.0 // indirect
github.com/navidys/tvxwidgets v0.12.1 // indirect
github.com/rivo/tview v0.42.1-0.20250929082832-e113793670e2 // indirect github.com/rivo/tview v0.42.1-0.20250929082832-e113793670e2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.bug.st/serial v1.6.4 // indirect go.bug.st/serial v1.6.4 // indirect
golang.org/x/sys v0.36.0 // indirect golang.org/x/sys v0.36.0 // indirect
golang.org/x/term v0.28.0 // indirect golang.org/x/term v0.34.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.28.0 // indirect
) )
+8
View File
@@ -24,6 +24,8 @@ github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uh
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU= github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw= github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
github.com/gdamore/tcell/v2 v2.9.0 h1:N6t+eqK7/xwtRPwxzs1PXeRWnm0H9l02CrgJ7DLn1ys=
github.com/gdamore/tcell/v2 v2.9.0/go.mod h1:8/ZoqM9rxzYphT9tH/9LnunhV9oPBqwS8WHGYm5nrmo=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
@@ -39,6 +41,8 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/navidys/tvxwidgets v0.12.1 h1:/5yJf/0MPlg50VKnaAfnRF1sBMPos/Aeb9tY0/UXJ3M=
github.com/navidys/tvxwidgets v0.12.1/go.mod h1:3EQbBvdokrZsEjnXKfOdcYAQk4dZIQSfmTJPxQbBE9A=
github.com/rivo/tview v0.42.1-0.20250929082832-e113793670e2 h1:0SWZkAwSpcwyWOTFxFOVjnB+nrUkHAPNnERVYfVzRow= github.com/rivo/tview v0.42.1-0.20250929082832-e113793670e2 h1:0SWZkAwSpcwyWOTFxFOVjnB+nrUkHAPNnERVYfVzRow=
github.com/rivo/tview v0.42.1-0.20250929082832-e113793670e2/go.mod h1:cSfIYfhpSGCjp3r/ECJb+GKS7cGJnqV8vfjQPwoXyfY= github.com/rivo/tview v0.42.1-0.20250929082832-e113793670e2/go.mod h1:cSfIYfhpSGCjp3r/ECJb+GKS7cGJnqV8vfjQPwoXyfY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@@ -102,6 +106,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -114,6 +120,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+19 -8
View File
@@ -24,7 +24,10 @@ func MonitorDevice(ctx context.Context, app *tview.Application, frame **tview.Fr
if err != nil { if err != nil {
if device.Connected { if device.Connected {
device.Close() device.Close()
UpdateDeviceStatus(app, *frame, state, "Cardputer disconnected", tcell.ColorRed) state.Error = "Cardputer disconnected"
UpdateDeviceStatus(app, *frame, state, "Waiting for cardputer...", tcell.ColorWhite)
} else {
UpdateDeviceStatus(app, *frame, state, "Waiting for cardputer...", tcell.ColorWhite)
} }
} else { } else {
if !device.Connected { if !device.Connected {
@@ -34,6 +37,9 @@ func MonitorDevice(ctx context.Context, app *tview.Application, frame **tview.Fr
UpdateDeviceStatus(app, *frame, state, "Error connecting to cardputer: "+err.Error(), tcell.ColorRed) UpdateDeviceStatus(app, *frame, state, "Error connecting to cardputer: "+err.Error(), tcell.ColorRed)
} else { } else {
UpdateDeviceStatus(app, *frame, state, "Connected to cardputer: "+port, tcell.ColorGreen) UpdateDeviceStatus(app, *frame, state, "Connected to cardputer: "+port, tcell.ColorGreen)
if state.Error == "Cardputer disconnected" {
state.Error = ""
}
} }
} else { } else {
UpdateDeviceStatus(app, *frame, state, "Connected to cardputer: "+port, tcell.ColorGreen) UpdateDeviceStatus(app, *frame, state, "Connected to cardputer: "+port, tcell.ColorGreen)
@@ -47,10 +53,21 @@ func main() {
// Initialize Device manager // Initialize Device manager
device := NewDevice() device := NewDevice()
// Ensure cleanup happens no matter how we exit
defer func() {
log.Println("Cleanup: Disconnecting device...")
if err := device.Close(); err != nil {
log.Printf("Cleanup: Error disconnecting device: %v\n", err)
} else {
log.Println("Cleanup: Device disconnected successfully")
}
}()
app := tview.NewApplication() app := tview.NewApplication()
var currentFrame *tview.Frame var currentFrame *tview.Frame
state := &UIState{ state := &UIState{
Page: "home", Page: "home",
Error: "",
} }
frame := AppUI(app, &currentFrame, state) frame := AppUI(app, &currentFrame, state)
currentFrame = frame currentFrame = frame
@@ -66,15 +83,9 @@ func main() {
// Handle signals in a goroutine // Handle signals in a goroutine
go func() { go func() {
<-sigChan <-sigChan
log.Println("Signal received, shutting down...")
// Cancel context to stop monitoring // Cancel context to stop monitoring
cancel() cancel()
// Disconnect device gracefully
if device.Connected {
log.Println("Disconnecting device...")
if err := device.Close(); err != nil {
log.Printf("Error disconnecting device: %v\n", err)
}
}
// Stop the application // Stop the application
app.Stop() app.Stop()
}() }()
+1 -4
View File
@@ -1,10 +1,9 @@
package main package main
import ( import (
"io/fs"
"encoding/json" "encoding/json"
"io/fs"
"strings" "strings"
"log"
) )
type PackageJSON struct { type PackageJSON struct {
@@ -50,12 +49,10 @@ func createCpkg(fsys fs.FS, pkgData *PackageJSON) (*cpkg, error) {
// Skip certain files // Skip certain files
fileName := d.Name() fileName := d.Name()
if fileName == "debug.log" || strings.HasSuffix(fileName, ".cpkg") { if fileName == "debug.log" || strings.HasSuffix(fileName, ".cpkg") {
log.Println("Skipping file:", path)
return nil return nil
} }
// Add file with full relative path // Add file with full relative path
log.Println("Adding file:", path)
err = pkg.AddFile(fsys, path) err = pkg.AddFile(fsys, path)
if err != nil { if err != nil {
return err return err
+39 -1
View File
@@ -1,12 +1,16 @@
package main package main
import ( import (
"os"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/rivo/tview" "github.com/rivo/tview"
) )
type UIState struct { type UIState struct {
Page string Page string
Error string
Success string
} }
func AppUI(app *tview.Application, currentFrame **tview.Frame, state *UIState) *tview.Frame { func AppUI(app *tview.Application, currentFrame **tview.Frame, state *UIState) *tview.Frame {
@@ -18,6 +22,7 @@ func AppUI(app *tview.Application, currentFrame **tview.Frame, state *UIState) *
app.SetRoot(newFrame, true) app.SetRoot(newFrame, true)
}) })
list.AddItem("Upload to Cardputer", "Creates a package and uploads it to the connected Cardputer", 'u', nil) list.AddItem("Upload to Cardputer", "Creates a package and uploads it to the connected Cardputer", 'u', nil)
list.AddItem("Live Refresh", "Monitors for changes and automatically uploads to the Cardputer (WIP)", 'l', nil)
frame := tview.NewFrame(list) frame := tview.NewFrame(list)
frame.SetBorders(0, 0, 0, 0, 0, 0) frame.SetBorders(0, 0, 0, 0, 0, 0)
frame.AddText("Parchment - v0.0.1", true, tview.AlignLeft, tcell.ColorWhite) frame.AddText("Parchment - v0.0.1", true, tview.AlignLeft, tcell.ColorWhite)
@@ -26,9 +31,36 @@ func AppUI(app *tview.Application, currentFrame **tview.Frame, state *UIState) *
return frame return frame
} }
func SetError(state *UIState, text string) {
state.Success = ""
state.Error = text
}
func SetSuccess(state *UIState, text string) {
state.Error = ""
state.Success = text
}
func PackageUI(app *tview.Application, currentFrame **tview.Frame, state *UIState) *tview.Frame { func PackageUI(app *tview.Application, currentFrame **tview.Frame, state *UIState) *tview.Frame {
list := tview.NewList() list := tview.NewList()
list.AddItem("Create Package", "Creates a .cpkg file in the running directory", 'c', nil) list.AddItem("Create Package", "Creates a .cpkg file in the running directory", 'c', func() {
fsys := os.DirFS(".")
pkgData, err := loadPackageJSON(fsys)
if err != nil {
SetError(state, "Failed to load package.json")
return
}
if pkgData == nil {
SetError(state, "Failed to load package.json")
return
}
pkg, err := createCpkg(fsys, pkgData)
if err != nil {
SetError(state, err.Error())
} else {
SetSuccess(state, "Created file "+pkg.packageName+".cpkg")
}
})
list.AddItem("Unpack Package", "Unpacks a chosen .cpkg file into a subdirectory", 'u', nil) list.AddItem("Unpack Package", "Unpacks a chosen .cpkg file into a subdirectory", 'u', nil)
list.AddItem("Back", "Go back to the home page", 'b', func() { list.AddItem("Back", "Go back to the home page", 'b', func() {
newFrame := AppUI(app, currentFrame, state) newFrame := AppUI(app, currentFrame, state)
@@ -52,6 +84,12 @@ func UpdateDeviceStatus(app *tview.Application, frame *tview.Frame, state *UISta
case "package": case "package":
frame.AddText("Package Tools", true, tview.AlignLeft, tcell.ColorWhite) frame.AddText("Package Tools", true, tview.AlignLeft, tcell.ColorWhite)
} }
if state.Error != "" {
frame.AddText(state.Error, false, tview.AlignRight, tcell.ColorRed)
}
if state.Success != "" {
frame.AddText(state.Success, false, tview.AlignRight, tcell.ColorGreen)
}
frame.AddText(status, false, tview.AlignLeft, color) frame.AddText(status, false, tview.AlignLeft, color)
}) })
} }