Add local and remote folder browsing

This commit is contained in:
Trevor Slocum 2020-04-08 19:54:56 -07:00
parent f61003972c
commit dc78a21119
6 changed files with 195 additions and 49 deletions

View File

@ -4,6 +4,8 @@ import (
"log"
"os"
"path/filepath"
"sort"
"strings"
"github.com/gdamore/tcell"
"gitlab.com/tslocum/cbind"
@ -23,13 +25,13 @@ var (
)
func initTUI() error {
devices, err := adb.ListDevices()
devices, err := bridge.ListDevices()
if err != nil {
log.Fatalf("failed to list devices: %s", err)
}
if len(devices) == 0 {
log.Fatal("connect to a device via ADB before launching")
log.Fatal("failed to start adbfm: please connect to a device via ADB before launching")
}
/*cview.Styles.TitleColor = tcell.ColorDefault
@ -42,7 +44,7 @@ func initTUI() error {
devicesDropDown = cview.NewDropDown().SetLabel(" Device: ").SetLabelColor(tcell.ColorDefault)
for _, device := range devices {
devicesDropDown.AddOption(device.Serial, setDeviceFunc(adb, device))
devicesDropDown.AddOption(device.Serial, setDeviceFunc(device))
}
localBuffer = cview.NewList().ShowSecondaryText(false).SetScrollBarVisibility(cview.ScrollBarAlways)
@ -84,7 +86,11 @@ func focusUpdated() {
}
func browseLocal(p string) {
var entries []string
localLock.Lock()
defer localLock.Unlock()
localEntries = nil
var skippedFirst bool
err := filepath.Walk(p, func(path string, info os.FileInfo, err error) error {
if err != nil {
@ -95,9 +101,10 @@ func browseLocal(p string) {
}
// TODO Store entries with more info
entries = append(entries, info.Name())
localEntries = append(localEntries, info)
if info.IsDir() {
// Do not search recursively
if info.IsDir() && info.Name() != ".." {
return filepath.SkipDir
}
return nil
@ -106,25 +113,72 @@ func browseLocal(p string) {
log.Fatal(err)
}
localBuffer.SetTitle(" " + p + " ")
sort.Slice(localEntries, func(i, j int) bool {
if localEntries[i].Name() == ".." {
return true
} else if localEntries[i].IsDir() != localEntries[j].IsDir() {
return localEntries[i].IsDir()
}
localBuffer.Clear()
for _, entry := range entries {
localBuffer.AddItem(entry, "", 0, nil)
}
return strings.ToLower(localEntries[i].Name()) < strings.ToLower(localEntries[j].Name())
})
app.QueueUpdateDraw(func() {
localLock.Lock()
defer localLock.Unlock()
localBuffer.SetTitle(" " + p + " ")
localBuffer.Clear()
for _, entry := range localEntries {
if len(entry.Name()) > 0 && entry.Name()[0] == '.' && entry.Name() != ".." && !showHidden {
continue
}
label := entry.Name()
if entry.IsDir() {
label += "/"
}
localBuffer.AddItem(label, "", 0, nil)
}
})
}
func browseRemote(p string) {
entries, err := adb.DirectoryEntries(p)
remoteLock.Lock()
defer remoteLock.Unlock()
app.QueueUpdateDraw(func() {
remoteBuffer.SetTitle(" " + remotePath + "... ")
})
var err error
remoteEntries, err = bridge.DirectoryEntries(p)
if err != nil {
log.Fatalf("failed to list files: %s", err)
}
remotePath = p
go app.QueueUpdateDraw(func() {
remoteBuffer.Clear()
remoteLock.Lock()
defer remoteLock.Unlock()
remoteBuffer.SetTitle(" " + p + " ")
for _, entry := range entries {
remoteBuffer.AddItem(entry.Name, "", 0, nil)
remoteBuffer.Clear()
for _, entry := range remoteEntries {
if len(entry.Name) > 0 && entry.Name[0] == '.' && entry.Name != ".." && !showHidden {
continue
}
label := entry.Name
if entry.Mode&os.ModeDir != 0 {
label += "/"
}
remoteBuffer.AddItem(label, "", 0, nil)
}
})
}

View File

@ -1,13 +1,54 @@
package main
import "github.com/gdamore/tcell"
import (
"log"
"os"
"path"
"github.com/gdamore/tcell"
)
func selectItem(ev *tcell.EventKey) *tcell.EventKey {
if currentFocus == 0 {
return ev
// Select device
} else if currentFocus == 1 {
localLock.Lock()
defer localLock.Unlock()
if localBuffer.GetItemCount() > 0 {
currentItem := localBuffer.GetCurrentItem()
if currentItem >= 0 && currentItem < len(localEntries) {
newPath := localEntries[currentItem].Name()
if localEntries[currentItem].IsDir() {
go browseLocal(path.Join(localPath, newPath))
return nil
}
log.Fatal(remoteEntries[currentItem])
return nil
}
}
return nil
} else { // == 2
remoteLock.Lock()
defer remoteLock.Unlock()
if remoteBuffer.GetItemCount() > 0 {
currentItem := remoteBuffer.GetCurrentItem()
if currentItem >= 0 && currentItem < len(remoteEntries) {
newPath := remoteEntries[currentItem].Name
if remoteEntries[currentItem].Mode&os.ModeDir != 0 {
go browseRemote(path.Join(remotePath, newPath))
return nil
}
log.Fatal(remoteEntries[currentItem])
return nil
}
}
}
return nil
return ev
}

View File

@ -1,13 +1,16 @@
package main
import (
"errors"
"flag"
"fmt"
"log"
"os"
"strings"
"sync"
"github.com/gdamore/tcell"
adb "github.com/zach-klippenstein/goadb"
goadb "github.com/zach-klippenstein/goadb"
"gitlab.com/tslocum/adbfm/pkg/android"
"gitlab.com/tslocum/cbind"
@ -22,10 +25,18 @@ type appConfig struct {
var config = &appConfig{}
var (
showHidden bool
localPath string
remotePath string
adb *android.ADB
localEntries []os.FileInfo
remoteEntries []*adb.DirEntry
localLock sync.Mutex
remoteLock sync.Mutex
bridge *android.Bridge
done = make(chan bool)
)
@ -82,22 +93,41 @@ func setDefaultKeyBinds() {
}
}
func setDeviceFunc(adb *android.ADB, device *goadb.DeviceInfo) func() {
func setDeviceFunc(device *goadb.DeviceInfo) func() {
return func() {
err := adb.SetDevice(device)
if err != nil {
log.Fatal(err)
}
go func() {
localLock.Lock()
remoteLock.Lock()
browseRemote(remotePath)
err := bridge.SetDevice(device)
if err != nil {
log.Fatal(err)
}
remoteLock.Unlock()
localLock.Unlock()
browseRemote(remotePath)
}()
}
}
func main() {
log.SetFlags(0)
log.SetPrefix("")
flag.StringVar(&localPath, "local", "", "local starting directory")
flag.StringVar(&remotePath, "remote", "", "remote starting directory")
flag.BoolVar(&showHidden, "hidden", false, "show hidden files and folders")
flag.Parse()
err := run()
if err != nil {
log.Fatalf("failed to start adbfm: %s", err)
}
}
func run() error {
if localPath == "" {
homeDir, err := os.UserHomeDir()
if err == nil && homeDir != "" {
@ -105,7 +135,7 @@ func main() {
}
}
if localPath == "" {
log.Fatalf("failed to start adbfm: local path must be specified with --local")
return errors.New("ocal path must be specified with --local")
}
if remotePath == "" {
@ -113,22 +143,22 @@ func main() {
}
var err error
adb, err = android.NewADB("", 0, "")
bridge, err = android.NewBridge("", 0, "")
if err != nil {
log.Fatalf("failed to connect to ADB server: %s", err)
return fmt.Errorf("failed to connect to ADB server: %s", err)
}
err = setKeyBinds()
if err != nil {
log.Fatalf("failed to set keybinds: %s", err)
return fmt.Errorf("failed to set keybinds: %s", err)
}
err = initTUI()
if err != nil {
log.Fatalf("failed to initialize user interface: %s", err)
return fmt.Errorf("failed to initialize user interface: %s", err)
}
browseLocal(localPath)
go browseLocal(localPath)
go func() {
if err := app.Run(); err != nil {
@ -139,4 +169,5 @@ func main() {
}()
<-done
return nil
}

4
go.mod
View File

@ -7,6 +7,6 @@ require (
github.com/stretchr/testify v1.4.0 // indirect
github.com/zach-klippenstein/goadb v0.0.0-20170530005145-029cc6bee481
gitlab.com/tslocum/cbind v0.1.1
gitlab.com/tslocum/cview v1.4.4-0.20200224160604-74844d6d3f06
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae // indirect
gitlab.com/tslocum/cview v1.4.5-0.20200403150121-d2f2938ea070
golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b // indirect
)

14
go.sum
View File

@ -11,6 +11,8 @@ github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
@ -22,15 +24,15 @@ github.com/zach-klippenstein/goadb v0.0.0-20170530005145-029cc6bee481 h1:yVrbGOZ
github.com/zach-klippenstein/goadb v0.0.0-20170530005145-029cc6bee481/go.mod h1:Drd+klC4FSDx0vKNEQDsSpWX5so04NA7l0vzHqkH8AQ=
gitlab.com/tslocum/cbind v0.1.1 h1:JXXtxMWHgWLvoF+QkrvcNvOQ59juy7OE1RhT7hZfdt0=
gitlab.com/tslocum/cbind v0.1.1/go.mod h1:rX7vkl0pUSg/yy427MmD1FZAf99S7WwpUlxF/qTpPqk=
gitlab.com/tslocum/cview v1.4.4-0.20200224160604-74844d6d3f06 h1:in6osHxntontXHPyNg46ry0rbKDdrZQf4OoNVJLXaQs=
gitlab.com/tslocum/cview v1.4.4-0.20200224160604-74844d6d3f06/go.mod h1:+bEf1cg6IoWvL16YHJAKwGGpQf5s/nxXAA7YJr+WOHE=
gitlab.com/tslocum/cview v1.4.5-0.20200403150121-d2f2938ea070 h1:JjfUFMVbUbB5R2ozidsmicpIZcRC7h6p6c019WIJODQ=
gitlab.com/tslocum/cview v1.4.5-0.20200403150121-d2f2938ea070/go.mod h1:3J8gp3ocr/DDjkyDKcstfZvMGs3BBYZHpjnlpOPzASQ=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775 h1:TC0v2RSO1u2kn1ZugjrFXkRZAEaqMN/RW+OTZkBzmLE=
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b h1:h03Ur1RlPrGTjua4koYdpGl8W0eYo8p1uI9w7RPlkdk=
golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=

View File

@ -4,6 +4,9 @@ import (
"errors"
"fmt"
"log"
"os"
"sort"
"strings"
"sync"
adb "github.com/zach-klippenstein/goadb"
@ -13,16 +16,16 @@ import (
const DefaultPort = 5037
// ADB represents a connection with an ADB server.
type ADB struct {
type Bridge struct {
bridge *adb.Adb
device *adb.Device
*sync.Mutex
}
// NewADB establishes a connection with an ADB server. When host and port are
// unspecified they are replaced with localhost and the default port.
func NewADB(host string, port int, pathToADB string) (*ADB, error) {
// NewBridge establishes a connection with an ADB server. When host and port
// are unspecified they are replaced with localhost and the default port.
func NewBridge(host string, port int, pathToADB string) (*Bridge, error) {
bridge, err := adb.NewWithConfig(adb.ServerConfig{
PathToAdb: pathToADB,
Host: host,
@ -39,11 +42,11 @@ func NewADB(host string, port int, pathToADB string) (*ADB, error) {
return nil, fmt.Errorf("failed to get server version: %s", err)
}
a := ADB{bridge: bridge, Mutex: new(sync.Mutex)}
a := Bridge{bridge: bridge, Mutex: new(sync.Mutex)}
return &a, nil
}
func (a *ADB) ListDevices() (devices []*adb.DeviceInfo, err error) {
func (a *Bridge) ListDevices() (devices []*adb.DeviceInfo, err error) {
a.Lock()
defer a.Unlock()
@ -59,14 +62,14 @@ func (a *ADB) ListDevices() (devices []*adb.DeviceInfo, err error) {
return devices, nil
}
func (a *ADB) SetDevice(deviceInfo *adb.DeviceInfo) (err error) {
func (a *Bridge) SetDevice(deviceInfo *adb.DeviceInfo) (err error) {
a.Lock()
defer a.Unlock()
return a.setDevice(deviceInfo)
}
func (a *ADB) setDevice(deviceInfo *adb.DeviceInfo) (err error) {
func (a *Bridge) setDevice(deviceInfo *adb.DeviceInfo) (err error) {
device := a.bridge.Device(adb.DeviceWithSerial(deviceInfo.Serial))
if device == nil {
return fmt.Errorf("failed to get device with serial %s", deviceInfo.Serial)
@ -76,7 +79,7 @@ func (a *ADB) setDevice(deviceInfo *adb.DeviceInfo) (err error) {
return nil
}
func (a *ADB) DirectoryEntries(path string) (entries []*adb.DirEntry, err error) {
func (a *Bridge) DirectoryEntries(path string) (entries []*adb.DirEntry, err error) {
a.Lock()
defer a.Unlock()
@ -90,11 +93,26 @@ func (a *ADB) DirectoryEntries(path string) (entries []*adb.DirEntry, err error)
}
for listEntries.Next() {
entries = append(entries, listEntries.Entry())
entry := listEntries.Entry()
if entry.Name == "." {
continue
}
entries = append(entries, entry)
}
if listEntries.Err() != nil {
return nil, fmt.Errorf("failed to list files: %s", err)
}
sort.Slice(entries, func(i, j int) bool {
if entries[i].Name == ".." {
return true
} else if (entries[i].Mode&os.ModeDir != 0) != (entries[j].Mode&os.ModeDir != 0) {
return entries[i].Mode&os.ModeDir != 0
}
return strings.ToLower(entries[i].Name) < strings.ToLower(entries[j].Name)
})
return entries, nil
}