mirror of https://github.com/mvdan/fdroidcl.git
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.
264 lines
5.9 KiB
264 lines
5.9 KiB
// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc> |
|
// See LICENSE for licensing information |
|
|
|
package adb |
|
|
|
import ( |
|
"bufio" |
|
"bytes" |
|
"fmt" |
|
"os/exec" |
|
"regexp" |
|
"strconv" |
|
"strings" |
|
) |
|
|
|
type Device struct { |
|
ID string |
|
Usb string |
|
Product string |
|
Model string |
|
Device string |
|
ABIs []string |
|
APILevel int |
|
} |
|
|
|
var deviceRegex = regexp.MustCompile(`^([^\s]+)\s+device(.*)$`) |
|
|
|
func Devices() ([]*Device, error) { |
|
cmd := exec.Command("adb", "devices", "-l") |
|
stdout, err := cmd.StdoutPipe() |
|
if err != nil { |
|
return nil, err |
|
} |
|
if err := cmd.Start(); err != nil { |
|
return nil, err |
|
} |
|
var devices []*Device |
|
scanner := bufio.NewScanner(stdout) |
|
for scanner.Scan() { |
|
m := deviceRegex.FindStringSubmatch(scanner.Text()) |
|
if m == nil { |
|
continue |
|
} |
|
device := &Device{ |
|
ID: m[1], |
|
} |
|
for _, extra := range strings.Split(m[2], " ") { |
|
sp := strings.SplitN(extra, ":", 2) |
|
if len(sp) < 2 { |
|
continue |
|
} |
|
switch sp[0] { |
|
case "usb": |
|
device.Usb = sp[1] |
|
case "product": |
|
device.Product = sp[1] |
|
case "model": |
|
device.Model = sp[1] |
|
case "device": |
|
device.Device = sp[1] |
|
} |
|
} |
|
|
|
props, err := device.AdbProps() |
|
if err != nil { |
|
return nil, err |
|
} |
|
device.ABIs = getAbis(device, props) |
|
if len(device.ABIs) == 0 { |
|
return nil, fmt.Errorf("failed to get device ABIs") |
|
} |
|
device.APILevel, _ = strconv.Atoi(props["ro.build.version.sdk"]) |
|
if device.APILevel == 0 { |
|
return nil, fmt.Errorf("failed to get device API level") |
|
} |
|
|
|
devices = append(devices, device) |
|
} |
|
return devices, nil |
|
} |
|
|
|
func (d *Device) AdbCmd(args ...string) *exec.Cmd { |
|
cmdArgs := append([]string{"-s", d.ID}, args...) |
|
return exec.Command("adb", cmdArgs...) |
|
} |
|
|
|
func (d *Device) AdbShell(args ...string) *exec.Cmd { |
|
shellArgs := append([]string{"shell"}, args...) |
|
return d.AdbCmd(shellArgs...) |
|
} |
|
|
|
var propLineRegex = regexp.MustCompile(`^\[(.*)\]: \[(.*)\]$`) |
|
|
|
func (d *Device) AdbProps() (map[string]string, error) { |
|
cmd := d.AdbShell("getprop") |
|
stdout, err := cmd.StdoutPipe() |
|
if err != nil { |
|
return nil, err |
|
} |
|
if err := cmd.Start(); err != nil { |
|
return nil, err |
|
} |
|
props := make(map[string]string) |
|
scanner := bufio.NewScanner(stdout) |
|
for scanner.Scan() { |
|
m := propLineRegex.FindStringSubmatch(scanner.Text()) |
|
if m == nil { |
|
continue |
|
} |
|
key, val := m[1], m[2] |
|
props[key] = val |
|
} |
|
return props, nil |
|
} |
|
|
|
func (d *Device) AdbProp(property string) (string, error) { |
|
cmd := d.AdbShell("getprop", property) |
|
stdout, err := cmd.Output() |
|
if err != nil { |
|
return "", err |
|
} |
|
result := string(stdout) |
|
if result == "" { |
|
return "", fmt.Errorf("could not get property %s", property) |
|
} |
|
return result, nil |
|
} |
|
|
|
func AdbPropFallback(device *Device, props map[string]string, property string) (string, error) { |
|
if value, e := props[property]; e { |
|
return value, nil |
|
} |
|
return device.AdbProp(property) |
|
} |
|
|
|
func getFailureCode(r *regexp.Regexp, line string) string { |
|
return r.FindStringSubmatch(line)[1] |
|
} |
|
|
|
func getAbis(device *Device, props map[string]string) []string { |
|
// Android 5.0 and later specify a list of ABIs |
|
if abilist, err := AdbPropFallback(device, props, "ro.product.cpu.abilist"); err == nil { |
|
return strings.Split(abilist, ",") |
|
} |
|
// Older Android versions specify one primary ABI and optionally |
|
// one secondary ABI |
|
abi, err := AdbPropFallback(device, props, "ro.product.cpu.abi") |
|
if err != nil { |
|
return nil |
|
} |
|
if abi2, err := AdbPropFallback(device, props, "ro.product.cpu.abi2"); err == nil { |
|
return []string{abi, abi2} |
|
} |
|
return []string{abi} |
|
} |
|
|
|
var installFailureRegex = regexp.MustCompile(`^Failure \[INSTALL_(.+)\]$`) |
|
|
|
func (d *Device) Install(path string) error { |
|
cmd := d.AdbCmd(append([]string{"install", "-r"}, path)...) |
|
output, err := cmd.CombinedOutput() |
|
if err != nil { |
|
return err |
|
} |
|
line := getResultLine(output) |
|
if line == "Success" { |
|
return nil |
|
} |
|
return parseError(getFailureCode(installFailureRegex, line)) |
|
} |
|
|
|
func (d *Device) InstallUser(path, user string) error { |
|
cmd := d.AdbCmd(append([]string{"install", "-r", "--user"}, user, path)...) |
|
output, err := cmd.CombinedOutput() |
|
if err != nil { |
|
return err |
|
} |
|
line := getResultLine(output) |
|
if line == "Success" { |
|
return nil |
|
} |
|
return parseError(getFailureCode(installFailureRegex, line)) |
|
} |
|
|
|
func getResultLine(output []byte) string { |
|
scanner := bufio.NewScanner(bytes.NewReader(output)) |
|
for scanner.Scan() { |
|
l := scanner.Text() |
|
if strings.HasPrefix(l, "Success") { |
|
return l |
|
} |
|
failure := strings.Index(l, "Failure") |
|
if failure >= 0 { |
|
return l[failure:] |
|
} |
|
} |
|
return "" |
|
} |
|
|
|
var deleteFailureRegex = regexp.MustCompile(`^Failure \[DELETE_(.+)\]$`) |
|
|
|
func (d *Device) Uninstall(pkg string) error { |
|
cmd := d.AdbCmd("uninstall", pkg) |
|
output, err := cmd.CombinedOutput() |
|
if err != nil { |
|
return err |
|
} |
|
line := getResultLine(output) |
|
if line == "Success" { |
|
return nil |
|
} |
|
return parseError(getFailureCode(deleteFailureRegex, line)) |
|
} |
|
|
|
type Package struct { |
|
ID string |
|
VersCode int |
|
VersName string |
|
} |
|
|
|
var ( |
|
packageRegex = regexp.MustCompile(`^ Package \[([^\s]+)\]`) |
|
verCodeRegex = regexp.MustCompile(`^ versionCode=([0-9]+)`) |
|
verNameRegex = regexp.MustCompile(`^ versionName=(.+)`) |
|
) |
|
|
|
func (d *Device) Installed() (map[string]Package, error) { |
|
cmd := d.AdbShell("dumpsys", "package", "packages") |
|
stdout, err := cmd.StdoutPipe() |
|
if err != nil { |
|
return nil, err |
|
} |
|
if err := cmd.Start(); err != nil { |
|
return nil, err |
|
} |
|
packages := make(map[string]Package) |
|
scanner := bufio.NewScanner(stdout) |
|
var cur Package |
|
first := true |
|
for scanner.Scan() { |
|
l := scanner.Text() |
|
if m := packageRegex.FindStringSubmatch(l); m != nil { |
|
if first { |
|
first = false |
|
} else { |
|
packages[cur.ID] = cur |
|
cur = Package{} |
|
} |
|
cur.ID = m[1] |
|
} else if m := verCodeRegex.FindStringSubmatch(l); m != nil { |
|
n, err := strconv.Atoi(m[1]) |
|
if err != nil { |
|
panic(err) |
|
} |
|
cur.VersCode = n |
|
} else if m := verNameRegex.FindStringSubmatch(l); m != nil { |
|
cur.VersName = m[1] |
|
} |
|
} |
|
if !first { |
|
packages[cur.ID] = cur |
|
} |
|
return packages, nil |
|
}
|
|
|