mirror of https://github.com/mvdan/fdroidcl.git
Browse Source
Splitting adb into a separate repo wasn't a good idea, as it's not very useful in general and it's tightly coupled with fdroidcl.pull/10/head
4 changed files with 407 additions and 0 deletions
@ -0,0 +1,247 @@
|
||||
// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
|
||||
// See LICENSE for licensing information
|
||||
|
||||
package adb |
||||
|
||||
import ( |
||||
"bufio" |
||||
"fmt" |
||||
"io" |
||||
"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() { |
||||
line := scanner.Text() |
||||
m := deviceRegex.FindStringSubmatch(line) |
||||
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(props) |
||||
if len(device.ABIs) == 0 { |
||||
return nil, fmt.Errorf("failed to get device ABIs") |
||||
} |
||||
device.APILevel, err = 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 getFailureCode(r *regexp.Regexp, line string) string { |
||||
return r.FindStringSubmatch(line)[1] |
||||
} |
||||
|
||||
func getAbis(props map[string]string) []string { |
||||
// Android 5.0 and later specify a list of ABIs
|
||||
if abilist, e := props["ro.product.cpu.abilist"]; e { |
||||
return strings.Split(abilist, ",") |
||||
} |
||||
// Older Android versions specify one primary ABI and optionally
|
||||
// one secondary ABI
|
||||
abi, e := props["ro.product.cpu.abi"] |
||||
if !e { |
||||
return nil |
||||
} |
||||
if abi2, e := props["ro.product.cpu.abi2"]; e { |
||||
return []string{abi, abi2} |
||||
} |
||||
return []string{abi} |
||||
} |
||||
|
||||
var installFailureRegex = regexp.MustCompile(`^Failure \[INSTALL_(.+)\]$`) |
||||
|
||||
func withOpts(cmd string, opts []string, args ...string) []string { |
||||
v := append([]string{"install"}, opts...) |
||||
return append(v, args...) |
||||
} |
||||
|
||||
func (d *Device) install(opts []string, path string) error { |
||||
cmd := d.AdbCmd(withOpts("install", opts, path)...) |
||||
stdout, err := cmd.StdoutPipe() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if err := cmd.Start(); err != nil { |
||||
return err |
||||
} |
||||
line := getResultLine(stdout) |
||||
if line == "Success" { |
||||
return nil |
||||
} |
||||
return parseError(getFailureCode(installFailureRegex, line)) |
||||
} |
||||
|
||||
func (d *Device) Install(path string) error { |
||||
return d.install(nil, path) |
||||
} |
||||
|
||||
func (d *Device) Upgrade(path string) error { |
||||
return d.install([]string{"-r"}, path) |
||||
} |
||||
|
||||
func getResultLine(out io.Reader) string { |
||||
scanner := bufio.NewScanner(out) |
||||
for scanner.Scan() { |
||||
l := scanner.Text() |
||||
if strings.HasPrefix(l, "Failure") || strings.HasPrefix(l, "Success") { |
||||
return l |
||||
} |
||||
} |
||||
return "" |
||||
} |
||||
|
||||
var deleteFailureRegex = regexp.MustCompile(`^Failure \[DELETE_(.+)\]$`) |
||||
|
||||
func (d *Device) Uninstall(pkg string) error { |
||||
cmd := d.AdbCmd("uninstall", pkg) |
||||
stdout, err := cmd.StdoutPipe() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if err := cmd.Start(); err != nil { |
||||
return err |
||||
} |
||||
line := getResultLine(stdout) |
||||
if line == "Success" { |
||||
return nil |
||||
} |
||||
return parseError(getFailureCode(deleteFailureRegex, line)) |
||||
} |
||||
|
||||
type Package struct { |
||||
ID string |
||||
VCode int |
||||
VName 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.VCode = n |
||||
} else if m := verNameRegex.FindStringSubmatch(l); m != nil { |
||||
cur.VName = m[1] |
||||
} |
||||
} |
||||
if !first { |
||||
packages[cur.ID] = cur |
||||
} |
||||
return packages, nil |
||||
} |
||||
@ -0,0 +1,109 @@
|
||||
// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
|
||||
// See LICENSE for licensing information
|
||||
|
||||
package adb |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
) |
||||
|
||||
var ( |
||||
// Common install and uninstall errors
|
||||
ErrInternalError = errors.New("internal error") |
||||
ErrUserRestricted = errors.New("user restricted") |
||||
ErrAborted = errors.New("aborted") |
||||
|
||||
// Install errors
|
||||
ErrAlreadyExists = errors.New("already exists") |
||||
ErrInvalidApk = errors.New("invalid apk") |
||||
ErrInvalidURI = errors.New("invalid uri") |
||||
ErrInsufficientStorage = errors.New("insufficient storage") |
||||
ErrDuplicatePackage = errors.New("duplicate package") |
||||
ErrNoSharedUser = errors.New("no shared user") |
||||
ErrUpdateIncompatible = errors.New("update incompatible") |
||||
ErrSharedUserIncompatible = errors.New("shared user incompatible") |
||||
ErrMissingSharedLibrary = errors.New("missing shared library") |
||||
ErrReplaceCouldntDelete = errors.New("replace couldn't delete") |
||||
ErrDexopt = errors.New("dexopt") |
||||
ErrOlderSdk = errors.New("older sdk") |
||||
ErrConflictingProvider = errors.New("conflicting provider") |
||||
ErrNewerSdk = errors.New("newer sdk") |
||||
ErrTestOnly = errors.New("test only") |
||||
ErrCPUAbiIncompatible = errors.New("cpu abi incompatible") |
||||
ErrMissingFeature = errors.New("missing feature") |
||||
ErrContainerError = errors.New("combiner error") |
||||
ErrInvalidInstallLocation = errors.New("invalid install location") |
||||
ErrMediaUnavailable = errors.New("media unavailable") |
||||
ErrVerificationTimeout = errors.New("verification timeout") |
||||
ErrVerificationFailure = errors.New("verification failure") |
||||
ErrPackageChanged = errors.New("package changed") |
||||
ErrUIDChanged = errors.New("uid changed") |
||||
ErrVersionDowngrade = errors.New("version downgrade") |
||||
ErrNotApk = errors.New("not apk") |
||||
ErrBadManifest = errors.New("bad manifest") |
||||
ErrUnexpectedException = errors.New("unexpected exception") |
||||
ErrNoCertificates = errors.New("no certificates") |
||||
ErrInconsistentCertificates = errors.New("inconsistent certificates") |
||||
ErrCertificateEncoding = errors.New("certificate encoding") |
||||
ErrBadPackageName = errors.New("bad package name") |
||||
ErrBadSharedUserID = errors.New("bad shared user id") |
||||
ErrManifestMalformed = errors.New("manifest malformed") |
||||
ErrManifestEmpty = errors.New("manifest empty") |
||||
ErrDuplicatePermission = errors.New("duplicate permission") |
||||
ErrNoMatchingAbis = errors.New("no matching abis") |
||||
|
||||
// Uninstall errors
|
||||
ErrDevicePolicyManager = errors.New("device policy manager") |
||||
ErrOwnerBlocked = errors.New("owner blocked") |
||||
) |
||||
|
||||
var errorVals = map[string]error{ |
||||
"FAILED_ALREADY_EXISTS": ErrAlreadyExists, |
||||
"FAILED_INVALID_APK": ErrInvalidApk, |
||||
"FAILED_INVALID_URI": ErrInvalidURI, |
||||
"FAILED_INSUFFICIENT_STORAGE": ErrInsufficientStorage, |
||||
"FAILED_DUPLICATE_PACKAGE": ErrDuplicatePackage, |
||||
"FAILED_NO_SHARED_USER": ErrNoSharedUser, |
||||
"FAILED_UPDATE_INCOMPATIBLE": ErrUpdateIncompatible, |
||||
"FAILED_SHARED_USER_INCOMPATIBLE": ErrSharedUserIncompatible, |
||||
"FAILED_MISSING_SHARED_LIBRARY": ErrMissingSharedLibrary, |
||||
"FAILED_REPLACE_COULDNT_DELETE": ErrReplaceCouldntDelete, |
||||
"FAILED_DEXOPT": ErrDexopt, |
||||
"FAILED_OLDER_SDK": ErrOlderSdk, |
||||
"FAILED_CONFLICTING_PROVIDER": ErrConflictingProvider, |
||||
"FAILED_NEWER_SDK": ErrNewerSdk, |
||||
"FAILED_TEST_ONLY": ErrTestOnly, |
||||
"FAILED_CPU_ABI_INCOMPATIBLE": ErrCPUAbiIncompatible, |
||||
"FAILED_MISSING_FEATURE": ErrMissingFeature, |
||||
"FAILED_CONTAINER_ERROR": ErrContainerError, |
||||
"FAILED_INVALID_INSTALL_LOCATION": ErrInvalidInstallLocation, |
||||
"FAILED_MEDIA_UNAVAILABLE": ErrMediaUnavailable, |
||||
"FAILED_VERIFICATION_TIMEOUT": ErrVerificationTimeout, |
||||
"FAILED_VERIFICATION_FAILURE": ErrVerificationFailure, |
||||
"FAILED_PACKAGE_CHANGED": ErrPackageChanged, |
||||
"FAILED_UID_CHANGED": ErrUIDChanged, |
||||
"FAILED_VERSION_DOWNGRADE": ErrVersionDowngrade, |
||||
"PARSE_FAILED_NOT_APK": ErrNotApk, |
||||
"PARSE_FAILED_BAD_MANIFEST": ErrBadManifest, |
||||
"PARSE_FAILED_UNEXPECTED_EXCEPTION": ErrUnexpectedException, |
||||
"PARSE_FAILED_NO_CERTIFICATES": ErrNoCertificates, |
||||
"PARSE_FAILED_INCONSISTENT_CERTIFICATES": ErrInconsistentCertificates, |
||||
"PARSE_FAILED_CERTIFICATE_ENCODING": ErrCertificateEncoding, |
||||
"PARSE_FAILED_BAD_PACKAGE_NAME": ErrBadPackageName, |
||||
"PARSE_FAILED_BAD_SHARED_USER_ID": ErrBadSharedUserID, |
||||
"PARSE_FAILED_MANIFEST_MALFORMED": ErrManifestMalformed, |
||||
"PARSE_FAILED_MANIFEST_EMPTY": ErrManifestEmpty, |
||||
"FAILED_INTERNAL_ERROR": ErrInternalError, |
||||
"FAILED_USER_RESTRICTED": ErrUserRestricted, |
||||
"FAILED_DUPLICATE_PERMISSION": ErrDuplicatePermission, |
||||
"FAILED_NO_MATCHING_ABIS": ErrNoMatchingAbis, |
||||
"FAILED_ABORTED": ErrAborted, |
||||
} |
||||
|
||||
func parseError(s string) error { |
||||
if err, e := errorVals[s]; e { |
||||
return err |
||||
} |
||||
return fmt.Errorf("unknown error: %s", s) |
||||
} |
||||
@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
|
||||
// See LICENSE for licensing information
|
||||
|
||||
package adb |
||||
|
||||
import "testing" |
||||
|
||||
func TestParseError(t *testing.T) { |
||||
tests := []struct { |
||||
in string |
||||
want error |
||||
}{ |
||||
{"FAILED_DEXOPT", ErrDexopt}, |
||||
{"PARSE_FAILED_NOT_APK", ErrNotApk}, |
||||
{"FAILED_ABORTED", ErrAborted}, |
||||
} |
||||
for _, c := range tests { |
||||
got := parseError(c.in) |
||||
if got != c.want { |
||||
t.Fatalf("Parse error in %s - wanted %v, got %v", c.in, c.want, got) |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
|
||||
// See LICENSE for licensing information
|
||||
|
||||
package adb |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net" |
||||
"os/exec" |
||||
) |
||||
|
||||
const ( |
||||
host = "127.0.0.1" |
||||
port = 5037 |
||||
) |
||||
|
||||
func IsServerRunning() bool { |
||||
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, port)) |
||||
if err != nil { |
||||
return false |
||||
} |
||||
conn.Close() |
||||
return true |
||||
} |
||||
|
||||
func StartServer() error { |
||||
return exec.Command("adb", "start-server").Run() |
||||
} |
||||
Loading…
Reference in new issue