]> git.r.bdr.sh - rbdr/mobius/commitdiff
First pass at file browsing
authorJeff Halter <redacted>
Sun, 1 Aug 2021 04:05:43 +0000 (21:05 -0700)
committerJeff Halter <redacted>
Sun, 1 Aug 2021 04:05:43 +0000 (21:05 -0700)
hotline/client.go
hotline/field.go
hotline/file_name_with_info.go [new file with mode: 0644]
hotline/file_name_with_info_test.go [new file with mode: 0644]
hotline/files.go
hotline/ui.go

index cfd21eee5b860e16fb853a1828a46ffbae48ea28..70e1d56c490318ee4badad091f1bba2696d5fa49 100644 (file)
@@ -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
index 161a9b29007b14199e3905f763782071abd27779..b057be3134e8a8ec0208a471d10317b9987ba716 100644 (file)
@@ -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 (file)
index 0000000..eecf5ec
--- /dev/null
@@ -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 (file)
index 0000000..15ec0d3
--- /dev/null
@@ -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)
+
+                       }
+               })
+       }
+}
index 69c22b33b9d0728f3ac7e11c70d699fa6aa57a20..1db698baa45a9e74b76e50495e2a651bb86148a5 100644 (file)
@@ -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(),
                ))
        }
index dc852677f51a89fdf5b08b006b37be1891ff6d57..5056954c6ebe170660110be76606a975fa0be16c 100644 (file)
@@ -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 {