新增文件推送方法
This commit is contained in:
@@ -60,41 +60,6 @@ func ReadLenFrame(conn net.Conn) ([]byte, error) {
|
||||
return readN(conn, int(l), 10*time.Second)
|
||||
}
|
||||
|
||||
func ExecOut(conn net.Conn, cmd string) ([]byte, error) {
|
||||
if err := WriteAdbCmd(conn, "exec-out:"+cmd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
status, err := ReadStatus(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if status == "FAIL" {
|
||||
msg, _ := ReadLenFrame(conn)
|
||||
return nil, fmt.Errorf("exec-out FAIL: %s", string(msg))
|
||||
}
|
||||
if status != "OKAY" {
|
||||
return nil, fmt.Errorf("unexpected exec-out status: %s", status)
|
||||
}
|
||||
var out []byte
|
||||
for {
|
||||
data, err := ReadLenFrame(conn)
|
||||
if err != nil {
|
||||
if ne, ok := err.(net.Error); ok && ne.Timeout() {
|
||||
break
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
break
|
||||
}
|
||||
out = append(out, data...)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// transportTo: 指示 adb server 将后续请求路由到指定 serial
|
||||
func TransportTo(conn net.Conn, serial string) error {
|
||||
if err := WriteAdbCmd(conn, "host:transport:"+serial); err != nil {
|
||||
@@ -150,21 +115,7 @@ func ExecShell(conn net.Conn, shellCmd string) ([]byte, error) {
|
||||
|
||||
// readResponse reads a 4-byte response like OKAY/FAIL and returns it and optional message (for FAIL)
|
||||
func ReadResponse(conn net.Conn, debug bool) (string, []byte, error) {
|
||||
readN := func(n int) ([]byte, error) {
|
||||
buf := make([]byte, n)
|
||||
total := 0
|
||||
conn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||
for total < n {
|
||||
nr, err := conn.Read(buf[total:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
total += nr
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
stb, err := readN(4)
|
||||
stb, err := readN(conn, 4, 10*time.Second)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
@@ -177,7 +128,7 @@ func ReadResponse(conn net.Conn, debug bool) (string, []byte, error) {
|
||||
}
|
||||
if st == "FAIL" {
|
||||
// read next 4 bytes (may be little-endian uint32 length or ASCII hex)
|
||||
hdr, err := readN(4)
|
||||
hdr, err := readN(conn, 4, 10*time.Second)
|
||||
if err != nil {
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "ReadResponseFixed: no length header after FAIL: %v\n", err)
|
||||
@@ -191,7 +142,7 @@ func ReadResponse(conn net.Conn, debug bool) (string, []byte, error) {
|
||||
// try little-endian uint32 first
|
||||
l := int(binary.LittleEndian.Uint32(hdr))
|
||||
if l > 0 {
|
||||
msg, err := readN(l)
|
||||
msg, err := readN(conn, l, 10*time.Second)
|
||||
if err != nil {
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "ReadResponseFixed: failed to read %d bytes message: %v\n", l, err)
|
||||
@@ -206,7 +157,7 @@ func ReadResponse(conn net.Conn, debug bool) (string, []byte, error) {
|
||||
|
||||
// fallback: try ASCII-hex parse (backwards compatibility)
|
||||
if n, perr := strconv.ParseInt(string(hdr), 16, 32); perr == nil && n > 0 {
|
||||
msg, err := readN(int(n))
|
||||
msg, err := readN(conn, int(n), 10*time.Second)
|
||||
if err != nil {
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "ReadResponseFixed: failed to read ascii-hex message of len %d: %v\n", n, err)
|
||||
|
||||
@@ -111,69 +111,6 @@ func parseDevicesPayload(payload string) []DeviceInfo {
|
||||
return out
|
||||
}
|
||||
|
||||
// getPropExecOut: 通过 exec-out:getprop 一次性获取设备所有 getprop 输出(返回原始字节)
|
||||
func getPropExecOut(addr, serial string, timeout time.Duration) ([]byte, error) {
|
||||
// open a connection and switch transport for this connection
|
||||
conn, err := DialADB(addr, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// ensure close
|
||||
defer conn.Close()
|
||||
|
||||
// transport
|
||||
if err := WriteAdbCmd(conn, "host:transport:"+serial); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
status, err := ReadStatus(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if status == "FAIL" {
|
||||
msg, _ := ReadLenFrame(conn)
|
||||
return nil, fmt.Errorf("transport FAIL: %s", string(msg))
|
||||
}
|
||||
if status != "OKAY" {
|
||||
return nil, fmt.Errorf("unexpected transport status: %s", status)
|
||||
}
|
||||
|
||||
// send exec-out:getprop
|
||||
if err := WriteAdbCmd(conn, "exec-out:getprop"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
status, err = ReadStatus(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if status == "FAIL" {
|
||||
msg, _ := ReadLenFrame(conn)
|
||||
return nil, fmt.Errorf("exec-out FAIL: %s", string(msg))
|
||||
}
|
||||
if status != "OKAY" {
|
||||
return nil, fmt.Errorf("unexpected exec-out status: %s", status)
|
||||
}
|
||||
|
||||
// read frames until timeout/EOF and concatenate
|
||||
var b []byte
|
||||
for {
|
||||
data, err := ReadLenFrame(conn)
|
||||
if err != nil {
|
||||
if ne, ok := err.(net.Error); ok && ne.Timeout() {
|
||||
break
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
break
|
||||
}
|
||||
b = append(b, data...)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// parseGetprop parses getprop output "key]: [value" lines into map
|
||||
func parseGetprop(raw []byte) map[string]string {
|
||||
m := map[string]string{}
|
||||
@@ -203,35 +140,6 @@ func parseGetprop(raw []byte) map[string]string {
|
||||
return m
|
||||
}
|
||||
|
||||
// CollectAllDevices gathers DeviceInfo for all devices listed by adb server
|
||||
func CollectAllDevices(addr string, timeout time.Duration) ([]DeviceInfo, error) {
|
||||
raw, err := listDevicesRaw(addr, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
devs := parseDevicesPayload(raw)
|
||||
if len(devs) == 0 {
|
||||
return devs, nil
|
||||
}
|
||||
|
||||
// For each device, fetch getprop via exec-out (sequential; can be parallelized)
|
||||
for i := range devs {
|
||||
serial := devs[i].Serial
|
||||
rawProps, err := getPropExecOut(addr, serial, timeout)
|
||||
if err != nil {
|
||||
// record error in Props under special key
|
||||
devs[i].Props["__getprop_error"] = err.Error()
|
||||
continue
|
||||
}
|
||||
props := parseGetprop(rawProps)
|
||||
// merge into existing short props (from devices -l)
|
||||
for k, v := range props {
|
||||
devs[i].Props[k] = v
|
||||
}
|
||||
}
|
||||
return devs, nil
|
||||
}
|
||||
|
||||
func parseDevicesMap(payload string) (map[string]string, []string) {
|
||||
m := make(map[string]string)
|
||||
lines := strings.Split(payload, "\n")
|
||||
@@ -292,7 +200,7 @@ func FindSerialByProduct(addr, targetProduct string) (string, error) {
|
||||
if err := TransportTo(conn, serial); err != nil {
|
||||
continue
|
||||
}
|
||||
out, err := ExecOut(conn, "getprop ro.product.model")
|
||||
out, err := ExecShell(conn, "getprop ro.product.model")
|
||||
if err == nil {
|
||||
if strings.TrimSpace(string(out)) == targetProduct {
|
||||
return serial, nil
|
||||
|
||||
186
adb/sync.go
186
adb/sync.go
@@ -7,6 +7,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -22,52 +23,13 @@ func InitSync(conn net.Conn) *Sync {
|
||||
}
|
||||
}
|
||||
|
||||
// SyncPushTryVariants 按顺序尝试多种 SEND payload 风格,直到成功或尝试完毕。
|
||||
// addr: adb server (e.g., "127.0.0.1:5037")
|
||||
// serial: device serial
|
||||
// SyncPushFile 将本地文件推送到设备
|
||||
// localPath: 本地文件路径
|
||||
// remotePath: 目标完整路径(必须包含文件名)
|
||||
// mode: unix permission like 0644
|
||||
// debug: 打印调试信息
|
||||
func SyncPushTryVariants(addr, serial, localPath, remotePath string, mode int, debug bool) (int64, error) {
|
||||
type sendOption struct {
|
||||
name string
|
||||
withNUL bool
|
||||
modeFormat string // "hex" (0x8000|mode) or "dec" (decimal S_IFREG|mode)
|
||||
}
|
||||
opts := []sendOption{
|
||||
{"send-with-nul-hex", true, "hex"},
|
||||
{"send-without-nul-hex", false, "hex"},
|
||||
{"send-without-nul-dec", false, "dec"},
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for _, opt := range opts {
|
||||
if debug {
|
||||
fmt.Printf("[try] option=%s\n", opt.name)
|
||||
}
|
||||
n, err := syncPushOne(addr, serial, localPath, remotePath, mode, opt.withNUL, opt.modeFormat, debug)
|
||||
if err == nil {
|
||||
if debug {
|
||||
fmt.Printf("[ok] option=%s pushed=%d\n", opt.name, n)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
lastErr = fmt.Errorf("%s: %w", opt.name, err)
|
||||
if debug {
|
||||
fmt.Printf("[fail] option=%s err=%v\n", opt.name, err)
|
||||
}
|
||||
// small pause between tries
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
}
|
||||
return 0, lastErr
|
||||
}
|
||||
|
||||
// syncPushOne 在单个连接上按给定选项完成 sync push(一次性连接)
|
||||
// withNUL: 是否在 SEND payload 后追加 NUL byte
|
||||
// modeFormat: "hex" 表示 use 0x8000|mode in decimal string (common), "dec" 表示 decimal S_IFREG|mode
|
||||
func syncPushOne(addr, serial, localPath, remotePath string, mode int, withNUL bool, modeFormat string, debug bool) (int64, error) {
|
||||
// open file
|
||||
// remotePath: 设备目标路径
|
||||
// mode: 文件权限 (如 0644)
|
||||
// debug: 是否打印调试信息
|
||||
func (s *Sync) SyncPushFile(localPath, remotePath string, mode int, debug bool) (int64, error) {
|
||||
// 打开文件
|
||||
f, err := os.Open(localPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -75,130 +37,44 @@ func syncPushOne(addr, serial, localPath, remotePath string, mode int, withNUL b
|
||||
defer f.Close()
|
||||
fi, _ := f.Stat()
|
||||
if debug {
|
||||
fmt.Printf("local filesize=%d\n", fi.Size())
|
||||
fmt.Printf("[DEBUG] local filesize=%d\n", fi.Size())
|
||||
}
|
||||
|
||||
// dial adb
|
||||
d := net.Dialer{Timeout: 8 * time.Second}
|
||||
conn, err := d.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
// 打开 sync
|
||||
if err := WriteAdbCmd(s.Conn, "sync:"); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer conn.Close()
|
||||
if tcp, ok := conn.(*net.TCPConn); ok {
|
||||
_ = tcp.SetNoDelay(true)
|
||||
}
|
||||
|
||||
writeAdbCmd := func(cmd string) error {
|
||||
hdr := fmt.Sprintf("%04x", len(cmd))
|
||||
conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
|
||||
_, err := conn.Write([]byte(hdr + cmd))
|
||||
return err
|
||||
}
|
||||
readN := func(n int) ([]byte, error) {
|
||||
buf := make([]byte, n)
|
||||
total := 0
|
||||
for total < n {
|
||||
conn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||
nr, err := conn.Read(buf[total:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
total += nr
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
read4 := func() ([]byte, error) { return readN(4) }
|
||||
|
||||
// readResponse that returns status and optional msg (LE length)
|
||||
readResponse := func() (string, []byte, error) {
|
||||
stb, err := read4()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
st := string(stb)
|
||||
// if OKAY quick return
|
||||
if st == "OKAY" {
|
||||
return st, nil, nil
|
||||
}
|
||||
if st == "FAIL" {
|
||||
// try LE length
|
||||
hdr, err := read4()
|
||||
if err != nil {
|
||||
return st, nil, nil
|
||||
}
|
||||
l := int(binary.LittleEndian.Uint32(hdr))
|
||||
if l > 0 {
|
||||
msg, err := readN(l)
|
||||
if err != nil {
|
||||
return st, nil, nil
|
||||
}
|
||||
return st, msg, nil
|
||||
}
|
||||
// fallback ascii-hex
|
||||
if n, perr := strconv.ParseInt(string(hdr), 16, 32); perr == nil && n > 0 {
|
||||
msg, err := readN(int(n))
|
||||
if err != nil {
|
||||
return st, nil, nil
|
||||
}
|
||||
return st, msg, nil
|
||||
}
|
||||
return st, nil, nil
|
||||
}
|
||||
return st, nil, nil
|
||||
}
|
||||
|
||||
// transport
|
||||
if err := writeAdbCmd("host:transport:" + serial); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
tok, err := read4()
|
||||
tok, err := readN(s.Conn, 4, 10*time.Second)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if string(tok) != "OKAY" {
|
||||
return 0, fmt.Errorf("transport failed: %q", string(tok))
|
||||
}
|
||||
|
||||
// open sync
|
||||
if err := writeAdbCmd("sync:"); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
tok, err = read4()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if string(tok) != "OKAY" {
|
||||
_, msg, _ := readResponse()
|
||||
_, msg, _ := ReadResponse(s.Conn, true)
|
||||
if len(msg) > 0 {
|
||||
return 0, fmt.Errorf("sync open failed: %s", string(msg))
|
||||
}
|
||||
return 0, fmt.Errorf("sync open failed: %q", string(tok))
|
||||
}
|
||||
|
||||
// build SEND payload according options
|
||||
var modeStr string
|
||||
if modeFormat == "hex" {
|
||||
// common implementations expect decimal of (S_IFREG|mode) where S_IFREG is 0100000 (octal) but using 0x8000 is fine as decimal string
|
||||
modeStr = strconv.FormatInt(int64(0x8000|mode), 10)
|
||||
} else {
|
||||
// decimal form: simply decimal of (0x8000|mode)
|
||||
modeStr = strconv.FormatInt(int64(0x8000|mode), 10)
|
||||
}
|
||||
// 构造 SEND payload
|
||||
modeStr := strconv.Itoa(syscall.S_IFREG | mode)
|
||||
sendPayload := []byte(remotePath + "," + modeStr)
|
||||
if withNUL {
|
||||
sendPayload = append(sendPayload, 0)
|
||||
}
|
||||
|
||||
// write SEND
|
||||
if _, err := conn.Write(append([]byte("SEND"), sendPayload...)); err != nil {
|
||||
// 写入 "SEND" + 长度 + payload
|
||||
hdr := make([]byte, 8)
|
||||
copy(hdr[:4], []byte("SEND"))
|
||||
binary.LittleEndian.PutUint32(hdr[4:], uint32(len(sendPayload)))
|
||||
if _, err := s.Conn.Write(hdr); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := s.Conn.Write(sendPayload); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if debug {
|
||||
fmt.Printf("Wrote SEND payload len=%d withNUL=%v modeFmt=%s path=%s\n", len(sendPayload), withNUL, modeFormat, remotePath)
|
||||
fmt.Printf("[DEBUG] Wrote SEND payload len=%d path=%s mode=%s\n", len(sendPayload), remotePath, modeStr)
|
||||
}
|
||||
|
||||
// write DATA blocks if file has content (if zero-length, skip DATA)
|
||||
// 写入 DATA 块
|
||||
var total int64
|
||||
buf := make([]byte, maxChunk)
|
||||
for {
|
||||
@@ -207,10 +83,10 @@ func syncPushOne(addr, serial, localPath, remotePath string, mode int, withNUL b
|
||||
hdr := make([]byte, 8)
|
||||
copy(hdr[:4], []byte("DATA"))
|
||||
binary.LittleEndian.PutUint32(hdr[4:], uint32(n))
|
||||
if _, err := conn.Write(hdr); err != nil {
|
||||
if _, err := s.Conn.Write(hdr); err != nil {
|
||||
return total, err
|
||||
}
|
||||
if _, err := conn.Write(buf[:n]); err != nil {
|
||||
if _, err := s.Conn.Write(buf[:n]); err != nil {
|
||||
return total, err
|
||||
}
|
||||
total += int64(n)
|
||||
@@ -223,19 +99,19 @@ func syncPushOne(addr, serial, localPath, remotePath string, mode int, withNUL b
|
||||
}
|
||||
}
|
||||
|
||||
// send DONE
|
||||
// 发送 DONE
|
||||
done := make([]byte, 8)
|
||||
copy(done[:4], []byte("DONE"))
|
||||
binary.LittleEndian.PutUint32(done[4:], uint32(time.Now().Unix()))
|
||||
if _, err := conn.Write(done); err != nil {
|
||||
if _, err := s.Conn.Write(done); err != nil {
|
||||
return total, err
|
||||
}
|
||||
if debug {
|
||||
fmt.Println("Wrote DONE, waiting response")
|
||||
fmt.Println("[DEBUG] Wrote DONE, waiting response")
|
||||
}
|
||||
|
||||
// read final
|
||||
resp, msg, err := readResponse()
|
||||
// 读取最终响应
|
||||
resp, msg, err := ReadResponse(s.Conn, true)
|
||||
if err != nil {
|
||||
return total, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user