Support renaming remote files

This commit is contained in:
Trevor Slocum 2020-10-02 17:30:24 -07:00
parent 5ff1d4b36b
commit 03675d41ac
6 changed files with 143 additions and 38 deletions

View File

@ -9,8 +9,10 @@ import (
"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"
@ -28,11 +30,46 @@ var (
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 {
@ -56,7 +93,7 @@ func initTUI() error {
devicesDropDown = cview.NewDropDown().SetLabel(" Device: ").SetLabelColor(tcell.ColorDefault)
for _, device := range devices {
devicesDropDown.AddOption(device.Serial, setDeviceFunc(device))
devicesDropDown.AddOptions(cview.NewDropDownOption(device.Serial).SetSelectedFunc(setDeviceFunc(device)))
}
// Local
@ -66,11 +103,10 @@ func initTUI() error {
SetScrollBarVisibility(cview.ScrollBarAlways)
localBuffer.SetTitleAlign(cview.AlignLeft).SetBorder(true)
localBuffer.
AddContextItem("Upload", 'z', func(index int) {
AddContextItem("Upload", 'k', func(index int) {
// TODO Exists confirmation dialog
modalPopup.SetText(fmt.Sprintf("Uploading %s", localEntriesShown[index].Name))
app.SetRoot(appContainer, true)
focusUpdated()
app.ForceDraw()
@ -96,8 +132,8 @@ func initTUI() error {
browseRemote(remotePath)
app.SetRoot(appGrid, true)
focusUpdated()
app.Draw()
}).
AddContextItem("", 0, nil).
AddContextItem("Cut", 'x', func(index int) {
@ -112,13 +148,17 @@ func initTUI() error {
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, s string, s2 string, r rune) {
localBuffer.SetSelectedFunc(func(i int, item *cview.ListItem) {
localLock.Lock()
if i < 0 || i > len(localEntriesShown) {
@ -134,7 +174,13 @@ func initTUI() error {
return
}
localBuffer.ShowContextMenu(localBuffer.GetCurrentItem(), -1, -1, func(primitive cview.Primitive) {
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()
@ -142,16 +188,23 @@ func initTUI() error {
// 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", 'z', func(index int) {
AddContextItem("Download", 'j', func(index int) {
// TODO Exists confirmation dialog
modalPopup.SetText(fmt.Sprintf("Downloading %s", remoteEntriesShown[index].Name))
app.SetRoot(appContainer, true)
modalPopup.SetText(fmt.Sprintf("Downloading %s", remoteEntriesShown[index].Name)).SetDoneFunc(acceptRename)
focusUpdated()
app.ForceDraw()
@ -168,8 +221,8 @@ func initTUI() error {
file.Write(data)
file.Close()
app.SetRoot(appGrid, true)
focusUpdated()
app.Draw()
}).
AddContextItem("", 0, nil).
AddContextItem("Cut", 'x', func(index int) {
@ -184,13 +237,32 @@ func initTUI() error {
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, s string, s2 string, r rune) {
remoteBuffer.SetSelectedFunc(func(i int, item *cview.ListItem) {
remoteLock.Lock()
if i < 0 || i > len(remoteEntriesShown) {
@ -206,7 +278,13 @@ func initTUI() error {
return
}
remoteBuffer.ShowContextMenu(remoteBuffer.GetCurrentItem(), -1, -1, func(primitive cview.Primitive) {
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()
@ -234,13 +312,23 @@ func initTUI() error {
appContainer.AddPage("main", appGrid, true, true)
appContainer.AddPage("modal", modalPopup, true, true)
app.SetRoot(appGrid, true).SetFocus(devicesDropDown)
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)
@ -311,7 +399,7 @@ func browseLocal(p string) {
label += "/"
}
localBuffer.AddItem(label, "", 0, nil)
localBuffer.AddItem(cview.NewListItem(cview.Escape(label)))
localEntriesShown = append(localEntriesShown, entry)
}
})
@ -324,7 +412,7 @@ func browseRemote(p string) {
previousSelection := -1
previousOffset := -1
if remotePath == p {
previousSelection = remoteBuffer.GetCurrentItem()
previousSelection = remoteBuffer.GetCurrentItemIndex()
previousOffset = remoteBuffer.GetOffset()
}
@ -358,7 +446,7 @@ func browseRemote(p string) {
label += "/"
}
remoteBuffer.AddItem(label, "", 0, nil)
remoteBuffer.AddItem(cview.NewListItem(cview.Escape(label)))
remoteEntriesShown = append(remoteEntriesShown, entry)
}

View File

@ -17,7 +17,7 @@ func selectItem(ev *tcell.EventKey) *tcell.EventKey {
defer localLock.Unlock()
if localBuffer.GetItemCount() > 0 {
currentItem := localBuffer.GetCurrentItem()
currentItem := localBuffer.GetCurrentItemIndex()
if currentItem >= 0 && currentItem < len(localEntriesShown) {
newPath := localEntriesShown[currentItem].Name
if localEntriesShown[currentItem].Mode&os.ModeDir != 0 {
@ -43,7 +43,7 @@ func selectItem(ev *tcell.EventKey) *tcell.EventKey {
defer remoteLock.Unlock()
if remoteBuffer.GetItemCount() > 0 {
currentItem := remoteBuffer.GetCurrentItem()
currentItem := remoteBuffer.GetCurrentItemIndex()
if currentItem >= 0 && currentItem < len(remoteEntriesShown) {
newPath := remoteEntriesShown[currentItem].Name
if remoteEntriesShown[currentItem].Mode&os.ModeDir != 0 {

View File

@ -14,6 +14,7 @@ import (
goadb "github.com/zach-klippenstein/goadb"
"gitlab.com/tslocum/adbfm/pkg/android"
"gitlab.com/tslocum/cbind"
"gitlab.com/tslocum/cview"
)
const defaultRemotePath = "/storage/emulated/0"
@ -93,8 +94,8 @@ func setDefaultKeyBinds() {
}
}
func setDeviceFunc(device *goadb.DeviceInfo) func() {
return func() {
func setDeviceFunc(device *goadb.DeviceInfo) func(index int, option *cview.DropDownOption) {
return func(index int, option *cview.DropDownOption) {
go func() {
localLock.Lock()
remoteLock.Lock()

4
go.mod
View File

@ -3,10 +3,10 @@ module gitlab.com/tslocum/adbfm
go 1.14
require (
github.com/gdamore/tcell/v2 v2.0.0-dev.0.20200908121250-0c5e1e1720f1
github.com/gdamore/tcell/v2 v2.0.0-dev.0.20200926152101-0fb77ddaa5b4
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.9
gitlab.com/tslocum/cview v1.4.10-0.20201003000057-5a3409bfd6c0
)

15
go.sum
View File

@ -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.20200908121250-0c5e1e1720f1 h1:ec/DAe6ms4fBkpSHObVDYU4N/w6Swd929zkN01g8ozY=
github.com/gdamore/tcell/v2 v2.0.0-dev.0.20200908121250-0c5e1e1720f1/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/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,23 +14,24 @@ 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-0.20200826214515-b5f2c6a8711a/go.mod h1:HfB7qAhHSZbn1rFK8M9SvSN5NG6ScAg/3h3iE6xdeeI=
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.9 h1:JXTXwqK3oC7WOQjCvs+NFq1WNUAyWOe0tqkj/tTVMSM=
gitlab.com/tslocum/cview v1.4.9/go.mod h1:XWb+3I8x6aCVBa658HYVXb2wxsa9TK6TBc81W9pX/ss=
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=
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-20200908134130-d2e65c121b96 h1:gJciq3lOg0eS9fSZJcoHfv7q1BfC6cJfnmSSKL1yu3Q=
golang.org/x/sys v0.0.0-20200908134130-d2e65c121b96/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/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/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=

View File

@ -29,8 +29,8 @@ type Bridge struct {
// 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{
func NewBridge(host string, port int, pathToADB string) (bridge *Bridge, err error) {
adbBridge, err := adb.NewWithConfig(adb.ServerConfig{
PathToAdb: pathToADB,
Host: host,
Port: port,
@ -39,15 +39,14 @@ func NewBridge(host string, port int, pathToADB string) (*Bridge, error) {
log.Fatal(err)
}
bridge.StartServer() // Ignore error
adbBridge.StartServer() // Ignore error
_, err = bridge.ServerVersion()
_, err = adbBridge.ServerVersion()
if err != nil {
return nil, fmt.Errorf("failed to get server version: %s", err)
}
a := Bridge{bridge: bridge}
return &a, nil
return &Bridge{bridge: adbBridge}, nil
}
func (a *Bridge) ListDevices() (devices []*adb.DeviceInfo, err error) {
@ -83,7 +82,7 @@ func (a *Bridge) setDevice(deviceInfo *adb.DeviceInfo) (err error) {
return nil
}
func (a *Bridge) Upload(fileName string, data []byte, mtime time.Time) error {
func (a *Bridge) Upload(fileName string, data []byte, mtime time.Time) (err error) {
a.Lock()
defer a.Unlock()
@ -103,7 +102,7 @@ func (a *Bridge) Upload(fileName string, data []byte, mtime time.Time) error {
return nil
}
func (a *Bridge) Download(fileName string) ([]byte, error) {
func (a *Bridge) Download(fileName string) (data []byte, err error) {
a.Lock()
defer a.Unlock()
@ -113,7 +112,7 @@ func (a *Bridge) Download(fileName string) ([]byte, error) {
}
defer reader.Close()
data, err := ioutil.ReadAll(reader)
data, err = ioutil.ReadAll(reader)
if err != nil {
return nil, err
}
@ -160,3 +159,19 @@ func (a *Bridge) DirectoryEntries(dir string) (entries []*adb.DirEntry, err erro
return entries, nil
}
func (a *Bridge) Move(old string, new string) (err error) {
a.Lock()
defer a.Unlock()
if a.device == nil {
return errors.New("no device")
}
_, err = a.device.RunCommand("mv", old, new)
if err != nil {
return fmt.Errorf("failed to move file: %s", err)
}
return nil
}