From: Jeff Halter Date: Sun, 1 Aug 2021 04:05:43 +0000 (-0700) Subject: First pass at file browsing X-Git-Url: https://git.r.bdr.sh/rbdr/mobius/commitdiff_plain/43ecc0f42eaeface5f640479df7372bfb8021f23?ds=inline;hp=625b0580010c7fbf183c53e388ceba8356df76f4 First pass at file browsing --- diff --git a/hotline/client.go b/hotline/client.go index cfd21ee..70e1d56 100644 --- a/hotline/client.go +++ b/hotline/client.go @@ -71,7 +71,7 @@ type Client struct { ID *[]byte Version []byte UserAccess []byte - Agreed bool + filePath []string UserList []User Logger *zap.SugaredLogger activeTasks map[uint32]*Transaction @@ -97,15 +97,14 @@ func NewClient(cfgPath string, logger *zap.SugaredLogger) *Client { prefs, err := readConfig(cfgPath) if err != nil { - fmt.Printf("unable to read config file") - logger.Fatal("unable to read config file", "path", cfgPath) + fmt.Printf("unable to read config file %s", cfgPath) + os.Exit(1) } c.pref = prefs return c } - // DebugBuffer wraps a *tview.TextView and adds a Sync() method to make it available as a Zap logger type DebugBuffer struct { TextView *tview.TextView @@ -129,10 +128,6 @@ func randomBanner() string { return fmt.Sprintf("\n\n\nWelcome to...\n\n[red::b]%s[-:-:-]\n\n", file) } - - - - type clientTransaction struct { Name string Handler func(*Client, *Transaction) ([]Transaction, error) @@ -189,6 +184,93 @@ var clientHandlers = map[uint16]clientTHandler{ Name: "tranNotifyDeleteUser", Handler: handleGetMsgs, }, + tranGetFileNameList: clientTransaction{ + Name: "tranGetFileNameList", + Handler: handleGetFileNameList, + }, +} + +func handleGetFileNameList(c *Client, t *Transaction) (res []Transaction, err error) { + fTree := tview.NewTreeView().SetTopLevel(1) + root := tview.NewTreeNode("Root") + fTree.SetRoot(root).SetCurrentNode(root) + fTree.SetBorder(true).SetTitle("| Files |") + fTree.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + switch event.Key() { + case tcell.KeyEscape: + c.UI.Pages.RemovePage("files") + c.filePath = []string{} + case tcell.KeyEnter: + selectedNode := fTree.GetCurrentNode() + + if selectedNode.GetText() == "<- Back" { + c.filePath = c.filePath[:len(c.filePath)-1] + f := NewField(fieldFilePath, EncodeFilePath(strings.Join(c.filePath, "/"))) + + if err := c.UI.HLClient.Send(*NewTransaction(tranGetFileNameList, nil, f)); err != nil { + c.UI.HLClient.Logger.Errorw("err", "err", err) + } + return event + } + + entry := selectedNode.GetReference().(*FileNameWithInfo) + + if bytes.Equal(entry.Type, []byte("fldr")) { + c.Logger.Infow("get new directory listing", "name", string(entry.Name)) + + c.filePath = append(c.filePath, string(entry.Name)) + f := NewField(fieldFilePath, EncodeFilePath(strings.Join(c.filePath, "/"))) + + if err := c.UI.HLClient.Send(*NewTransaction(tranGetFileNameList, nil, f)); err != nil { + c.UI.HLClient.Logger.Errorw("err", "err", err) + } + } else { + // TODO: initiate file download + c.Logger.Infow("download file", "name", string(entry.Name)) + } + } + + return event + }) + + if len(c.filePath) > 0 { + node := tview.NewTreeNode("<- Back") + root.AddChild(node) + } + + var fileList []FileNameWithInfo + for _, f := range t.Fields { + var fn FileNameWithInfo + _, _ = fn.Read(f.Data) + fileList = append(fileList, fn) + + if bytes.Equal(fn.Type, []byte("fldr")) { + node := tview.NewTreeNode(fmt.Sprintf("[blue::]📁 %s[-:-:-]", fn.Name)) + node.SetReference(&fn) + root.AddChild(node) + } else { + size := binary.BigEndian.Uint32(fn.FileSize) / 1024 + + node := tview.NewTreeNode(fmt.Sprintf(" %-10s %10v KB", fn.Name, size)) + node.SetReference(&fn) + root.AddChild(node) + } + + } + + centerFlex := tview.NewFlex(). + AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex(). + SetDirection(tview.FlexRow). + AddItem(nil, 0, 1, false). + AddItem(fTree, 20, 1, true). + AddItem(nil, 0, 1, false), 40, 1, true). + AddItem(nil, 0, 1, false) + + c.UI.Pages.AddPage("files", centerFlex, true, true) + c.UI.App.Draw() + + return res, err } func handleGetMsgs(c *Client, t *Transaction) (res []Transaction, err error) { @@ -204,8 +286,8 @@ func handleGetMsgs(c *Client, t *Transaction) (res []Transaction, err error) { newsTextView.SetBorder(true).SetTitle("News") c.UI.Pages.AddPage("news", newsTextView, true, true) - c.UI.Pages.SwitchToPage("news") - c.UI.App.SetFocus(newsTextView) + //c.UI.Pages.SwitchToPage("news") + //c.UI.App.SetFocus(newsTextView) c.UI.App.Draw() return res, err @@ -379,7 +461,6 @@ func handleClientTranShowAgreement(c *Client, t *Transaction) (res []Transaction NewField(fieldOptions, []byte{0x00, 0x00}), ), ) - c.Agreed = true c.UI.Pages.HidePage("agreement") c.UI.App.SetFocus(c.UI.chatInput) } else { @@ -556,7 +637,6 @@ func (c *Client) HandleTransaction(t *Transaction) error { } func (c *Client) Connected() bool { - fmt.Printf("Agreed: %v UserAccess: %v\n", c.Agreed, c.UserAccess) // c.Agreed == true && if c.UserAccess != nil { return true diff --git a/hotline/field.go b/hotline/field.go index 161a9b2..b057be3 100644 --- a/hotline/field.go +++ b/hotline/field.go @@ -87,34 +87,3 @@ func NewField(id uint16, data []byte) Field { func (f Field) Payload() []byte { return concat.Slices(f.ID, f.FieldSize, f.Data) } - -type FileNameWithInfo struct { - Type string // file type code - Creator []byte // File creator code - FileSize uint32 // File Size in bytes - NameScript []byte // TODO: What is this? - NameSize []byte // Length of name field - Name string // File name -} - -func (f FileNameWithInfo) Payload() []byte { - name := []byte(f.Name) - nameSize := make([]byte, 2) - binary.BigEndian.PutUint16(nameSize, uint16(len(name))) - - kb := f.FileSize - - fSize := make([]byte, 4) - binary.BigEndian.PutUint32(fSize, kb) - - return concat.Slices( - []byte(f.Type), - f.Creator, - fSize, - []byte{0, 0, 0, 0}, - f.NameScript, - nameSize, - []byte(f.Name), - ) - -} diff --git a/hotline/file_name_with_info.go b/hotline/file_name_with_info.go new file mode 100644 index 0000000..eecf5ec --- /dev/null +++ b/hotline/file_name_with_info.go @@ -0,0 +1,53 @@ +package hotline + +import ( + "encoding/binary" + "github.com/jhalter/mobius/concat" +) + +// FileNameWithInfo field content is presented in this structure: +// Type 4 Folder (‘fldr’) or other +// Creator 4 +// File size 4 +// 4 Reserved? +// Name script 2 +// Name size 2 +// Name data size +type FileNameWithInfo struct { + Type []byte // file type code + Creator []byte // File creator code + FileSize []byte // File Size in bytes + RSVD []byte + NameScript []byte // TODO: What is this? + NameSize []byte // Length of name field + Name []byte // File name +} + +func (f FileNameWithInfo) Payload() []byte { + name := f.Name + nameSize := make([]byte, 2) + binary.BigEndian.PutUint16(nameSize, uint16(len(name))) + + return concat.Slices( + f.Type, + f.Creator, + f.FileSize, + []byte{0, 0, 0, 0}, + f.NameScript, + nameSize, + f.Name, + ) +} + +func (f *FileNameWithInfo) Read(p []byte) (n int, err error) { + // TODO: check p for expected len + f.Type = p[0:4] + f.Creator = p[4:8] + f.FileSize = p[8:12] + f.RSVD = p[12:16] + f.NameScript = p[16:18] + f.NameSize = p[18:20] + f.Name = p[20:] + + return len(p), err +} diff --git a/hotline/file_name_with_info_test.go b/hotline/file_name_with_info_test.go new file mode 100644 index 0000000..15ec0d3 --- /dev/null +++ b/hotline/file_name_with_info_test.go @@ -0,0 +1,79 @@ +package hotline + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestFileNameWithInfo_Read(t *testing.T) { + type fields struct { + Type []byte + Creator []byte + FileSize []byte + NameScript []byte + NameSize []byte + Name []byte + } + type args struct { + p []byte + } + tests := []struct { + name string + fields fields + args args + want *FileNameWithInfo + wantN int + wantErr bool + }{ + { + name: "reads bytes into struct", + fields: fields{}, + args: args{ + p: []byte{ + 0x54, 0x45, 0x58, 0x54, // TEXT + 0x54, 0x54, 0x58, 0x54, // TTXT + 0x00, 0x43, 0x16, 0xd3, // File Size + 0x00, 0x00, 0x00, 0x00, // RSVD + 0x00, 0x00, // NameScript + 0x00, 0x0e, // Name Size + 0x41, 0x75, 0x64, 0x69, 0x6f, 0x6e, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x7a, 0x69, 0x70, + }, + }, + want: &FileNameWithInfo{ + Type: []byte("TEXT"), + Creator: []byte("TTXT"), + FileSize: []byte{0x00, 0x43, 0x16, 0xd3}, + RSVD: []byte{0, 0, 0, 0}, + NameScript: []byte{0, 0}, + NameSize: []byte{0x00, 0x0e}, + Name: []byte("Audion.app.zip"), + }, + wantN: 34, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f := &FileNameWithInfo{ + Type: tt.fields.Type, + Creator: tt.fields.Creator, + FileSize: tt.fields.FileSize, + NameScript: tt.fields.NameScript, + NameSize: tt.fields.NameSize, + Name: tt.fields.Name, + } + gotN, err := f.Read(tt.args.p) + if (err != nil) != tt.wantErr { + t.Errorf("Read() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotN != tt.wantN { + t.Errorf("Read() gotN = %v, want %v", gotN, tt.wantN) + } + if !assert.Equal(t, tt.want, f) { + t.Errorf("Read() got = %v, want %v", f, tt.want) + + } + }) + } +} diff --git a/hotline/files.go b/hotline/files.go index 69c22b3..1db698b 100644 --- a/hotline/files.go +++ b/hotline/files.go @@ -52,22 +52,22 @@ func getFileNameList(filePath string) ([]Field, error) { } for _, file := range files { - var fileType string - var fileCreator []byte - var fileSize uint32 + var fileType []byte + fileCreator := make([]byte, 4) + fileSize := make([]byte, 4) if !file.IsDir() { - fileType = fileTypeFromFilename(file.Name()) + fileType = []byte(fileTypeFromFilename(file.Name())) fileCreator = []byte(fileCreatorFromFilename(file.Name())) - fileSize = uint32(file.Size()) + + binary.BigEndian.PutUint32(fileSize, uint32(file.Size())) } else { - fileType = "fldr" - fileCreator = make([]byte, 4) + fileType = []byte("fldr") dir, err := ioutil.ReadDir(filePath + "/" + file.Name()) if err != nil { return fields, err } - fileSize = uint32(len(dir)) + binary.BigEndian.PutUint32(fileSize, uint32(len(dir))) } fields = append(fields, NewField( @@ -77,7 +77,7 @@ func getFileNameList(filePath string) ([]Field, error) { Creator: fileCreator, FileSize: fileSize, NameScript: []byte{0, 0}, - Name: file.Name(), + Name: []byte(file.Name()), }.Payload(), )) } diff --git a/hotline/ui.go b/hotline/ui.go index dc85267..5056954 100644 --- a/hotline/ui.go +++ b/hotline/ui.go @@ -285,7 +285,7 @@ func (ui *UI) renderServerUI() *tview.Flex { commandList. SetText("[yellow]^n[-::]: Read News [yellow]^p[-::]: Post News\n[yellow]^l[-::]: View Logs\n"). SetBorder(true). - SetTitle("Keyboard Shortcuts") + SetTitle("| Keyboard Shortcuts| ") modal := tview.NewModal(). SetText("Disconnect from the server?"). @@ -313,6 +313,13 @@ func (ui *UI) renderServerUI() *tview.Flex { ui.Pages.AddPage("modal", modal, false, true) } + // List files + if event.Key() == tcell.KeyCtrlF { + if err := ui.HLClient.Send(*NewTransaction(tranGetFileNameList, nil)); err != nil { + ui.HLClient.Logger.Errorw("err", "err", err) + } + } + // Show News if event.Key() == tcell.KeyCtrlN { if err := ui.HLClient.Send(*NewTransaction(tranGetMsgs, nil)); err != nil {