<commit_before>// Copyright (c) 2015, <NAME> <<EMAIL>>
// 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(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 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)...)
	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) Install(path string) error {
	return d.install(nil, path)
}

func (d *Device) Upgrade(path string) error {
	return d.install([]string{"-r"}, path)
}

func getResultLine(output []byte) string {
	scanner := bufio.NewScanner(bytes.NewReader(output))
	for scanner.Scan() {
		l := scanner.Text()
		if strings.HasPrefix(l, "Success") {
			return l
		}<commit_msg>adb: actually use cmd parameter<commit_after>// Copyright (c) 2015, <NAME> <<EMAIL>>
// 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(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 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{cmd}, opts...)
	return append(v, args...)
}

func (d *Device) install(opts []string, path string) error {
	cmd := d.AdbCmd(withOpts("install", opts, 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) Install(path string) error {
	return d.install(nil, path)
}

func (d *Device) Upgrade(path string) error {
	return d.install([]string{"-r"}, path)
}

func getResultLine(output []byte) string {
	scanner := bufio.NewScanner(bytes.NewReader(output))
	for scanner.Scan() {
		l := scanner.Text()
		if strings.HasPrefix(l, "Success") {
			return l
		}
{'fraction_non_alphanumeric': 0.17448527495439145, 'fraction_numerical': 0.005994266353922335, 'mean_word_length': 3.5067527891955375, 'pattern_counts': {'":': 10, '<': 11, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '563956', 'n_tokens_mistral': 3027, 'n_tokens_neox': 2821, 'n_words': 730}