cb7e06bdb9
All sync status reads now also check for failure status, using the same logic as the normal Scanner (the only difference being that for normal scanners, length is encoded as a hex string, but for sync scanners it's in binary little-endian).
190 lines
5.1 KiB
Go
190 lines
5.1 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/cheggaaa/pb"
|
|
"github.com/zach-klippenstein/goadb"
|
|
"github.com/zach-klippenstein/goadb/util"
|
|
"gopkg.in/alecthomas/kingpin.v2"
|
|
)
|
|
|
|
var (
|
|
serial = kingpin.Flag("serial", "Connect to device by serial number.").Short('s').String()
|
|
|
|
shellCommand = kingpin.Command("shell", "Run a shell command on the device.")
|
|
shellCommandArg = shellCommand.Arg("command", "Command to run on device.").Strings()
|
|
|
|
devicesCommand = kingpin.Command("devices", "List devices.")
|
|
devicesLongFlag = devicesCommand.Flag("long", "Include extra detail about devices.").Short('l').Bool()
|
|
|
|
pullCommand = kingpin.Command("pull", "Pull a file from the device.")
|
|
pullProgressFlag = pullCommand.Flag("progress", "Show progress.").Short('p').Bool()
|
|
pullRemoteArg = pullCommand.Arg("remote", "Path of source file on device.").Required().String()
|
|
pullLocalArg = pullCommand.Arg("local", "Path of destination file.").String()
|
|
|
|
pushCommand = kingpin.Command("push", "Push a file to the device.").Hidden()
|
|
pushProgressFlag = pushCommand.Flag("progress", "Show progress.").Short('p').Bool()
|
|
pushLocalArg = pushCommand.Arg("local", "Path of source file.").Required().String()
|
|
pushRemoteArg = pushCommand.Arg("remote", "Path of destination file on device.").Required().String()
|
|
)
|
|
|
|
func main() {
|
|
var exitCode int
|
|
|
|
switch kingpin.Parse() {
|
|
case "devices":
|
|
exitCode = listDevices(*devicesLongFlag)
|
|
case "shell":
|
|
exitCode = runShellCommand(*shellCommandArg, parseDevice())
|
|
case "pull":
|
|
exitCode = pull(*pullProgressFlag, *pullRemoteArg, *pullLocalArg, parseDevice())
|
|
case "push":
|
|
exitCode = push(*pushProgressFlag, *pushLocalArg, *pushRemoteArg, parseDevice())
|
|
}
|
|
|
|
os.Exit(exitCode)
|
|
}
|
|
|
|
func parseDevice() goadb.DeviceDescriptor {
|
|
if *serial != "" {
|
|
return goadb.DeviceWithSerial(*serial)
|
|
}
|
|
|
|
return goadb.AnyDevice()
|
|
}
|
|
|
|
func listDevices(long bool) int {
|
|
client := goadb.NewHostClient(goadb.ClientConfig{})
|
|
devices, err := client.ListDevices()
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "error:", err)
|
|
}
|
|
|
|
for _, device := range devices {
|
|
if long {
|
|
if device.Usb == "" {
|
|
fmt.Printf("%s\tproduct:%s model:%s device:%s\n",
|
|
device.Serial, device.Product, device.Model, device.DeviceInfo)
|
|
} else {
|
|
fmt.Printf("%s\tusb:%s product:%s model:%s device:%s\n",
|
|
device.Serial, device.Usb, device.Product, device.Model, device.DeviceInfo)
|
|
}
|
|
} else {
|
|
fmt.Println(device.Serial)
|
|
}
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func runShellCommand(commandAndArgs []string, device goadb.DeviceDescriptor) int {
|
|
if len(commandAndArgs) == 0 {
|
|
fmt.Fprintln(os.Stderr, "error: no command")
|
|
kingpin.Usage()
|
|
return 1
|
|
}
|
|
|
|
command := commandAndArgs[0]
|
|
var args []string
|
|
|
|
if len(commandAndArgs) > 1 {
|
|
args = commandAndArgs[1:]
|
|
}
|
|
|
|
client := goadb.NewDeviceClient(goadb.ClientConfig{}, device)
|
|
output, err := client.RunCommand(command, args...)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "error:", err)
|
|
return 1
|
|
}
|
|
|
|
fmt.Print(output)
|
|
return 0
|
|
}
|
|
|
|
func pull(showProgress bool, remotePath, localPath string, device goadb.DeviceDescriptor) int {
|
|
if remotePath == "" {
|
|
fmt.Fprintln(os.Stderr, "error: must specify remote file")
|
|
kingpin.Usage()
|
|
return 1
|
|
}
|
|
|
|
if localPath == "" {
|
|
localPath = filepath.Base(remotePath)
|
|
}
|
|
|
|
client := goadb.NewDeviceClient(goadb.ClientConfig{}, device)
|
|
|
|
info, err := client.Stat(remotePath)
|
|
if util.HasErrCode(err, util.FileNoExistError) {
|
|
fmt.Fprintln(os.Stderr, "remote file does not exist:", remotePath)
|
|
return 1
|
|
} else if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error reading remote file %s: %s\n", remotePath, err)
|
|
return 1
|
|
}
|
|
|
|
remoteFile, err := client.OpenRead(remotePath)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error opening remote file %s: %s\n", remotePath, util.ErrorWithCauseChain(err))
|
|
return 1
|
|
}
|
|
defer remoteFile.Close()
|
|
|
|
localFile, err := os.Create(localPath)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "error opening local file %s: %s\n", localPath, err)
|
|
return 1
|
|
}
|
|
defer localFile.Close()
|
|
|
|
var output io.Writer
|
|
var updateProgress func()
|
|
if showProgress {
|
|
output, updateProgress = createProgressBarWriter(int(info.Size), localFile)
|
|
} else {
|
|
output = localFile
|
|
updateProgress = func() {}
|
|
}
|
|
|
|
startTime := time.Now()
|
|
copied, err := io.Copy(output, remoteFile)
|
|
|
|
// Force progress update if the transfer was really fast.
|
|
updateProgress()
|
|
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "error pulling file:", err)
|
|
return 1
|
|
}
|
|
|
|
duration := time.Now().Sub(startTime)
|
|
rate := int64(float64(copied) / duration.Seconds())
|
|
fmt.Printf("%d B/s (%d bytes in %s)\n", rate, copied, duration)
|
|
return 0
|
|
}
|
|
|
|
func push(showProgress bool, localPath, remotePath string, device goadb.DeviceDescriptor) int {
|
|
fmt.Fprintln(os.Stderr, "not implemented")
|
|
return 1
|
|
}
|
|
|
|
func createProgressBarWriter(size int, w io.Writer) (progressWriter io.Writer, update func()) {
|
|
progress := pb.New(size)
|
|
progress.SetUnits(pb.U_BYTES)
|
|
progress.SetRefreshRate(100 * time.Millisecond)
|
|
progress.ShowSpeed = true
|
|
progress.ShowPercent = true
|
|
progress.ShowTimeLeft = true
|
|
progress.Start()
|
|
|
|
progressWriter = io.MultiWriter(w, progress)
|
|
update = progress.Update
|
|
return
|
|
}
|