796 lines
18 KiB
Go
796 lines
18 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"code.rocketnine.space/tslocum/cbind"
|
|
"code.rocketnine.space/tslocum/cview"
|
|
"github.com/gdamore/tcell/v2"
|
|
"github.com/mattn/go-runewidth"
|
|
adb "github.com/zach-klippenstein/goadb"
|
|
)
|
|
|
|
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
|
|
renameRemote bool
|
|
renameItem int
|
|
|
|
deleteRemote bool
|
|
deleteItem int
|
|
|
|
newFolderField *cview.InputField
|
|
newFolderRemote bool
|
|
newFolderItem int
|
|
|
|
currentFocus = 0
|
|
currentMode = modeNormal
|
|
|
|
clipboardRemote bool
|
|
clipboardPath string
|
|
clipboardCut bool
|
|
)
|
|
|
|
const contextMenuTitleWidth = 16
|
|
|
|
const (
|
|
modeNormal = 0
|
|
modeWait = 1
|
|
modeRename = 2
|
|
modeDelete = 3
|
|
modeNewFolder = 4
|
|
)
|
|
|
|
func getMode() int {
|
|
return currentMode
|
|
}
|
|
|
|
func setMode(mode int) {
|
|
currentMode = mode
|
|
|
|
switch currentMode {
|
|
case modeNormal:
|
|
appContainer.HidePanel("modal")
|
|
case modeWait, modeRename, modeDelete, modeNewFolder:
|
|
appContainer.ShowPanel("modal")
|
|
}
|
|
|
|
focusUpdated()
|
|
}
|
|
|
|
func acceptRename(buttonIndex int, buttonLabel string) {
|
|
renameText := renameField.GetText()
|
|
if buttonIndex >= 0 && renameText != "" {
|
|
if renameRemote {
|
|
err := bridge.move(path.Join(remotePath, remoteEntriesShown[renameItem].Name), path.Join(remotePath, renameText))
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
} else {
|
|
err := os.Rename(path.Join(localPath, localEntriesShown[renameItem].Name), path.Join(localPath, renameText))
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
setMode(modeNormal)
|
|
|
|
if buttonIndex >= 0 {
|
|
go func() {
|
|
if renameRemote {
|
|
browseRemote(remotePath)
|
|
} else {
|
|
browseLocal(localPath)
|
|
}
|
|
focusUpdated()
|
|
app.Draw()
|
|
}()
|
|
return
|
|
}
|
|
|
|
app.Draw()
|
|
}
|
|
|
|
func acceptNewFolder(buttonIndex int, buttonLabel string) {
|
|
folderName := newFolderField.GetText()
|
|
if buttonIndex >= 0 && folderName != "" {
|
|
if newFolderRemote {
|
|
err := bridge.newDirectory(path.Join(remotePath, folderName))
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
} else {
|
|
err := os.Mkdir(path.Join(localPath, folderName), 0655)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
setMode(modeNormal)
|
|
|
|
if buttonIndex >= 0 {
|
|
go func() {
|
|
if newFolderRemote {
|
|
browseRemote(remotePath)
|
|
} else {
|
|
browseLocal(localPath)
|
|
}
|
|
focusUpdated()
|
|
app.Draw()
|
|
}()
|
|
}
|
|
|
|
app.Draw()
|
|
}
|
|
|
|
func cancelDelete() {
|
|
setMode(modeNormal)
|
|
}
|
|
|
|
func confirmDelete() {
|
|
if deleteRemote {
|
|
err := bridge.delete(path.Join(remotePath, remoteEntriesShown[deleteItem].Name))
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
} else {
|
|
err := os.RemoveAll(path.Join(localPath, localEntriesShown[deleteItem].Name))
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
setMode(modeNormal)
|
|
|
|
if deleteRemote {
|
|
browseRemote(remotePath)
|
|
} else {
|
|
browseLocal(localPath)
|
|
}
|
|
focusUpdated()
|
|
app.Draw()
|
|
}
|
|
|
|
func previousField(ev *tcell.EventKey) *tcell.EventKey {
|
|
if currentMode == modeNormal && currentFocus > 0 {
|
|
currentFocus--
|
|
focusUpdated()
|
|
return nil
|
|
}
|
|
return ev
|
|
}
|
|
|
|
func nextField(ev *tcell.EventKey) *tcell.EventKey {
|
|
if currentMode == modeNormal && currentFocus < 2 {
|
|
currentFocus++
|
|
focusUpdated()
|
|
return nil
|
|
}
|
|
return ev
|
|
}
|
|
|
|
func exit(_ *tcell.EventKey) *tcell.EventKey {
|
|
return nil
|
|
}
|
|
|
|
func upload(filePath string) {
|
|
f, err := os.Open(filePath)
|
|
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(path.Base(filePath), data, stat.ModTime())
|
|
if err != nil {
|
|
log.Fatalf("failed to upload file: %s", err)
|
|
}
|
|
}
|
|
|
|
func renameFunc(remote bool) func(index int) {
|
|
return func(index int) {
|
|
entries := localEntriesShown
|
|
if remote {
|
|
entries = remoteEntriesShown
|
|
}
|
|
|
|
if entries[index].Name == ".." {
|
|
return
|
|
}
|
|
|
|
renameField.SetText(entries[index].Name)
|
|
|
|
modalPopup.SetDoneFunc(acceptRename)
|
|
|
|
modalPopup.SetText(fmt.Sprintf("%s", entries[index].Name))
|
|
modalPopup.GetForm().Clear(true)
|
|
modalPopup.GetForm().AddFormItem(renameField)
|
|
modalPopup.GetForm().AddButton("Rename", func() {
|
|
acceptRename(0, "")
|
|
})
|
|
|
|
renameRemote = remote
|
|
renameItem = index
|
|
setMode(modeRename)
|
|
|
|
app.Draw()
|
|
}
|
|
}
|
|
|
|
func deleteFunc(remote bool) func(index int) {
|
|
return func(index int) {
|
|
entries := localEntriesShown
|
|
if remote {
|
|
entries = remoteEntriesShown
|
|
}
|
|
|
|
if entries[index].Name == ".." {
|
|
return
|
|
}
|
|
|
|
if entries[index].Mode&os.ModeDir != 0 {
|
|
modalPopup.SetText("Are you sure you want to delete this directory?\n\n" + entries[index].Name)
|
|
} else {
|
|
modalPopup.SetText("Are you sure you want to delete this file?\n\n" + entries[index].Name)
|
|
}
|
|
modalPopup.GetForm().Clear(true)
|
|
modalPopup.GetForm().AddButton("No", cancelDelete)
|
|
modalPopup.GetForm().AddButton("Yes", confirmDelete)
|
|
|
|
deleteRemote = remote
|
|
deleteItem = index
|
|
setMode(modeDelete)
|
|
}
|
|
}
|
|
|
|
func newFolderFunc(remote bool) func(index int) {
|
|
return func(index int) {
|
|
newFolderField.SetLabel("Folder name")
|
|
|
|
modalPopup.SetDoneFunc(acceptNewFolder)
|
|
|
|
modalPopup.SetText("Create new folder")
|
|
modalPopup.GetForm().Clear(true)
|
|
modalPopup.GetForm().AddFormItem(newFolderField)
|
|
modalPopup.GetForm().AddButton("Create", func() {
|
|
acceptNewFolder(0, "")
|
|
})
|
|
|
|
newFolderRemote = remote
|
|
newFolderItem = index
|
|
setMode(modeNewFolder)
|
|
|
|
app.Draw()
|
|
}
|
|
}
|
|
|
|
func initTUI() {
|
|
devices, err := bridge.listDevices()
|
|
if err != nil {
|
|
log.Fatalf("failed to list devices: %s", err)
|
|
}
|
|
|
|
if len(devices) == 0 {
|
|
if connectAddress != "" {
|
|
log.Fatalf("failed to connect to device %s", connectAddress)
|
|
}
|
|
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) {
|
|
if localEntriesShown[index].Name == ".." {
|
|
return
|
|
}
|
|
|
|
// TODO Exists confirmation dialog
|
|
|
|
modalPopup.SetText(fmt.Sprintf("Uploading %s", localEntriesShown[index].Name))
|
|
focusUpdated()
|
|
app.Draw()
|
|
|
|
upload(path.Join(localPath, localEntriesShown[index].Name))
|
|
|
|
browseRemote(remotePath)
|
|
|
|
focusUpdated()
|
|
app.Draw()
|
|
})
|
|
localBuffer.AddContextItem("", 0, nil)
|
|
localBuffer.AddContextItem("Cut", 'x', func(index int) {
|
|
if localEntriesShown[index].Name == ".." {
|
|
return
|
|
}
|
|
|
|
clipboardRemote = false
|
|
clipboardCut = true
|
|
clipboardPath = path.Join(localPath, localEntriesShown[index].Name)
|
|
|
|
focusUpdated()
|
|
})
|
|
localBuffer.AddContextItem("Copy", 'c', func(index int) {
|
|
if localEntriesShown[index].Name == ".." {
|
|
return
|
|
}
|
|
|
|
clipboardRemote = false
|
|
clipboardCut = false
|
|
clipboardPath = path.Join(localPath, localEntriesShown[index].Name)
|
|
|
|
focusUpdated()
|
|
})
|
|
localBuffer.AddContextItem("Paste", 'v', func(index int) {
|
|
// TODO Exists confirmation dialog
|
|
if clipboardPath == "" {
|
|
return
|
|
}
|
|
|
|
modalPopup.SetText(fmt.Sprintf("Pasting %s", path.Base(clipboardPath)))
|
|
modalPopup.SetDoneFunc(nil)
|
|
|
|
setMode(modeWait)
|
|
app.Draw()
|
|
|
|
if clipboardRemote {
|
|
data, err := bridge.download(clipboardPath)
|
|
if err != nil {
|
|
log.Fatalf("failed to paste file: %s", err)
|
|
}
|
|
|
|
file, err := os.Create(path.Join(localPath, filepath.Base(clipboardPath)))
|
|
if err != nil {
|
|
log.Fatalf("failed to paste file: failed to create local file: %s", err)
|
|
}
|
|
|
|
file.Write(data)
|
|
file.Close()
|
|
} else {
|
|
// TODO warn exists
|
|
err = copyRecursive(clipboardPath, path.Join(localPath, filepath.Base(clipboardPath)))
|
|
if err != nil {
|
|
log.Fatalf("failed to paste file: %s", err)
|
|
}
|
|
|
|
if clipboardCut {
|
|
os.RemoveAll(clipboardPath)
|
|
}
|
|
}
|
|
|
|
clipboardPath = ""
|
|
|
|
if clipboardCut {
|
|
browseLocal(localPath)
|
|
}
|
|
browseRemote(remotePath)
|
|
|
|
setMode(modeNormal)
|
|
app.Draw()
|
|
})
|
|
localBuffer.AddContextItem("", 0, nil)
|
|
localBuffer.AddContextItem("Delete", 'd', deleteFunc(false))
|
|
localBuffer.AddContextItem("", 0, nil)
|
|
localBuffer.AddContextItem("Rename", 'r', renameFunc(false))
|
|
localBuffer.AddContextItem("", 0, nil)
|
|
localBuffer.AddContextItem("New folder", 'f', newFolderFunc(false))
|
|
|
|
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
|
|
if remoteEntriesShown[index].Name == ".." {
|
|
return
|
|
}
|
|
|
|
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) {
|
|
if remoteEntriesShown[index].Name == ".." {
|
|
return
|
|
}
|
|
|
|
clipboardRemote = true
|
|
clipboardCut = true
|
|
clipboardPath = path.Join(remotePath, remoteEntriesShown[index].Name)
|
|
|
|
focusUpdated()
|
|
})
|
|
remoteBuffer.AddContextItem("Copy", 'c', func(index int) {
|
|
if remoteEntriesShown[index].Name == ".." {
|
|
return
|
|
}
|
|
|
|
clipboardRemote = true
|
|
clipboardCut = false
|
|
clipboardPath = path.Join(remotePath, remoteEntriesShown[index].Name)
|
|
|
|
focusUpdated()
|
|
|
|
})
|
|
remoteBuffer.AddContextItem("Paste", 'v', func(index int) {
|
|
// TODO Exists confirmation dialog
|
|
if clipboardPath == "" {
|
|
return
|
|
}
|
|
|
|
modalPopup.SetText(fmt.Sprintf("Pasting %s", path.Base(clipboardPath)))
|
|
modalPopup.SetDoneFunc(nil)
|
|
|
|
setMode(modeWait)
|
|
app.Draw()
|
|
|
|
if clipboardRemote {
|
|
if clipboardCut {
|
|
err = bridge.move(clipboardPath, path.Join(remotePath, path.Base(clipboardPath)))
|
|
if err != nil {
|
|
log.Fatalf("failed to paste file: %s", err)
|
|
}
|
|
} else {
|
|
bridge.copy(clipboardPath, path.Join(remotePath, path.Base(clipboardPath)))
|
|
if err != nil {
|
|
log.Fatalf("failed to paste file: %s", err)
|
|
}
|
|
}
|
|
} else {
|
|
// TODO recursively upload dir, check exists
|
|
fi, err := os.Stat(clipboardPath)
|
|
if err != nil {
|
|
log.Fatalf("failed to paste file: failed to read cut/copied file or directory: %s", err)
|
|
} else if fi.Mode()&os.ModeDir != 0 {
|
|
log.Fatal("copying directories not yet implemented")
|
|
}
|
|
|
|
upload(clipboardPath)
|
|
|
|
if clipboardCut {
|
|
os.RemoveAll(clipboardPath)
|
|
}
|
|
}
|
|
|
|
clipboardPath = ""
|
|
|
|
if clipboardCut {
|
|
browseLocal(localPath)
|
|
}
|
|
browseRemote(remotePath)
|
|
|
|
setMode(modeNormal)
|
|
app.Draw()
|
|
})
|
|
remoteBuffer.AddContextItem("", 0, nil)
|
|
remoteBuffer.AddContextItem("Delete", 'd', deleteFunc(true))
|
|
remoteBuffer.AddContextItem("", 0, nil)
|
|
remoteBuffer.AddContextItem("Rename", 'r', renameFunc(true))
|
|
remoteBuffer.AddContextItem("", 0, nil)
|
|
remoteBuffer.AddContextItem("New folder", 'f', newFolderFunc(true))
|
|
|
|
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()
|
|
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)
|
|
}
|
|
|
|
func focusUpdated() {
|
|
emptyClipboard := clipboardPath == ""
|
|
localBuffer.ContextMenuList().SetItemEnabled(4, !emptyClipboard)
|
|
remoteBuffer.ContextMenuList().SetItemEnabled(4, !emptyClipboard)
|
|
|
|
switch getMode() {
|
|
case modeRename:
|
|
app.SetFocus(renameField)
|
|
return
|
|
case modeNewFolder:
|
|
app.SetFocus(newFolderField)
|
|
return
|
|
case modeDelete:
|
|
app.SetFocus(modalPopup)
|
|
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
|
|
}
|
|
|
|
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)
|
|
}
|
|
})
|
|
}
|