forked from zach-klippenstein/goadb
-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.go
154 lines (128 loc) · 3.81 KB
/
server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package adb
import (
stderrors "errors"
"fmt"
"os"
"os/exec"
"strings"
"github.com/yosemite-open/go-adb/internal/errors"
"github.com/yosemite-open/go-adb/wire"
)
const (
AdbExecutableName = "adb"
// Default port the adb server listens on.
AdbPort = 5037
)
type ServerConfig struct {
// Path to the adb executable. If empty, the PATH environment variable will be searched.
PathToAdb string
// Host and port the adb server is listening on.
// If not specified, will use the default port on localhost.
Host string
Port int
// Dialer used to connect to the adb server.
Dialer
fs *filesystem
}
// Server knows how to start the adb server and connect to it.
type server interface {
Start() error
Dial() (*wire.Conn, error)
}
func roundTripSingleResponse(s server, req string) ([]byte, error) {
conn, err := s.Dial()
if err != nil {
return nil, err
}
defer conn.Close()
return conn.RoundTripSingleResponse([]byte(req))
}
func roundTripSingleNoResponse(s server, req string) error {
conn, err := s.Dial()
if err != nil {
return err
}
defer conn.Close()
return conn.RoundTripSingleNoResponse([]byte(req))
}
type realServer struct {
config ServerConfig
// Caches Host:Port so they don't have to be concatenated for every dial.
address string
}
func newServer(config ServerConfig) (server, error) {
if config.Dialer == nil {
config.Dialer = tcpDialer{}
}
if config.Host == "" {
// localhost becames very slow on windows(1s dial delay), use 127.0.0.1 works well
config.Host = "127.0.0.1"
}
if config.Port == 0 {
config.Port = AdbPort
}
if config.fs == nil {
config.fs = localFilesystem
}
if config.PathToAdb == "" {
path, err := config.fs.LookPath(AdbExecutableName)
if err != nil {
return nil, errors.WrapErrorf(err, errors.ServerNotAvailable, "could not find %s in PATH", AdbExecutableName)
}
config.PathToAdb = path
}
if err := config.fs.IsExecutableFile(config.PathToAdb); err != nil {
return nil, errors.WrapErrorf(err, errors.ServerNotAvailable, "invalid adb executable: %s", config.PathToAdb)
}
return &realServer{
config: config,
address: fmt.Sprintf("%s:%d", config.Host, config.Port),
}, nil
}
// Dial tries to connect to the server. If the first attempt fails, tries starting the server before
// retrying. If the second attempt fails, returns the error.
func (s *realServer) Dial() (*wire.Conn, error) {
conn, err := s.config.Dial(s.address)
if err != nil {
// Attempt to start the server and try again.
if err = s.Start(); err != nil {
return nil, errors.WrapErrorf(err, errors.ServerNotAvailable, "error starting server for dial")
}
conn, err = s.config.Dial(s.address)
if err != nil {
return nil, err
}
}
return conn, nil
}
// StartServer ensures there is a server running.
func (s *realServer) Start() error {
output, err := s.config.fs.CmdCombinedOutput(s.config.PathToAdb, "start-server")
outputStr := strings.TrimSpace(string(output))
return errors.WrapErrorf(err, errors.ServerNotAvailable, "error starting server: %s\noutput:\n%s", err, outputStr)
}
// filesystem abstracts interactions with the local filesystem for testability.
type filesystem struct {
// Wraps exec.LookPath.
LookPath func(string) (string, error)
// Returns nil if path is a regular file and executable by the current user.
IsExecutableFile func(path string) error
// Wraps exec.Command().CombinedOutput()
CmdCombinedOutput func(name string, arg ...string) ([]byte, error)
}
var localFilesystem = &filesystem{
LookPath: exec.LookPath,
IsExecutableFile: func(path string) error {
info, err := os.Stat(path)
if err != nil {
return err
}
if !info.Mode().IsRegular() {
return stderrors.New("not a regular file")
}
return isExecutable(path)
},
CmdCombinedOutput: func(name string, arg ...string) ([]byte, error) {
return exec.Command(name, arg...).CombinedOutput()
},
}