Files
go-mobile-uiautomator/adb/sync.go
2026-03-10 13:41:18 +08:00

170 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package adb
import (
"encoding/binary"
"fmt"
"io"
"net"
"os"
"strconv"
"syscall"
"time"
)
// maxChunk 定义单次数据块传输的最大字节数64KB
const maxChunk = 64 * 1024
// Sync 封装了 ADB 文件同步协议的操作
type Sync struct {
Conn net.Conn // 已建立的 ADB 连接
}
// InitSync 创建一个文件同步操作实例
// conn 必须是已经通过 TransportTo 路由到目标设备的连接
func InitSync(conn net.Conn) *Sync {
return &Sync{
Conn: conn,
}
}
// SyncPushFile 将本地文件推送到设备
// localPath: 本地文件路径
// 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
}
defer f.Close()
fi, _ := f.Stat()
if debug {
fmt.Printf("[调试] 本地文件大小=%d\n", fi.Size())
}
// 初始化同步模式
if err := s.StartSync(); err != nil {
return 0, err
}
// 构造 SEND 请求remotePath + "," + 文件权限
modeStr := strconv.Itoa(syscall.S_IFREG | mode)
sendPayload := []byte(remotePath + "," + modeStr)
// 写入 "SEND" 命令头 + 数据长度 + 请求内容
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("[调试] 发送 SEND 请求: 长度=%d 路径=%s 权限=%s\n", len(sendPayload), remotePath, modeStr)
}
// 分块写入文件数据DATA 命令)
var total int64
buf := make([]byte, maxChunk)
for {
n, rerr := f.Read(buf)
if n > 0 {
dataHdr := make([]byte, 8)
copy(dataHdr[:4], []byte("DATA"))
binary.LittleEndian.PutUint32(dataHdr[4:], uint32(n))
if _, err := s.Conn.Write(dataHdr); err != nil {
return total, err
}
if _, err := s.Conn.Write(buf[:n]); err != nil {
return total, err
}
total += int64(n)
}
if rerr != nil {
if rerr == io.EOF {
break
}
return total, rerr
}
}
// 发送 DONE 命令,携带文件修改时间戳
done := make([]byte, 8)
copy(done[:4], []byte("DONE"))
mtime := uint32(fi.ModTime().Unix())
binary.LittleEndian.PutUint32(done[4:], mtime)
if _, err := s.Conn.Write(done); err != nil {
return total, err
}
if debug {
fmt.Println("[调试] 发送 DONE等待响应")
}
// 读取最终响应
resp, msg, err := ReadSyncStatus(s.Conn)
if err != nil {
return total, err
}
if resp != "OKAY" {
if len(msg) > 0 {
return total, fmt.Errorf("同步失败: %s", string(msg))
}
return total, fmt.Errorf("同步失败: %s", resp)
}
return total, nil
}
// StartSync 启动 ADB 同步模式
// 发送 "sync:" 命令并等待 "OKAY" 响应
func (s *Sync) StartSync() error {
if err := WriteAdbCmd(s.Conn, "sync:"); err != nil {
return err
}
tok, err := readN(s.Conn, 4, 10*time.Second)
if err != nil {
return err
}
if string(tok) != "OKAY" {
_, msg, _ := ReadSyncStatus(s.Conn)
if len(msg) > 0 {
return fmt.Errorf("同步模式启动失败: %s", string(msg))
}
return fmt.Errorf("同步模式启动失败: %q", string(tok))
}
return nil
}
// ReadSyncStatus 读取同步协议的状态响应
// 返回状态码("OKAY"/"FAIL"/其他)、失败时的消息内容和错误
func ReadSyncStatus(r io.Reader) (string, string, error) {
hdr := make([]byte, 4)
if _, err := io.ReadFull(r, hdr); err != nil {
return "", "", err
}
status := string(hdr)
switch status {
case "OKAY":
return "OKAY", "", nil
case "FAIL":
// 读取 4 字节小端长度 + 对应长度的错误消息
lenBuf := make([]byte, 4)
if _, err := io.ReadFull(r, lenBuf); err != nil {
return "FAIL", "", err
}
l := binary.LittleEndian.Uint32(lenBuf)
msg := make([]byte, l)
if _, err := io.ReadFull(r, msg); err != nil {
return "FAIL", "", err
}
return "FAIL", string(msg), nil
default:
// 非预期状态,直接返回原始字符串,便于上层排查
return status, "", nil
}
}