diff --git a/cpkg.go b/cpkg.go index 6aab37e..cc14911 100644 --- a/cpkg.go +++ b/cpkg.go @@ -2,28 +2,26 @@ package main import ( "encoding/binary" + "fmt" "io/fs" "os" "path/filepath" - "strconv" - "log" - "fmt" ) type fileData struct { - name string + name string content []byte } type cpkg struct { packageName string - files []fileData + files []fileData } func NewCpkg(packageName string) *cpkg { return &cpkg{ 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 = append(headerBytes, byte(len(file.name))) headerBytes = append(headerBytes, []byte(file.name)...) - + // file size (uint32 - 4 bytes) fileSizeBytes := make([]byte, 4) binary.LittleEndian.PutUint32(fileSizeBytes, uint32(len(file.content))) headerBytes = append(headerBytes, fileSizeBytes...) - + return headerBytes } @@ -60,17 +58,17 @@ func (c *cpkg) createCpkgFileHeader(file fileData) []byte { func (c *cpkg) createCpkgHeader() []byte { headerBytes := make([]byte, 0) headerBytes = append(headerBytes, []byte("Cpkg")...) - + // version (uint16 - 2 bytes) versionBytes := make([]byte, 2) binary.LittleEndian.PutUint16(versionBytes, 1) headerBytes = append(headerBytes, versionBytes...) - + // file count (uint32 - 4 bytes) fileCountBytes := make([]byte, 4) binary.LittleEndian.PutUint32(fileCountBytes, uint32(len(c.files))) headerBytes = append(headerBytes, fileCountBytes...) - + return headerBytes } @@ -80,7 +78,7 @@ func (c *cpkg) Create() (bool, error) { return false, err } defer file.Close() - + // Write main header header := c.createCpkgHeader() n, err := file.Write(header) @@ -90,7 +88,7 @@ func (c *cpkg) Create() (bool, error) { if n != len(header) { return false, fmt.Errorf("incomplete header write: wrote %d of %d bytes", n, len(header)) } - + // Write each file for i, fileData := range c.files { // Write file header @@ -102,7 +100,7 @@ func (c *cpkg) Create() (bool, error) { 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)) } - + // Write file content n, err = file.Write(fileData.content) if err != nil { @@ -111,21 +109,10 @@ func (c *cpkg) Create() (bool, error) { 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)) } - - 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 { @@ -134,33 +121,32 @@ func (c *cpkg) Unpack(fsys fs.FS) error { return err } defer file.Close() - + // Read main header (10 bytes) header := make([]byte, 10) _, err = file.Read(header) if err != nil { return fmt.Errorf("failed to read header: %w", err) } - + // Verify magic number magicNumber := string(header[:4]) if magicNumber != "Cpkg" { return fmt.Errorf("invalid cpkg file: wrong magic number") } - + // Verify version version := binary.LittleEndian.Uint16(header[4:6]) if version != 1 { return fmt.Errorf("unsupported cpkg file version: %d", version) } - + // Read file count fileCount := binary.LittleEndian.Uint32(header[6:10]) - log.Printf("Unpacking %d files from %s.cpkg", fileCount, c.packageName) - + // Clear existing files array c.files = make([]fileData, 0, fileCount) - + // Read each file sequentially for i := 0; i < int(fileCount); i++ { // 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) } fileNameLength := fileNameLengthBuf[0] - + // Read filename fileNameBuf := make([]byte, fileNameLength) 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) } fileName := string(fileNameBuf) - + // Read file size (4 bytes) fileSizeBuf := make([]byte, 4) 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) } fileSize := binary.LittleEndian.Uint32(fileSizeBuf) - + // Read file content fileContent := make([]byte, fileSize) n, err = file.Read(fileContent) @@ -205,8 +191,7 @@ func (c *cpkg) Unpack(fsys fs.FS) error { if n != int(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}) } @@ -215,24 +200,22 @@ func (c *cpkg) Unpack(fsys fs.FS) error { if err != nil { return fmt.Errorf("failed to create output directory: %w", err) } - + for _, fileData := range c.files { outputPath := filepath.Join(c.packageName, fileData.name) - + // Create parent directories if they don't exist parentDir := filepath.Dir(outputPath) err = os.MkdirAll(parentDir, 0755) if err != nil { return fmt.Errorf("failed to create directory for file %s: %w", fileData.name, err) } - + err = os.WriteFile(outputPath, fileData.content, 0644) if err != nil { 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 -} \ No newline at end of file +} diff --git a/device.go b/device.go index a7a34dd..3016129 100644 --- a/device.go +++ b/device.go @@ -68,13 +68,18 @@ func (d *Device) Connect() error { // ExitDevMode sends the command to exit dev mode. func (d *Device) ExitDevMode() error { - if !d.Connected || d.Port == nil { - return fmt.Errorf("device not connected") + if d.Port == nil { + return fmt.Errorf("port not open") } - // Send exit dev mode command - // TODO: Replace with the actual command sequence to exit dev mode - // This is currently not implemented on the Cardputer side. + // disable dev mode + _, err := d.Port.Write([]byte{0xAA, 0x05, 0x00, 0x00, 0x00}) + 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 } diff --git a/go.mod b/go.mod index fdcd717..ad38ab9 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/creack/goselect v0.1.2 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // 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/mattn/go-isatty v0.0.20 // 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/cancelreader v0.2.2 // 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/uniseg v0.4.7 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.bug.st/serial v1.6.4 // indirect golang.org/x/sys v0.36.0 // indirect - golang.org/x/term v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect ) diff --git a/go.sum b/go.sum index 7209f16..20e0a2a 100644 --- a/go.sum +++ b/go.sum @@ -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/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.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/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 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/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 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/go.mod h1:cSfIYfhpSGCjp3r/ECJb+GKS7cGJnqV8vfjQPwoXyfY= 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.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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/main.go b/main.go index c9c7a8c..28f4ac4 100644 --- a/main.go +++ b/main.go @@ -24,7 +24,10 @@ func MonitorDevice(ctx context.Context, app *tview.Application, frame **tview.Fr if err != nil { if device.Connected { 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 { 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) } else { UpdateDeviceStatus(app, *frame, state, "Connected to cardputer: "+port, tcell.ColorGreen) + if state.Error == "Cardputer disconnected" { + state.Error = "" + } } } else { UpdateDeviceStatus(app, *frame, state, "Connected to cardputer: "+port, tcell.ColorGreen) @@ -47,10 +53,21 @@ func main() { // Initialize Device manager 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() var currentFrame *tview.Frame state := &UIState{ - Page: "home", + Page: "home", + Error: "", } frame := AppUI(app, ¤tFrame, state) currentFrame = frame @@ -66,15 +83,9 @@ func main() { // Handle signals in a goroutine go func() { <-sigChan + log.Println("Signal received, shutting down...") // Cancel context to stop monitoring 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 app.Stop() }() diff --git a/packager.go b/packager.go index 57feb3f..9df10a5 100644 --- a/packager.go +++ b/packager.go @@ -1,10 +1,9 @@ package main import ( - "io/fs" "encoding/json" + "io/fs" "strings" - "log" ) type PackageJSON struct { @@ -35,39 +34,37 @@ func loadPackageJSON(fsys fs.FS) (*PackageJSON, error) { func createCpkg(fsys fs.FS, pkgData *PackageJSON) (*cpkg, error) { pkg := NewCpkg(pkgData.Pkg) - + // Walk through all directories recursively err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { return err } - + // Skip directories themselves (we only want files) if d.IsDir() { return nil } - + // Skip certain files fileName := d.Name() if fileName == "debug.log" || strings.HasSuffix(fileName, ".cpkg") { - log.Println("Skipping file:", path) return nil } - + // Add file with full relative path - log.Println("Adding file:", path) err = pkg.AddFile(fsys, path) if err != nil { return err } - + return nil }) - + if err != nil { return nil, err } - + success, err := pkg.Create() if err != nil { return nil, err @@ -76,4 +73,4 @@ func createCpkg(fsys fs.FS, pkgData *PackageJSON) (*cpkg, error) { return nil, err } return pkg, nil -} \ No newline at end of file +} diff --git a/tui.go b/tui.go index 9061079..1342789 100644 --- a/tui.go +++ b/tui.go @@ -1,12 +1,16 @@ package main import ( + "os" + "github.com/gdamore/tcell/v2" "github.com/rivo/tview" ) type UIState struct { - Page string + Page string + Error string + Success string } 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) }) 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.SetBorders(0, 0, 0, 0, 0, 0) 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 } +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 { 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("Back", "Go back to the home page", 'b', func() { newFrame := AppUI(app, currentFrame, state) @@ -52,6 +84,12 @@ func UpdateDeviceStatus(app *tview.Application, frame *tview.Frame, state *UISta case "package": 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) }) }