ADB file manager
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.

194 lines
3.7 KiB

package main
import (
"errors"
"flag"
"fmt"
"log"
"net"
"os"
"strconv"
"strings"
"sync"
"code.rocketnine.space/tslocum/cbind"
"code.rocketnine.space/tslocum/cview"
"github.com/gdamore/tcell/v2"
adb "github.com/zach-klippenstein/goadb"
)
const defaultRemotePath = "/storage/emulated/0"
type appConfig struct {
Input map[string][]string // Keybinds
}
var config = &appConfig{}
var (
showHidden bool
localPath string
remotePath string
connectAddress string
localEntries []*adb.DirEntry
localEntriesShown []*adb.DirEntry
remoteEntries []*adb.DirEntry
remoteEntriesShown []*adb.DirEntry
localLock sync.Mutex
remoteLock sync.Mutex
bridge *adbConn
done = make(chan bool)
)
const (
actionPreviousField = "previous-field"
actionNextField = "next-field"
actionExit = "exit"
)
var actionHandlers = map[string]func(*tcell.EventKey) *tcell.EventKey{
actionPreviousField: previousField,
actionNextField: nextField,
actionExit: exit,
}
func setKeyBinds() error {
if len(config.Input) == 0 {
setDefaultKeyBinds()
}
for a, keys := range config.Input {
a = strings.ToLower(a)
handler := actionHandlers[a]
if handler == nil {
return fmt.Errorf("failed to set keybind for %s: unknown action", a)
}
for _, k := range keys {
mod, key, ch, err := cbind.Decode(k)
if err != nil {
return fmt.Errorf("failed to set keybind %s for %s: %s", k, a, err)
}
if key == tcell.KeyRune {
inputConfig.SetRune(mod, ch, handler)
} else {
inputConfig.SetKey(mod, key, handler)
}
}
}
return nil
}
func setDefaultKeyBinds() {
config.Input = map[string][]string{
actionPreviousField: {"Backtab"},
actionNextField: {"Tab"},
actionExit: {"Alt+q"},
}
}
func setDeviceFunc(device *adb.DeviceInfo) func(index int, option *cview.DropDownOption) {
return func(index int, option *cview.DropDownOption) {
go func() {
localLock.Lock()
remoteLock.Lock()
err := bridge.setDevice(device)
if err != nil {
log.Fatal(err)
}
remoteLock.Unlock()
localLock.Unlock()
browseRemote(remotePath)
}()
}
}
func main() {
log.SetFlags(0)
log.SetPrefix("")
flag.StringVar(&localPath, "local", "", "local starting directory")
flag.StringVar(&remotePath, "remote", "", "remote starting directory")
flag.BoolVar(&showHidden, "hidden", false, "show hidden files and folders")
flag.StringVar(&connectAddress, "connect", "", "connect to device over network")
flag.Parse()
err := run()
if err != nil {
log.Fatalf("failed to start adbfm: %s", err)
}
}
func run() error {
if localPath == "" {
homeDir, err := os.UserHomeDir()
if err == nil && homeDir != "" {
localPath = homeDir
}
}
if localPath == "" {
return errors.New("local path must be specified with --local")
}
if remotePath == "" {
remotePath = defaultRemotePath
}
var err error
bridge, err = connectBridge("", 0, "")
if err != nil {
return fmt.Errorf("failed to connect to ADB server: %s", err)
}
if connectAddress != "" {
if !strings.ContainsRune(connectAddress, ':') {
connectAddress += ":5555"
}
host, p, err := net.SplitHostPort(connectAddress)
if err != nil {
log.Fatalf("invalid device address %s: %s", connectAddress, err)
}
port, err := strconv.Atoi(p)
if err != nil {
log.Fatalf("invalid device address %s: %s", connectAddress, err)
}
err = bridge.connectDevice(host, port)
if err != nil {
log.Fatalf("failed to connect to device %s: %s", connectAddress, err)
}
}
err = setKeyBinds()
if err != nil {
return fmt.Errorf("failed to set keybinds: %s", err)
}
initTUI()
go browseLocal(localPath)
go func() {
if err := app.Run(); err != nil {
log.Fatalf("failed to run application: %s", err)
}
done <- true
}()
<-done
return nil
}