Support creating a new folder
This commit is contained in:
parent
03675d41ac
commit
5350889e5b
|
@ -1,5 +1,4 @@
|
|||
.idea/
|
||||
dist/
|
||||
*.sh
|
||||
cmd/adbfm/adbfm
|
||||
cmd/adbfm-gtk/adbfm-gtk
|
||||
adbfm
|
||||
|
|
|
@ -13,24 +13,14 @@ fmt:
|
|||
vet:
|
||||
stage: validate
|
||||
script:
|
||||
- apt-get update && apt-get install -y libgtk-3-dev
|
||||
- go vet -composites=false ./...
|
||||
|
||||
test:
|
||||
stage: validate
|
||||
script:
|
||||
- apt-get update && apt-get install -y libgtk-3-dev
|
||||
- go test -race -v ./...
|
||||
|
||||
build-adbfm:
|
||||
build:
|
||||
stage: build
|
||||
script:
|
||||
- cd cmd/adbfm
|
||||
- go build
|
||||
|
||||
build-adbfm-gtk:
|
||||
stage: build
|
||||
script:
|
||||
- apt-get update && apt-get install -y libgtk-3-dev
|
||||
- cd cmd/adbfm-gtk
|
||||
- go build
|
||||
|
|
27
README.md
27
README.md
|
@ -4,10 +4,33 @@
|
|||
|
||||
ADB file manager
|
||||
|
||||
## Dependencies
|
||||
**Note:** adbfm is in early development. Additional features are planned.
|
||||
|
||||
* [zach-klippenstein/goadb](https://github.com/zach-klippenstein/goadb)
|
||||
## Features
|
||||
|
||||
- Download
|
||||
- Upload
|
||||
- Rename
|
||||
- New folder
|
||||
|
||||
## Download
|
||||
|
||||
```bash
|
||||
go get gitlab.com/tslocum/adbfm/cmd/adbfm
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
adbfm may be used with a keyboard or mouse.
|
||||
|
||||
- Navigation: Arrow keys or VIM-keys (H/J/K/L), Tab, Shift+Tab
|
||||
- Enter directory: Enter
|
||||
- Select file or option: Enter
|
||||
|
||||
## Support
|
||||
|
||||
Please share issues and suggestions [here](https://gitlab.com/tslocum/adbfm/issues).
|
||||
|
||||
## Dependencies
|
||||
|
||||
* [zach-klippenstein/goadb](https://github.com/zach-klippenstein/goadb)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package android
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
@ -18,8 +18,8 @@ import (
|
|||
// DefaultPort is the default port the ADB server listens on.
|
||||
const DefaultPort = 5037
|
||||
|
||||
// ADB represents a connection with an ADB server.
|
||||
type Bridge struct {
|
||||
// adbConn represents a connection with an ADB server.
|
||||
type adbConn struct {
|
||||
bridge *adb.Adb
|
||||
device *adb.Device
|
||||
dir string
|
||||
|
@ -27,9 +27,9 @@ type Bridge struct {
|
|||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewBridge establishes a connection with an ADB server. When host and port
|
||||
// connect 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 *Bridge, err error) {
|
||||
func connect(host string, port int, pathToADB string) (bridge *adbConn, err error) {
|
||||
adbBridge, err := adb.NewWithConfig(adb.ServerConfig{
|
||||
PathToAdb: pathToADB,
|
||||
Host: host,
|
||||
|
@ -46,10 +46,10 @@ func NewBridge(host string, port int, pathToADB string) (bridge *Bridge, err err
|
|||
return nil, fmt.Errorf("failed to get server version: %s", err)
|
||||
}
|
||||
|
||||
return &Bridge{bridge: adbBridge}, nil
|
||||
return &adbConn{bridge: adbBridge}, nil
|
||||
}
|
||||
|
||||
func (a *Bridge) ListDevices() (devices []*adb.DeviceInfo, err error) {
|
||||
func (a *adbConn) listDevices() (devices []*adb.DeviceInfo, err error) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
|
@ -65,14 +65,10 @@ func (a *Bridge) ListDevices() (devices []*adb.DeviceInfo, err error) {
|
|||
return devices, nil
|
||||
}
|
||||
|
||||
func (a *Bridge) SetDevice(deviceInfo *adb.DeviceInfo) (err error) {
|
||||
func (a *adbConn) setDevice(deviceInfo *adb.DeviceInfo) (err error) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
return a.setDevice(deviceInfo)
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -82,7 +78,7 @@ func (a *Bridge) setDevice(deviceInfo *adb.DeviceInfo) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *Bridge) Upload(fileName string, data []byte, mtime time.Time) (err error) {
|
||||
func (a *adbConn) upload(fileName string, data []byte, mtime time.Time) (err error) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
|
@ -102,7 +98,7 @@ func (a *Bridge) Upload(fileName string, data []byte, mtime time.Time) (err erro
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *Bridge) Download(fileName string) (data []byte, err error) {
|
||||
func (a *adbConn) download(fileName string) (data []byte, err error) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
|
@ -120,7 +116,7 @@ func (a *Bridge) Download(fileName string) (data []byte, err error) {
|
|||
return data, nil
|
||||
}
|
||||
|
||||
func (a *Bridge) DirectoryEntries(dir string) (entries []*adb.DirEntry, err error) {
|
||||
func (a *adbConn) directoryEntries(dir string) (entries []*adb.DirEntry, err error) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
|
@ -160,7 +156,7 @@ func (a *Bridge) DirectoryEntries(dir string) (entries []*adb.DirEntry, err erro
|
|||
return entries, nil
|
||||
}
|
||||
|
||||
func (a *Bridge) Move(old string, new string) (err error) {
|
||||
func (a *adbConn) move(old string, new string) (err error) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
|
@ -175,3 +171,19 @@ func (a *Bridge) Move(old string, new string) (err error) {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *adbConn) newDirectory(path string) (err error) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
if a.device == nil {
|
||||
return errors.New("no device")
|
||||
}
|
||||
|
||||
_, err = a.device.RunCommand("mkdir", path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package main
|
||||
|
||||
func main() {
|
||||
// TODO
|
||||
}
|
458
cmd/adbfm/gui.go
458
cmd/adbfm/gui.go
|
@ -1,458 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/mattn/go-runewidth"
|
||||
adb "github.com/zach-klippenstein/goadb"
|
||||
"gitlab.com/tslocum/cbind"
|
||||
"gitlab.com/tslocum/cview"
|
||||
)
|
||||
|
||||
var (
|
||||
app *cview.Application
|
||||
|
||||
inputConfig = cbind.NewConfiguration()
|
||||
|
||||
appGrid *cview.Grid
|
||||
appContainer *cview.Pages
|
||||
modalPopup *cview.Modal
|
||||
devicesDropDown *cview.DropDown
|
||||
localBuffer *cview.List
|
||||
remoteBuffer *cview.List
|
||||
|
||||
renameField *cview.InputField
|
||||
renameItem int
|
||||
|
||||
currentFocus = 0
|
||||
currentMode = modeNormal
|
||||
modeLock sync.RWMutex
|
||||
)
|
||||
|
||||
const contextMenuTitleWidth = 16
|
||||
|
||||
const (
|
||||
modeNormal = 0
|
||||
modeRename = 1
|
||||
)
|
||||
|
||||
func getMode() int {
|
||||
modeLock.RLock()
|
||||
defer modeLock.RUnlock()
|
||||
|
||||
return currentMode
|
||||
}
|
||||
|
||||
func setMode(mode int) {
|
||||
modeLock.Lock()
|
||||
defer modeLock.Unlock()
|
||||
|
||||
currentMode = mode
|
||||
}
|
||||
|
||||
func acceptRename(buttonIndex int, buttonLabel string) {
|
||||
if buttonIndex >= 0 {
|
||||
bridge.Move(path.Join(remotePath, remoteEntriesShown[renameItem].Name), path.Join(remotePath, renameField.GetText()))
|
||||
browseRemote(remotePath)
|
||||
}
|
||||
|
||||
setMode(modeNormal)
|
||||
focusUpdated()
|
||||
app.Draw()
|
||||
}
|
||||
|
||||
func initTUI() error {
|
||||
devices, err := bridge.ListDevices()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to list devices: %s", err)
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
log.Fatal("failed to start adbfm: please connect to a device via ADB before launching")
|
||||
}
|
||||
|
||||
/*cview.Styles.TitleColor = tcell.ColorDefault
|
||||
cview.Styles.BorderColor = tcell.ColorDefault
|
||||
cview.Styles.PrimaryTextColor = tcell.ColorDefault
|
||||
cview.Styles.PrimitiveBackgroundColor = tcell.ColorDefault*/
|
||||
|
||||
app = cview.NewApplication().
|
||||
EnableMouse(true).
|
||||
SetInputCapture(inputConfig.Capture)
|
||||
|
||||
// Devices
|
||||
|
||||
devicesDropDown = cview.NewDropDown().SetLabel(" Device: ").SetLabelColor(tcell.ColorDefault)
|
||||
for _, device := range devices {
|
||||
devicesDropDown.AddOptions(cview.NewDropDownOption(device.Serial).SetSelectedFunc(setDeviceFunc(device)))
|
||||
}
|
||||
|
||||
// Local
|
||||
|
||||
localBuffer = cview.NewList().
|
||||
ShowSecondaryText(false).
|
||||
SetScrollBarVisibility(cview.ScrollBarAlways)
|
||||
localBuffer.SetTitleAlign(cview.AlignLeft).SetBorder(true)
|
||||
localBuffer.
|
||||
AddContextItem("Upload", 'k', func(index int) {
|
||||
// TODO Exists confirmation dialog
|
||||
|
||||
modalPopup.SetText(fmt.Sprintf("Uploading %s", localEntriesShown[index].Name))
|
||||
focusUpdated()
|
||||
app.ForceDraw()
|
||||
|
||||
f, err := os.Open(path.Join(localPath, localEntriesShown[index].Name))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to open local file: %s", err)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read local file: %s", err)
|
||||
}
|
||||
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to fetch info of local file: %s", err)
|
||||
}
|
||||
|
||||
err = bridge.Upload(localEntriesShown[index].Name, data, stat.ModTime())
|
||||
if err != nil {
|
||||
log.Fatalf("failed to upload file: %s", err)
|
||||
}
|
||||
|
||||
browseRemote(remotePath)
|
||||
|
||||
focusUpdated()
|
||||
app.Draw()
|
||||
}).
|
||||
AddContextItem("", 0, nil).
|
||||
AddContextItem("Cut", 'x', func(index int) {
|
||||
|
||||
}).
|
||||
AddContextItem("Copy", 'c', func(index int) {
|
||||
|
||||
}).
|
||||
AddContextItem("Paste", 'v', func(index int) {
|
||||
|
||||
}).
|
||||
AddContextItem("", 0, nil).
|
||||
AddContextItem("Delete", 'd', func(index int) {
|
||||
|
||||
}).
|
||||
AddContextItem("", 0, nil).
|
||||
AddContextItem("Rename", 'r', func(index int) {
|
||||
|
||||
}).
|
||||
AddContextItem("", 0, nil).
|
||||
AddContextItem("New folder", 'f', func(index int) {
|
||||
|
||||
})
|
||||
|
||||
localBuffer.SetSelectedFunc(func(i int, item *cview.ListItem) {
|
||||
localLock.Lock()
|
||||
|
||||
if i < 0 || i > len(localEntriesShown) {
|
||||
localLock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
if localEntriesShown[i].Mode&os.ModeDir != 0 {
|
||||
newPath := localEntriesShown[i].Name
|
||||
localLock.Unlock()
|
||||
|
||||
go browseLocal(path.Join(localPath, newPath))
|
||||
return
|
||||
}
|
||||
|
||||
align := cview.AlignCenter
|
||||
if runewidth.StringWidth(localEntriesShown[i].Name) > contextMenuTitleWidth {
|
||||
align = cview.AlignLeft
|
||||
}
|
||||
localBuffer.ContextMenuList().SetTitle(fmt.Sprintf("%s", localEntriesShown[i].Name)).SetTitleAlign(align)
|
||||
|
||||
localBuffer.ShowContextMenu(localBuffer.GetCurrentItemIndex(), -1, -1, func(primitive cview.Primitive) {
|
||||
app.SetFocus(primitive)
|
||||
})
|
||||
localLock.Unlock()
|
||||
})
|
||||
|
||||
// Remote
|
||||
|
||||
renameField = cview.NewInputField().SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEnter {
|
||||
acceptRename(0, "")
|
||||
} else {
|
||||
acceptRename(-1, "")
|
||||
}
|
||||
})
|
||||
|
||||
remoteBuffer = cview.NewList().
|
||||
ShowSecondaryText(false).
|
||||
SetScrollBarVisibility(cview.ScrollBarAlways)
|
||||
remoteBuffer.SetTitleAlign(cview.AlignLeft).SetBorder(true)
|
||||
remoteBuffer.
|
||||
AddContextItem("Download", 'j', func(index int) {
|
||||
// TODO Exists confirmation dialog
|
||||
|
||||
modalPopup.SetText(fmt.Sprintf("Downloading %s", remoteEntriesShown[index].Name)).SetDoneFunc(acceptRename)
|
||||
focusUpdated()
|
||||
app.ForceDraw()
|
||||
|
||||
data, err := bridge.Download(remoteEntriesShown[index].Name)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to download file: %s", err)
|
||||
}
|
||||
|
||||
file, err := os.Create(path.Join(localPath, remoteEntriesShown[index].Name))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to download file: failed to create local file: %s", err)
|
||||
}
|
||||
|
||||
file.Write(data)
|
||||
file.Close()
|
||||
|
||||
focusUpdated()
|
||||
app.Draw()
|
||||
}).
|
||||
AddContextItem("", 0, nil).
|
||||
AddContextItem("Cut", 'x', func(index int) {
|
||||
|
||||
}).
|
||||
AddContextItem("Copy", 'c', func(index int) {
|
||||
|
||||
}).
|
||||
AddContextItem("Paste", 'v', func(index int) {
|
||||
|
||||
}).
|
||||
AddContextItem("", 0, nil).
|
||||
AddContextItem("Delete", 'd', func(index int) {
|
||||
|
||||
}).
|
||||
AddContextItem("", 0, nil).
|
||||
AddContextItem("Rename", 'r', func(index int) {
|
||||
renameItem = index
|
||||
setMode(modeRename)
|
||||
|
||||
renameField.SetText(remoteEntriesShown[index].Name)
|
||||
|
||||
modalPopup.SetDoneFunc(acceptRename)
|
||||
|
||||
modalPopup.SetText(fmt.Sprintf("%s", remoteEntriesShown[index].Name)).GetForm().
|
||||
Clear(true).
|
||||
AddFormItem(renameField).
|
||||
AddButton("Rename", func() {
|
||||
acceptRename(0, "")
|
||||
})
|
||||
|
||||
focusUpdated()
|
||||
app.Draw()
|
||||
}).
|
||||
AddContextItem("", 0, nil).
|
||||
AddContextItem("New folder", 'f', func(index int) {
|
||||
|
||||
})
|
||||
|
||||
remoteBuffer.SetSelectedFunc(func(i int, item *cview.ListItem) {
|
||||
remoteLock.Lock()
|
||||
|
||||
if i < 0 || i > len(remoteEntriesShown) {
|
||||
remoteLock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
if remoteEntriesShown[i].Mode&os.ModeDir != 0 {
|
||||
newPath := remoteEntriesShown[i].Name
|
||||
remoteLock.Unlock()
|
||||
|
||||
go browseRemote(path.Join(remotePath, newPath))
|
||||
return
|
||||
}
|
||||
|
||||
align := cview.AlignCenter
|
||||
if runewidth.StringWidth(remoteEntriesShown[i].Name) > contextMenuTitleWidth {
|
||||
align = cview.AlignLeft
|
||||
}
|
||||
remoteBuffer.ContextMenuList().SetTitle(fmt.Sprintf("%s", remoteEntriesShown[i].Name)).SetTitleAlign(align)
|
||||
|
||||
remoteBuffer.ShowContextMenu(remoteBuffer.GetCurrentItemIndex(), -1, -1, func(primitive cview.Primitive) {
|
||||
app.SetFocus(primitive)
|
||||
})
|
||||
remoteLock.Unlock()
|
||||
})
|
||||
|
||||
// Select first device
|
||||
if len(devices) > 0 {
|
||||
devicesDropDown.SetCurrentOption(0)
|
||||
}
|
||||
|
||||
pad := cview.NewTextView()
|
||||
|
||||
appGrid = cview.NewGrid().
|
||||
SetRows(1, 1, -1).
|
||||
SetColumns(-1, -1).
|
||||
AddItem(devicesDropDown, 0, 0, 1, 2, 0, 0, true).
|
||||
AddItem(pad, 1, 0, 1, 2, 0, 0, true).
|
||||
AddItem(localBuffer, 2, 0, 1, 1, 0, 0, false).
|
||||
AddItem(remoteBuffer, 2, 1, 1, 1, 0, 0, false)
|
||||
|
||||
appContainer = cview.NewPages()
|
||||
modalPopup = cview.NewModal() // TODO center
|
||||
modalPopup.SetBackgroundColor(tcell.ColorBlack)
|
||||
|
||||
appContainer.AddPage("main", appGrid, true, true)
|
||||
appContainer.AddPage("modal", modalPopup, true, true)
|
||||
|
||||
app.SetRoot(appContainer, true).SetFocus(devicesDropDown)
|
||||
|
||||
focusUpdated()
|
||||
|
||||
// TODO No errors possible?
|
||||
return nil
|
||||
}
|
||||
|
||||
func focusUpdated() {
|
||||
if getMode() == modeRename {
|
||||
appContainer.ShowPage("modal")
|
||||
app.SetFocus(renameField)
|
||||
return
|
||||
}
|
||||
|
||||
appContainer.HidePage("modal")
|
||||
|
||||
switch currentFocus {
|
||||
case 0:
|
||||
app.SetFocus(devicesDropDown)
|
||||
case 1:
|
||||
app.SetFocus(localBuffer)
|
||||
case 2:
|
||||
app.SetFocus(remoteBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
func browseLocal(p string) {
|
||||
localLock.Lock()
|
||||
defer localLock.Unlock()
|
||||
|
||||
localEntries = nil
|
||||
localPath = p
|
||||
|
||||
var skippedFirst bool
|
||||
err := filepath.Walk(p, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !skippedFirst {
|
||||
skippedFirst = true
|
||||
return nil
|
||||
} else if path == "." || path == ".." {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO Store entries with more info
|
||||
localEntries = append(localEntries, &adb.DirEntry{Name: info.Name(), Mode: info.Mode(), Size: int32(info.Size()), ModifiedAt: info.ModTime()})
|
||||
|
||||
// Do not search recursively
|
||||
if info.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
localEntries = append(localEntries, &adb.DirEntry{Name: "..", Mode: os.ModeDir})
|
||||
|
||||
sort.Slice(localEntries, func(i, j int) bool {
|
||||
if localEntries[i].Name == ".." {
|
||||
return true
|
||||
} else if localEntries[i].Mode&os.ModeDir != localEntries[j].Mode&os.ModeDir {
|
||||
return localEntries[i].Mode&os.ModeDir != 0
|
||||
}
|
||||
|
||||
return strings.ToLower(localEntries[i].Name) < strings.ToLower(localEntries[j].Name)
|
||||
})
|
||||
|
||||
app.QueueUpdateDraw(func() {
|
||||
localLock.Lock()
|
||||
defer localLock.Unlock()
|
||||
|
||||
localBuffer.SetTitle(" " + p + " ")
|
||||
localBuffer.Clear()
|
||||
|
||||
localEntriesShown = nil
|
||||
for _, entry := range localEntries {
|
||||
if len(entry.Name) > 0 && entry.Name[0] == '.' && entry.Name != ".." && !showHidden {
|
||||
continue
|
||||
}
|
||||
|
||||
label := entry.Name
|
||||
if entry.Mode&os.ModeDir != 0 {
|
||||
label += "/"
|
||||
}
|
||||
|
||||
localBuffer.AddItem(cview.NewListItem(cview.Escape(label)))
|
||||
localEntriesShown = append(localEntriesShown, entry)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func browseRemote(p string) {
|
||||
remoteLock.Lock()
|
||||
defer remoteLock.Unlock()
|
||||
|
||||
previousSelection := -1
|
||||
previousOffset := -1
|
||||
if remotePath == p {
|
||||
previousSelection = remoteBuffer.GetCurrentItemIndex()
|
||||
previousOffset = remoteBuffer.GetOffset()
|
||||
}
|
||||
|
||||
oldPath := remotePath
|
||||
app.QueueUpdateDraw(func() {
|
||||
remoteBuffer.SetTitle(" " + oldPath + "... ")
|
||||
})
|
||||
remotePath = p
|
||||
|
||||
var err error
|
||||
remoteEntries, err = bridge.DirectoryEntries(p)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to list files: %s", err)
|
||||
}
|
||||
|
||||
go app.QueueUpdateDraw(func() {
|
||||
remoteLock.Lock()
|
||||
defer remoteLock.Unlock()
|
||||
|
||||
remoteBuffer.SetTitle(" " + p + " ")
|
||||
remoteBuffer.Clear()
|
||||
|
||||
remoteEntriesShown = nil
|
||||
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(cview.NewListItem(cview.Escape(label)))
|
||||
remoteEntriesShown = append(remoteEntriesShown, entry)
|
||||
}
|
||||
|
||||
if previousSelection >= 0 {
|
||||
remoteBuffer.SetCurrentItem(previousSelection)
|
||||
remoteBuffer.SetOffset(previousOffset)
|
||||
}
|
||||
})
|
||||
}
|
9
go.mod
9
go.mod
|
@ -1,12 +1,13 @@
|
|||
module gitlab.com/tslocum/adbfm
|
||||
|
||||
go 1.14
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/gdamore/tcell/v2 v2.0.0-dev.0.20200926152101-0fb77ddaa5b4
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201019142633-1057d5591ed1
|
||||
github.com/mattn/go-runewidth v0.0.9
|
||||
github.com/stretchr/testify v1.6.1 // indirect
|
||||
github.com/zach-klippenstein/goadb v0.0.0-20170530005145-029cc6bee481
|
||||
gitlab.com/tslocum/cbind v0.1.2
|
||||
gitlab.com/tslocum/cview v1.4.10-0.20201003000057-5a3409bfd6c0
|
||||
gitlab.com/tslocum/cbind v0.1.3
|
||||
gitlab.com/tslocum/cview v1.5.2-0.20201107170141-79a35fe4de6c
|
||||
golang.org/x/sys v0.0.0-20201109165425-215b40eba54c // indirect
|
||||
)
|
||||
|
|
25
go.sum
25
go.sum
|
@ -3,8 +3,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.0.0-dev/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
|
||||
github.com/gdamore/tcell/v2 v2.0.0-dev.0.20200926152101-0fb77ddaa5b4 h1:9WLVV5c2UI2qvgROlgzLgCuK5gi7igcU5LNsPXCSFB8=
|
||||
github.com/gdamore/tcell/v2 v2.0.0-dev.0.20200926152101-0fb77ddaa5b4/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201019142633-1057d5591ed1 h1:gp9ujdOQmQf1gMvqOYYgxdMS5tRpRGE3HAgRH4Hgzd4=
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201019142633-1057d5591ed1/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
|
@ -14,24 +14,25 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||
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=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/zach-klippenstein/goadb v0.0.0-20170530005145-029cc6bee481 h1:yVrbGOZZHihWLaa3xpaTKoO5FbYg/vG5hUufn429HAo=
|
||||
github.com/zach-klippenstein/goadb v0.0.0-20170530005145-029cc6bee481/go.mod h1:Drd+klC4FSDx0vKNEQDsSpWX5so04NA7l0vzHqkH8AQ=
|
||||
gitlab.com/tslocum/cbind v0.1.2 h1:ptDjO7WeOl1HglprsK18L8I9JeRkmtuBoBBaYw/6/Ow=
|
||||
gitlab.com/tslocum/cbind v0.1.2/go.mod h1:HfB7qAhHSZbn1rFK8M9SvSN5NG6ScAg/3h3iE6xdeeI=
|
||||
gitlab.com/tslocum/cview v1.4.10-0.20201003000057-5a3409bfd6c0 h1:zS1fXLRZN44JIt21KOjtN6d9lDj5JJwtSn3bbCEKdLs=
|
||||
gitlab.com/tslocum/cview v1.4.10-0.20201003000057-5a3409bfd6c0/go.mod h1:i9NyxtwBtkiVFrwmsh3Bv3dunvipjZrKX0TTdPHbzcw=
|
||||
gitlab.com/tslocum/cbind v0.1.3 h1:FT/fTQ4Yj3eo5021lB3IbkIt8eVtYGhrw/xur+cjvUU=
|
||||
gitlab.com/tslocum/cbind v0.1.3/go.mod h1:RvwYE3auSjBNlCmWeGspzn+jdLUVQ8C2QGC+0nP9ChI=
|
||||
gitlab.com/tslocum/cview v1.5.2-0.20201107170141-79a35fe4de6c h1:umKPjQ2bSmLWf0rpYWhjeZsl5IdhNGEq3041e+yq//U=
|
||||
gitlab.com/tslocum/cview v1.5.2-0.20201107170141-79a35fe4de6c/go.mod h1:BRtUi0zXzVXufhqFm/1GD7GL+iznKh5m9pEGN19SnKA=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201013132646-2da7054afaeb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7OwF73JPWsQLvH1z2Kxck=
|
||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201109165425-215b40eba54c h1:+B+zPA6081G5cEb2triOIJpcvSW4AYzmIyWAqMn2JAc=
|
||||
golang.org/x/sys v0.0.0-20201109165425-215b40eba54c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
|
||||
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
@ -0,0 +1,535 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/mattn/go-runewidth"
|
||||
adb "github.com/zach-klippenstein/goadb"
|
||||
"gitlab.com/tslocum/cbind"
|
||||
"gitlab.com/tslocum/cview"
|
||||
)
|
||||
|
||||
var (
|
||||
app *cview.Application
|
||||
|
||||
inputConfig = cbind.NewConfiguration()
|
||||
|
||||
appGrid *cview.Grid
|
||||
appContainer *cview.Panels
|
||||
modalPopup *cview.Modal
|
||||
devicesDropDown *cview.DropDown
|
||||
localBuffer *cview.List
|
||||
remoteBuffer *cview.List
|
||||
|
||||
renameField *cview.InputField
|
||||
renameItem int
|
||||
|
||||
newFolderField *cview.InputField
|
||||
newFolderItem int
|
||||
|
||||
currentFocus = 0
|
||||
currentMode = modeNormal
|
||||
)
|
||||
|
||||
const contextMenuTitleWidth = 16
|
||||
|
||||
const (
|
||||
modeNormal = 0
|
||||
modeRename = 1
|
||||
modeNewFolder = 2
|
||||
)
|
||||
|
||||
func getMode() int {
|
||||
return currentMode
|
||||
}
|
||||
|
||||
func setMode(mode int) {
|
||||
currentMode = mode
|
||||
|
||||
switch currentMode {
|
||||
case modeNormal:
|
||||
appContainer.HidePanel("modal")
|
||||
case modeRename, modeNewFolder:
|
||||
appContainer.ShowPanel("modal")
|
||||
}
|
||||
|
||||
focusUpdated()
|
||||
}
|
||||
|
||||
func acceptRename(buttonIndex int, buttonLabel string) {
|
||||
if buttonIndex >= 0 {
|
||||
err := bridge.move(path.Join(remotePath, remoteEntriesShown[renameItem].Name), path.Join(remotePath, renameField.GetText()))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
setMode(modeNormal)
|
||||
focusUpdated()
|
||||
|
||||
if buttonIndex >= 0 {
|
||||
go func() {
|
||||
browseRemote(remotePath)
|
||||
app.Draw()
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
app.Draw()
|
||||
}
|
||||
|
||||
func acceptNewFolder(buttonIndex int, buttonLabel string) {
|
||||
if buttonIndex >= 0 {
|
||||
err := bridge.newDirectory(path.Join(remotePath, newFolderField.GetText()))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
setMode(modeNormal)
|
||||
focusUpdated()
|
||||
|
||||
if buttonIndex >= 0 {
|
||||
go func() {
|
||||
browseRemote(remotePath)
|
||||
app.Draw()
|
||||
}()
|
||||
}
|
||||
|
||||
app.Draw()
|
||||
}
|
||||
|
||||
func initTUI() error {
|
||||
devices, err := bridge.listDevices()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to list devices: %s", err)
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
log.Fatal("failed to start adbfm: please connect to a device via ADB before launching")
|
||||
}
|
||||
|
||||
/*cview.Styles.TitleColor = tcell.ColorDefault
|
||||
cview.Styles.BorderColor = tcell.ColorDefault
|
||||
cview.Styles.PrimaryTextColor = tcell.ColorDefault
|
||||
cview.Styles.PrimitiveBackgroundColor = tcell.ColorDefault*/
|
||||
|
||||
app = cview.NewApplication()
|
||||
app.EnableMouse(true)
|
||||
app.SetInputCapture(inputConfig.Capture)
|
||||
|
||||
// Devices
|
||||
|
||||
devicesDropDown = cview.NewDropDown()
|
||||
devicesDropDown.SetLabel(" Device: ")
|
||||
devicesDropDown.SetLabelColor(tcell.ColorDefault)
|
||||
devicesDropDown.SetFieldTextColor(tcell.ColorWhite.TrueColor())
|
||||
devicesDropDown.SetFieldTextColorFocused(tcell.ColorBlack.TrueColor())
|
||||
devicesDropDown.SetFieldBackgroundColor(tcell.ColorBlack.TrueColor())
|
||||
devicesDropDown.SetFieldBackgroundColorFocused(tcell.ColorWhite.TrueColor())
|
||||
for _, device := range devices {
|
||||
option := cview.NewDropDownOption(device.Serial)
|
||||
option.SetSelectedFunc(setDeviceFunc(device))
|
||||
devicesDropDown.AddOptions(option)
|
||||
}
|
||||
|
||||
// Local
|
||||
|
||||
localBuffer = cview.NewList()
|
||||
localBuffer.ShowSecondaryText(false)
|
||||
localBuffer.SetScrollBarVisibility(cview.ScrollBarAlways)
|
||||
localBuffer.SetTitleAlign(cview.AlignLeft)
|
||||
localBuffer.SetBorder(true)
|
||||
localBuffer.AddContextItem("Upload", 'k', func(index int) {
|
||||
// TODO Exists confirmation dialog
|
||||
|
||||
modalPopup.SetText(fmt.Sprintf("Uploading %s", localEntriesShown[index].Name))
|
||||
focusUpdated()
|
||||
app.Draw()
|
||||
|
||||
f, err := os.Open(path.Join(localPath, localEntriesShown[index].Name))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to open local file: %s", err)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read local file: %s", err)
|
||||
}
|
||||
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to fetch info of local file: %s", err)
|
||||
}
|
||||
|
||||
err = bridge.upload(localEntriesShown[index].Name, data, stat.ModTime())
|
||||
if err != nil {
|
||||
log.Fatalf("failed to upload file: %s", err)
|
||||
}
|
||||
|
||||
browseRemote(remotePath)
|
||||
|
||||
focusUpdated()
|
||||
app.Draw()
|
||||
})
|
||||
localBuffer.AddContextItem("", 0, nil)
|
||||
localBuffer.AddContextItem("Cut", 'x', func(index int) {
|
||||
|
||||
})
|
||||
localBuffer.AddContextItem("Copy", 'c', func(index int) {
|
||||
|
||||
})
|
||||
localBuffer.AddContextItem("Paste", 'v', func(index int) {
|
||||
|
||||
})
|
||||
localBuffer.AddContextItem("", 0, nil)
|
||||
localBuffer.AddContextItem("Delete", 'd', func(index int) {
|
||||
|
||||
})
|
||||
localBuffer.AddContextItem("", 0, nil)
|
||||
localBuffer.AddContextItem("Rename", 'r', func(index int) {
|
||||
|
||||
})
|
||||
localBuffer.AddContextItem("", 0, nil)
|
||||
localBuffer.AddContextItem("New folder", 'f', func(index int) {
|
||||
|
||||
})
|
||||
|
||||
localBuffer.SetSelectedFunc(func(i int, item *cview.ListItem) {
|
||||
localLock.Lock()
|
||||
|
||||
if i < 0 || i > len(localEntriesShown) {
|
||||
localLock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
if localEntriesShown[i].Mode&os.ModeDir != 0 {
|
||||
newPath := localEntriesShown[i].Name
|
||||
localLock.Unlock()
|
||||
|
||||
go browseLocal(path.Join(localPath, newPath))
|
||||
return
|
||||
}
|
||||
|
||||
align := cview.AlignCenter
|
||||
if runewidth.StringWidth(localEntriesShown[i].Name) > contextMenuTitleWidth {
|
||||
align = cview.AlignLeft
|
||||
}
|
||||
localBuffer.ContextMenuList().SetTitle(fmt.Sprintf("%s", localEntriesShown[i].Name))
|
||||
localBuffer.ContextMenuList().SetTitleAlign(align)
|
||||
|
||||
localBuffer.ShowContextMenu(localBuffer.GetCurrentItemIndex(), -1, -1, func(primitive cview.Primitive) {
|
||||
app.SetFocus(primitive)
|
||||
})
|
||||
localLock.Unlock()
|
||||
})
|
||||
|
||||
// Remote
|
||||
|
||||
renameField = cview.NewInputField()
|
||||
renameField.SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEnter {
|
||||
acceptRename(0, "")
|
||||
} else {
|
||||
acceptRename(-1, "")
|
||||
}
|
||||
})
|
||||
|
||||
newFolderField = cview.NewInputField()
|
||||
newFolderField.SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEnter {
|
||||
acceptNewFolder(0, "")
|
||||
} else {
|
||||
acceptNewFolder(-1, "")
|
||||
}
|
||||
})
|
||||
|
||||
remoteBuffer = cview.NewList()
|
||||
remoteBuffer.ShowSecondaryText(false)
|
||||
remoteBuffer.SetScrollBarVisibility(cview.ScrollBarAlways)
|
||||
remoteBuffer.SetTitleAlign(cview.AlignLeft)
|
||||
remoteBuffer.SetBorder(true)
|
||||
remoteBuffer.AddContextItem("Download", 'j', func(index int) {
|
||||
// TODO Exists confirmation dialog
|
||||
|
||||
modalPopup.SetText(fmt.Sprintf("Downloading %s", remoteEntriesShown[index].Name))
|
||||
modalPopup.SetDoneFunc(acceptRename)
|
||||
focusUpdated()
|
||||
app.Draw()
|
||||
|
||||
data, err := bridge.download(remoteEntriesShown[index].Name)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to download file: %s", err)
|
||||
}
|
||||
|
||||
file, err := os.Create(path.Join(localPath, remoteEntriesShown[index].Name))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to download file: failed to create local file: %s", err)
|
||||
}
|
||||
|
||||
file.Write(data)
|
||||
file.Close()
|
||||
|
||||
focusUpdated()
|
||||
app.Draw()
|
||||
})
|
||||
remoteBuffer.AddContextItem("", 0, nil)
|
||||
remoteBuffer.AddContextItem("Cut", 'x', func(index int) {
|
||||
|
||||
})
|
||||
remoteBuffer.AddContextItem("Copy", 'c', func(index int) {
|
||||
|
||||
})
|
||||
remoteBuffer.AddContextItem("Paste", 'v', func(index int) {
|
||||
|
||||
})
|
||||
remoteBuffer.AddContextItem("", 0, nil)
|
||||
remoteBuffer.AddContextItem("Delete", 'd', func(index int) {
|
||||
|
||||
})
|
||||
remoteBuffer.AddContextItem("", 0, nil)
|
||||
remoteBuffer.AddContextItem("Rename", 'r', func(index int) {
|
||||
renameItem = index
|
||||
setMode(modeRename)
|
||||
|
||||
renameField.SetText(remoteEntriesShown[index].Name)
|
||||
|
||||
modalPopup.SetDoneFunc(acceptRename)
|
||||
|
||||
modalPopup.SetText(fmt.Sprintf("%s", remoteEntriesShown[index].Name))
|
||||
modalPopup.GetForm().Clear(true)
|
||||
modalPopup.GetForm().AddFormItem(renameField)
|
||||
modalPopup.GetForm().AddButton("Rename", func() {
|
||||
acceptRename(0, "")
|
||||
})
|
||||
|
||||
focusUpdated()
|
||||
app.Draw()
|
||||
})
|
||||
remoteBuffer.AddContextItem("", 0, nil)
|
||||
remoteBuffer.AddContextItem("New folder", 'f', func(index int) {
|
||||
newFolderItem = index
|
||||
setMode(modeNewFolder)
|
||||
|
||||
newFolderField.SetLabel("Folder name")
|
||||
|
||||
modalPopup.SetDoneFunc(acceptNewFolder)
|
||||
|
||||
modalPopup.SetText(fmt.Sprintf("%s", remoteEntriesShown[index].Name))
|
||||
modalPopup.GetForm().Clear(true)
|
||||
modalPopup.GetForm().AddFormItem(newFolderField)
|
||||
modalPopup.GetForm().AddButton("Create", func() {
|
||||
acceptNewFolder(0, "")
|
||||
})
|
||||
|
||||
focusUpdated()
|
||||
app.Draw()
|
||||
})
|
||||
|
||||
remoteBuffer.SetSelectedFunc(func(i int, item *cview.ListItem) {
|
||||
remoteLock.Lock()
|
||||
|
||||
if i < 0 || i > len(remoteEntriesShown) {
|
||||
remoteLock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
if remoteEntriesShown[i].Mode&os.ModeDir != 0 {
|
||||
newPath := remoteEntriesShown[i].Name
|
||||
remoteLock.Unlock()
|
||||
|
||||
go browseRemote(path.Join(remotePath, newPath))
|
||||
return
|
||||
}
|
||||
|
||||
align := cview.AlignCenter
|
||||
if runewidth.StringWidth(remoteEntriesShown[i].Name) > contextMenuTitleWidth {
|
||||
align = cview.AlignLeft
|
||||
}
|
||||
remoteBuffer.ContextMenuList().SetTitle(fmt.Sprintf("%s", remoteEntriesShown[i].Name))
|
||||
remoteBuffer.ContextMenuList().SetTitleAlign(align)
|
||||
|
||||
remoteBuffer.ShowContextMenu(remoteBuffer.GetCurrentItemIndex(), -1, -1, func(primitive cview.Primitive) {
|
||||
app.SetFocus(primitive)
|
||||
})
|
||||
remoteLock.Unlock()
|
||||
})
|
||||
|
||||
// Select first device
|
||||
if len(devices) > 0 {
|
||||
devicesDropDown.SetCurrentOption(0)
|
||||
}
|
||||
|
||||
pad := cview.NewTextView()
|
||||
|
||||
appGrid = cview.NewGrid()
|
||||
appGrid.SetRows(1, 1, -1)
|
||||
appGrid.SetColumns(-1, -1)
|
||||
appGrid.AddItem(devicesDropDown, 0, 0, 1, 2, 0, 0, true)
|
||||
appGrid.AddItem(pad, 1, 0, 1, 2, 0, 0, true)
|
||||
appGrid.AddItem(localBuffer, 2, 0, 1, 1, 0, 0, false)
|
||||
appGrid.AddItem(remoteBuffer, 2, 1, 1, 1, 0, 0, false)
|
||||
|
||||
appContainer = cview.NewPanels()
|
||||
modalPopup = cview.NewModal() // TODO center
|
||||
modalPopup.SetBackgroundColor(tcell.ColorBlack)
|
||||
|
||||
appContainer.AddPanel("main", appGrid, true, true)
|
||||
appContainer.AddPanel("modal", modalPopup, true, true)
|
||||
|
||||
app.SetRoot(appContainer, true)
|
||||
app.SetFocus(devicesDropDown)
|
||||
|
||||
currentMode = -1
|
||||
setMode(modeNormal)
|
||||
focusUpdated()
|
||||
|
||||
// TODO No errors possible?
|
||||
return nil
|
||||
}
|
||||
|
||||
func focusUpdated() {
|
||||
switch getMode() {
|
||||
case modeRename:
|
||||
app.SetFocus(renameField)
|
||||
return
|
||||
case modeNewFolder:
|
||||
app.SetFocus(newFolderField)
|
||||
return
|
||||
}
|
||||
|
||||
switch currentFocus {
|
||||
case 0:
|
||||
app.SetFocus(devicesDropDown)
|
||||
case 1:
|
||||
app.SetFocus(localBuffer)
|
||||
case 2:
|
||||
app.SetFocus(remoteBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
func browseLocal(p string) {
|
||||
localLock.Lock()
|
||||
defer localLock.Unlock()
|
||||
|
||||
localEntries = nil
|
||||
localPath = p
|
||||
|
||||
var skippedFirst bool
|
||||
err := filepath.Walk(p, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !skippedFirst {
|
||||
skippedFirst = true
|
||||
return nil
|
||||
} else if path == "." || path == ".." {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO Store entries with more info
|
||||
localEntries = append(localEntries, &adb.DirEntry{Name: info.Name(), Mode: info.Mode(), Size: int32(info.Size()), ModifiedAt: info.ModTime()})
|
||||
|
||||
// Do not search recursively
|
||||
if info.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
localEntries = append(localEntries, &adb.DirEntry{Name: "..", Mode: os.ModeDir})
|
||||
|
||||
sort.Slice(localEntries, func(i, j int) bool {
|
||||
if localEntries[i].Name == ".." {
|
||||
return true
|
||||
} else if localEntries[i].Mode&os.ModeDir != localEntries[j].Mode&os.ModeDir {
|
||||
return localEntries[i].Mode&os.ModeDir != 0
|
||||
}
|
||||
|
||||
return strings.ToLower(localEntries[i].Name) < strings.ToLower(localEntries[j].Name)
|
||||
})
|
||||
|
||||
app.QueueUpdateDraw(func() {
|
||||
localLock.Lock()
|
||||
defer localLock.Unlock()
|
||||
|
||||
localBuffer.SetTitle(" " + p + " ")
|
||||
localBuffer.Clear()
|
||||
|
||||
localEntriesShown = nil
|
||||
for _, entry := range localEntries {
|
||||
if len(entry.Name) > 0 && entry.Name[0] == '.' && entry.Name != ".." && !showHidden {
|
||||
continue
|
||||
}
|
||||
|
||||
label := entry.Name
|
||||
if entry.Mode&os.ModeDir != 0 {
|
||||
label += "/"
|
||||
}
|
||||
|
||||
localBuffer.AddItem(cview.NewListItem(cview.Escape(label)))
|
||||
localEntriesShown = append(localEntriesShown, entry)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func browseRemote(p string) {
|
||||
remoteLock.Lock()
|
||||
defer remoteLock.Unlock()
|
||||
|
||||
previousSelection := -1
|
||||
previousOffset := -1
|
||||
if remotePath == p {
|
||||
previousSelection = remoteBuffer.GetCurrentItemIndex()
|
||||
previousOffset, _ = remoteBuffer.GetOffset()
|
||||
}
|
||||
|
||||
oldPath := remotePath
|
||||
app.QueueUpdateDraw(func() {
|
||||
remoteBuffer.SetTitle(" " + oldPath + "... ")
|
||||
})
|
||||
remotePath = p
|
||||
|
||||
var err error
|
||||
remoteEntries, err = bridge.directoryEntries(p)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to list files: %s", err)
|
||||
}
|
||||
|
||||
go app.QueueUpdateDraw(func() {
|
||||
remoteLock.Lock()
|
||||
defer remoteLock.Unlock()
|
||||
|
||||
remoteBuffer.SetTitle(" " + p + " ")
|
||||
remoteBuffer.Clear()
|
||||
|
||||
remoteEntriesShown = nil
|
||||
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(cview.NewListItem(cview.Escape(label)))
|
||||
remoteEntriesShown = append(remoteEntriesShown, entry)
|
||||
}
|
||||
|
||||
if previousSelection >= 0 {
|
||||
remoteBuffer.SetCurrentItem(previousSelection)
|
||||
remoteBuffer.SetOffset(previousOffset, 0)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -29,7 +29,8 @@ func selectItem(ev *tcell.EventKey) *tcell.EventKey {
|
|||
if runewidth.StringWidth(localEntriesShown[currentItem].Name) > contextMenuTitleWidth {
|
||||
align = cview.AlignLeft
|
||||
}
|
||||
localBuffer.ContextMenuList().SetTitleAlign(align).SetTitle(localEntriesShown[currentItem].Name)
|
||||
localBuffer.ContextMenuList().SetTitleAlign(align)
|
||||
localBuffer.ContextMenuList().SetTitle(localEntriesShown[currentItem].Name)
|
||||
|
||||
localBuffer.ShowContextMenu(currentItem, -1, -1, func(primitive cview.Primitive) {
|
||||
app.SetFocus(primitive)
|
||||
|
@ -55,7 +56,8 @@ func selectItem(ev *tcell.EventKey) *tcell.EventKey {
|
|||
if runewidth.StringWidth(remoteEntriesShown[currentItem].Name) > contextMenuTitleWidth {
|
||||
align = cview.AlignLeft
|
||||
}
|
||||
remoteBuffer.ContextMenuList().SetTitleAlign(align).SetTitle(remoteEntriesShown[currentItem].Name)
|
||||
remoteBuffer.ContextMenuList().SetTitleAlign(align)
|
||||
remoteBuffer.ContextMenuList().SetTitle(remoteEntriesShown[currentItem].Name)
|
||||
|
||||
remoteBuffer.ShowContextMenu(currentItem, -1, -1, func(primitive cview.Primitive) {
|
||||
app.SetFocus(primitive)
|
|
@ -12,7 +12,6 @@ import (
|
|||
"github.com/gdamore/tcell/v2"
|
||||
adb "github.com/zach-klippenstein/goadb"
|
||||
goadb "github.com/zach-klippenstein/goadb"
|
||||
"gitlab.com/tslocum/adbfm/pkg/android"
|
||||
"gitlab.com/tslocum/cbind"
|
||||
"gitlab.com/tslocum/cview"
|
||||
)
|
||||
|
@ -39,7 +38,7 @@ var (
|
|||
localLock sync.Mutex
|
||||
remoteLock sync.Mutex
|
||||
|
||||
bridge *android.Bridge
|
||||
bridge *adbConn
|
||||
|
||||
done = make(chan bool)
|
||||
)
|
||||
|
@ -100,7 +99,7 @@ func setDeviceFunc(device *goadb.DeviceInfo) func(index int, option *cview.DropD
|
|||
localLock.Lock()
|
||||
remoteLock.Lock()
|
||||
|
||||
err := bridge.SetDevice(device)
|
||||
err := bridge.setDevice(device)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -144,7 +143,7 @@ func run() error {
|
|||
}
|
||||
|
||||
var err error
|
||||
bridge, err = android.NewBridge("", 0, "")
|
||||
bridge, err = connect("", 0, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to ADB server: %s", err)
|
||||
}
|
Loading…
Reference in New Issue