You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
795 lines
18 KiB
795 lines
18 KiB
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) |
|
} |
|
}) |
|
}
|
|
|