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
+29 -46
View File
@@ -2,28 +2,26 @@ 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 {
name string name string
content []byte content []byte
} }
type cpkg struct { type cpkg struct {
packageName string packageName string
files []fileData files []fileData
} }
func NewCpkg(packageName string) *cpkg { func NewCpkg(packageName string) *cpkg {
return &cpkg{ return &cpkg{
packageName: packageName, packageName: packageName,
files: make([]fileData, 0), files: make([]fileData, 0),
} }
} }
@@ -44,12 +42,12 @@ func (c *cpkg) createCpkgFileHeader(file fileData) []byte {
headerBytes := make([]byte, 0) headerBytes := make([]byte, 0)
headerBytes = append(headerBytes, byte(len(file.name))) headerBytes = append(headerBytes, byte(len(file.name)))
headerBytes = append(headerBytes, []byte(file.name)...) headerBytes = append(headerBytes, []byte(file.name)...)
// file size (uint32 - 4 bytes) // file size (uint32 - 4 bytes)
fileSizeBytes := make([]byte, 4) fileSizeBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(fileSizeBytes, uint32(len(file.content))) binary.LittleEndian.PutUint32(fileSizeBytes, uint32(len(file.content)))
headerBytes = append(headerBytes, fileSizeBytes...) headerBytes = append(headerBytes, fileSizeBytes...)
return headerBytes return headerBytes
} }
@@ -60,17 +58,17 @@ func (c *cpkg) createCpkgFileHeader(file fileData) []byte {
func (c *cpkg) createCpkgHeader() []byte { func (c *cpkg) createCpkgHeader() []byte {
headerBytes := make([]byte, 0) headerBytes := make([]byte, 0)
headerBytes = append(headerBytes, []byte("Cpkg")...) headerBytes = append(headerBytes, []byte("Cpkg")...)
// version (uint16 - 2 bytes) // version (uint16 - 2 bytes)
versionBytes := make([]byte, 2) versionBytes := make([]byte, 2)
binary.LittleEndian.PutUint16(versionBytes, 1) binary.LittleEndian.PutUint16(versionBytes, 1)
headerBytes = append(headerBytes, versionBytes...) headerBytes = append(headerBytes, versionBytes...)
// file count (uint32 - 4 bytes) // file count (uint32 - 4 bytes)
fileCountBytes := make([]byte, 4) fileCountBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(fileCountBytes, uint32(len(c.files))) binary.LittleEndian.PutUint32(fileCountBytes, uint32(len(c.files)))
headerBytes = append(headerBytes, fileCountBytes...) headerBytes = append(headerBytes, fileCountBytes...)
return headerBytes return headerBytes
} }
@@ -80,7 +78,7 @@ func (c *cpkg) Create() (bool, error) {
return false, err return false, err
} }
defer file.Close() defer file.Close()
// Write main header // Write main header
header := c.createCpkgHeader() header := c.createCpkgHeader()
n, err := file.Write(header) n, err := file.Write(header)
@@ -90,7 +88,7 @@ func (c *cpkg) Create() (bool, error) {
if n != len(header) { if n != len(header) {
return false, fmt.Errorf("incomplete header write: wrote %d of %d bytes", n, len(header)) return false, fmt.Errorf("incomplete header write: wrote %d of %d bytes", n, len(header))
} }
// Write each file // Write each file
for i, fileData := range c.files { for i, fileData := range c.files {
// Write file header // Write file header
@@ -102,7 +100,7 @@ func (c *cpkg) Create() (bool, error) {
if n != len(fileHeader) { if n != len(fileHeader) {
return false, fmt.Errorf("incomplete header write for file %d (%s): wrote %d of %d bytes", i, fileData.name, n, len(fileHeader)) return false, fmt.Errorf("incomplete header write for file %d (%s): wrote %d of %d bytes", i, fileData.name, n, len(fileHeader))
} }
// Write file content // Write file content
n, err = file.Write(fileData.content) n, err = file.Write(fileData.content)
if err != nil { if err != nil {
@@ -111,21 +109,10 @@ func (c *cpkg) Create() (bool, error) {
if n != len(fileData.content) { if 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)) 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
}
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++
} }
return true, nil
} }
func (c *cpkg) Unpack(fsys fs.FS) error { func (c *cpkg) Unpack(fsys fs.FS) error {
@@ -134,33 +121,32 @@ func (c *cpkg) Unpack(fsys fs.FS) error {
return err return err
} }
defer file.Close() defer file.Close()
// Read main header (10 bytes) // Read main header (10 bytes)
header := make([]byte, 10) header := make([]byte, 10)
_, err = file.Read(header) _, err = file.Read(header)
if err != nil { if err != nil {
return fmt.Errorf("failed to read header: %w", err) return fmt.Errorf("failed to read header: %w", err)
} }
// Verify magic number // Verify magic number
magicNumber := string(header[:4]) magicNumber := string(header[:4])
if magicNumber != "Cpkg" { if magicNumber != "Cpkg" {
return fmt.Errorf("invalid cpkg file: wrong magic number") return fmt.Errorf("invalid cpkg file: wrong magic number")
} }
// Verify version // Verify version
version := binary.LittleEndian.Uint16(header[4:6]) version := binary.LittleEndian.Uint16(header[4:6])
if version != 1 { if version != 1 {
return fmt.Errorf("unsupported cpkg file version: %d", version) return fmt.Errorf("unsupported cpkg file version: %d", version)
} }
// 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)
// Read each file sequentially // Read each file sequentially
for i := 0; i < int(fileCount); i++ { for i := 0; i < int(fileCount); i++ {
// Read filename length (1 byte) // Read filename length (1 byte)
@@ -173,7 +159,7 @@ func (c *cpkg) Unpack(fsys fs.FS) error {
return fmt.Errorf("incomplete read of filename length for file %d: got %d bytes, expected 1", i, n) return fmt.Errorf("incomplete read of filename length for file %d: got %d bytes, expected 1", i, n)
} }
fileNameLength := fileNameLengthBuf[0] fileNameLength := fileNameLengthBuf[0]
// Read filename // Read filename
fileNameBuf := make([]byte, fileNameLength) fileNameBuf := make([]byte, fileNameLength)
n, err = file.Read(fileNameBuf) n, err = file.Read(fileNameBuf)
@@ -184,7 +170,7 @@ func (c *cpkg) Unpack(fsys fs.FS) error {
return fmt.Errorf("incomplete read of filename for file %d: got %d bytes, expected %d", i, n, fileNameLength) return fmt.Errorf("incomplete read of filename for file %d: got %d bytes, expected %d", i, n, fileNameLength)
} }
fileName := string(fileNameBuf) fileName := string(fileNameBuf)
// Read file size (4 bytes) // Read file size (4 bytes)
fileSizeBuf := make([]byte, 4) fileSizeBuf := make([]byte, 4)
n, err = file.Read(fileSizeBuf) n, err = file.Read(fileSizeBuf)
@@ -195,7 +181,7 @@ func (c *cpkg) Unpack(fsys fs.FS) error {
return fmt.Errorf("incomplete read of file size for file %d: got %d bytes, expected 4", i, n) return fmt.Errorf("incomplete read of file size for file %d: got %d bytes, expected 4", i, n)
} }
fileSize := binary.LittleEndian.Uint32(fileSizeBuf) fileSize := binary.LittleEndian.Uint32(fileSizeBuf)
// Read file content // Read file content
fileContent := make([]byte, fileSize) fileContent := make([]byte, fileSize)
n, err = file.Read(fileContent) n, err = file.Read(fileContent)
@@ -205,8 +191,7 @@ func (c *cpkg) Unpack(fsys fs.FS) error {
if n != int(fileSize) { if n != int(fileSize) {
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})
} }
@@ -215,24 +200,22 @@ func (c *cpkg) Unpack(fsys fs.FS) error {
if err != nil { if err != nil {
return fmt.Errorf("failed to create output directory: %w", err) return fmt.Errorf("failed to create output directory: %w", err)
} }
for _, fileData := range c.files { for _, fileData := range c.files {
outputPath := filepath.Join(c.packageName, fileData.name) outputPath := filepath.Join(c.packageName, fileData.name)
// Create parent directories if they don't exist // Create parent directories if they don't exist
parentDir := filepath.Dir(outputPath) parentDir := filepath.Dir(outputPath)
err = os.MkdirAll(parentDir, 0755) err = os.MkdirAll(parentDir, 0755)
if err != nil { if err != nil {
return fmt.Errorf("failed to create directory for file %s: %w", fileData.name, err) return fmt.Errorf("failed to create directory for file %s: %w", fileData.name, err)
} }
err = os.WriteFile(outputPath, fileData.content, 0644) err = os.WriteFile(outputPath, fileData.content, 0644)
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=
+20 -9
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()
}() }()
+9 -12
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 {
@@ -35,39 +34,37 @@ func loadPackageJSON(fsys fs.FS) (*PackageJSON, error) {
func createCpkg(fsys fs.FS, pkgData *PackageJSON) (*cpkg, error) { func createCpkg(fsys fs.FS, pkgData *PackageJSON) (*cpkg, error) {
pkg := NewCpkg(pkgData.Pkg) pkg := NewCpkg(pkgData.Pkg)
// Walk through all directories recursively // Walk through all directories recursively
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil { if err != nil {
return err return err
} }
// Skip directories themselves (we only want files) // Skip directories themselves (we only want files)
if d.IsDir() { if d.IsDir() {
return nil return nil
} }
// 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
} }
return nil return nil
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
success, err := pkg.Create() success, err := pkg.Create()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -76,4 +73,4 @@ func createCpkg(fsys fs.FS, pkgData *PackageJSON) (*cpkg, error) {
return nil, err return nil, err
} }
return pkg, nil return pkg, nil
} }
+40 -2
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)
}) })
} }