diff --git a/host_client.go b/adb.go similarity index 63% rename from host_client.go rename to adb.go index 33850a0..cdbbf40 100644 --- a/host_client.go +++ b/adb.go @@ -8,26 +8,56 @@ import ( ) /* -HostClient communicates with host services on the adb server. +Adb communicates with host services on the adb server. Eg. - StartServer() - client := NewHostClient() + client := adb.New() client.ListDevices() See list of services at https://android.googlesource.com/platform/system/core/+/master/adb/SERVICES.TXT. */ // TODO(z): Finish implementing host services. -type HostClient struct { - server Server +type Adb struct { + server server } -func NewHostClient(server Server) *HostClient { - return &HostClient{server} +// New creates a new Adb client that uses the default ServerConfig. +func New() (*Adb, error) { + return NewWithConfig(ServerConfig{}) } -// GetServerVersion asks the ADB server for its internal version number. -func (c *HostClient) GetServerVersion() (int, error) { +func NewWithConfig(config ServerConfig) (*Adb, error) { + server, err := newServer(config) + if err != nil { + return nil, err + } + return &Adb{server}, nil +} + +// Dial establishes a connection with the adb server. +func (c *Adb) Dial() (*wire.Conn, error) { + return c.server.Dial() +} + +// Starts the adb server if it’s not running. +func (c *Adb) StartServer() error { + return c.server.Start() +} + +func (c *Adb) Device(descriptor DeviceDescriptor) *Device { + return &Device{ + server: c.server, + descriptor: descriptor, + deviceListFunc: c.ListDevices, + } +} + +func (c *Adb) NewDeviceWatcher() *DeviceWatcher { + return newDeviceWatcher(c.server) +} + +// ServerVersion asks the ADB server for its internal version number. +func (c *Adb) ServerVersion() (int, error) { resp, err := roundTripSingleResponse(c.server, "host:version") if err != nil { return 0, wrapClientError(err, c, "GetServerVersion") @@ -46,7 +76,7 @@ KillServer tells the server to quit immediately. Corresponds to the command: adb kill-server */ -func (c *HostClient) KillServer() error { +func (c *Adb) KillServer() error { conn, err := c.server.Dial() if err != nil { return wrapClientError(err, c, "KillServer") @@ -66,7 +96,7 @@ ListDeviceSerials returns the serial numbers of all attached devices. Corresponds to the command: adb devices */ -func (c *HostClient) ListDeviceSerials() ([]string, error) { +func (c *Adb) ListDeviceSerials() ([]string, error) { resp, err := roundTripSingleResponse(c.server, "host:devices") if err != nil { return nil, wrapClientError(err, c, "ListDeviceSerials") @@ -90,7 +120,7 @@ ListDevices returns the list of connected devices. Corresponds to the command: adb devices -l */ -func (c *HostClient) ListDevices() ([]*DeviceInfo, error) { +func (c *Adb) ListDevices() ([]*DeviceInfo, error) { resp, err := roundTripSingleResponse(c.server, "host:devices-l") if err != nil { return nil, wrapClientError(err, c, "ListDevices") @@ -103,7 +133,7 @@ func (c *HostClient) ListDevices() ([]*DeviceInfo, error) { return devices, nil } -func (c *HostClient) parseServerVersion(versionRaw []byte) (int, error) { +func (c *Adb) parseServerVersion(versionRaw []byte) (int, error) { versionStr := string(versionRaw) version, err := strconv.ParseInt(versionStr, 16, 32) if err != nil { diff --git a/host_client_test.go b/adb_test.go similarity index 84% rename from host_client_test.go rename to adb_test.go index 16ae26f..34726d9 100644 --- a/host_client_test.go +++ b/adb_test.go @@ -12,9 +12,9 @@ func TestGetServerVersion(t *testing.T) { Status: wire.StatusSuccess, Messages: []string{"000a"}, } - client := NewHostClient(s) + client := &Adb{s} - v, err := client.GetServerVersion() + v, err := client.ServerVersion() assert.Equal(t, "host:version", s.Requests[0]) assert.NoError(t, err) assert.Equal(t, 10, v) diff --git a/cmd/adb/main.go b/cmd/adb/main.go index 39ba8f7..d105736 100644 --- a/cmd/adb/main.go +++ b/cmd/adb/main.go @@ -65,13 +65,13 @@ var ( String() ) -var server adb.Server +var client *adb.Adb func main() { var exitCode int var err error - server, err = adb.NewServer(adb.ServerConfig{}) + client, err = adb.NewWithConfig(adb.ServerConfig{}) if err != nil { fmt.Fprintln(os.Stderr, "error:", err) os.Exit(1) @@ -100,7 +100,7 @@ func parseDevice() adb.DeviceDescriptor { } func listDevices(long bool) int { - client := adb.NewHostClient(server) + //client := adb.New(server) devices, err := client.ListDevices() if err != nil { fmt.Fprintln(os.Stderr, "error:", err) @@ -137,7 +137,7 @@ func runShellCommand(commandAndArgs []string, device adb.DeviceDescriptor) int { args = commandAndArgs[1:] } - client := adb.NewDeviceClient(server, device) + client := client.Device(device) output, err := client.RunCommand(command, args...) if err != nil { fmt.Fprintln(os.Stderr, "error:", err) @@ -159,7 +159,7 @@ func pull(showProgress bool, remotePath, localPath string, device adb.DeviceDesc localPath = filepath.Base(remotePath) } - client := adb.NewDeviceClient(server, device) + client := client.Device(device) info, err := client.Stat(remotePath) if util.HasErrCode(err, util.FileNoExistError) { @@ -232,7 +232,7 @@ func push(showProgress bool, localPath, remotePath string, device adb.DeviceDesc } defer localFile.Close() - client := adb.NewDeviceClient(server, device) + client := client.Device(device) writer, err := client.OpenWrite(remotePath, perms, mtime) if err != nil { fmt.Fprintf(os.Stderr, "error opening remote file %s: %s\n", remotePath, err) diff --git a/cmd/demo/demo.go b/cmd/demo/demo.go index fa0ff42..b561f72 100644 --- a/cmd/demo/demo.go +++ b/cmd/demo/demo.go @@ -15,25 +15,23 @@ import ( var ( port = flag.Int("p", adb.AdbPort, "") - server adb.Server + client *adb.Adb ) func main() { flag.Parse() var err error - server, err = adb.NewServer(adb.ServerConfig{ + client, err = adb.NewWithConfig(adb.ServerConfig{ Port: *port, }) if err != nil { log.Fatal(err) } fmt.Println("Starting server…") - server.Start() + client.StartServer() - client := adb.NewHostClient(server) - - serverVersion, err := client.GetServerVersion() + serverVersion, err := client.ServerVersion() if err != nil { log.Fatal(err) } @@ -62,7 +60,7 @@ func main() { fmt.Println() fmt.Println("Watching for device state changes.") - watcher := adb.NewDeviceWatcher(server) + watcher := client.NewDeviceWatcher() for event := range watcher.C() { fmt.Printf("\t[%s]%+v\n", time.Now(), event) } @@ -88,22 +86,22 @@ func printErr(err error) { } func PrintDeviceInfoAndError(descriptor adb.DeviceDescriptor) { - device := adb.NewDeviceClient(server, descriptor) + device := client.Device(descriptor) if err := PrintDeviceInfo(device); err != nil { log.Println(err) } } -func PrintDeviceInfo(device *adb.DeviceClient) error { - serialNo, err := device.GetSerial() +func PrintDeviceInfo(device *adb.Device) error { + serialNo, err := device.Serial() if err != nil { return err } - devPath, err := device.GetDevicePath() + devPath, err := device.DevicePath() if err != nil { return err } - state, err := device.GetState() + state, err := device.State() if err != nil { return err } diff --git a/cmd/raw-adb/raw-adb.go b/cmd/raw-adb/raw-adb.go index 747bf28..71b00ef 100644 --- a/cmd/raw-adb/raw-adb.go +++ b/cmd/raw-adb/raw-adb.go @@ -14,7 +14,7 @@ import ( "github.com/zach-klippenstein/goadb/wire" ) -var port = flag.Int("p", adb.AdbPort, "port the adb server is listening on") +var port = flag.Int("p", adb.AdbPort, "`port` the adb server is listening on") func main() { flag.Parse() @@ -49,7 +49,7 @@ func readLine() string { } func doCommand(cmd string) error { - server, err := adb.NewServer(adb.ServerConfig{ + server, err := adb.NewWithConfig(adb.ServerConfig{ Port: *port, }) if err != nil { diff --git a/device_client.go b/device.go similarity index 77% rename from device_client.go rename to device.go index 1da8fd6..6a6b2a1 100644 --- a/device_client.go +++ b/device.go @@ -15,61 +15,55 @@ import ( // method is called. var MtimeOfClose = time.Time{} -// DeviceClient communicates with a specific Android device. -type DeviceClient struct { - server Server +// Device communicates with a specific Android device. +// To get an instance, call Device() on an Adb. +type Device struct { + server server descriptor DeviceDescriptor // Used to get device info. deviceListFunc func() ([]*DeviceInfo, error) } -func NewDeviceClient(server Server, descriptor DeviceDescriptor) *DeviceClient { - return &DeviceClient{ - server: server, - descriptor: descriptor, - deviceListFunc: NewHostClient(server).ListDevices, - } -} - -func (c *DeviceClient) String() string { +func (c *Device) String() string { return c.descriptor.String() } -// get-product is documented, but not implemented in the server. -// TODO(z): Make getProduct exported if get-product is ever implemented in adb. -func (c *DeviceClient) getProduct() (string, error) { +// get-product is documented, but not implemented, in the server. +// TODO(z): Make product exported if get-product is ever implemented in adb. +func (c *Device) product() (string, error) { attr, err := c.getAttribute("get-product") - return attr, wrapClientError(err, c, "GetProduct") + return attr, wrapClientError(err, c, "Product") } -func (c *DeviceClient) GetSerial() (string, error) { +func (c *Device) Serial() (string, error) { attr, err := c.getAttribute("get-serialno") - return attr, wrapClientError(err, c, "GetSerial") + return attr, wrapClientError(err, c, "Serial") } -func (c *DeviceClient) GetDevicePath() (string, error) { +func (c *Device) DevicePath() (string, error) { attr, err := c.getAttribute("get-devpath") - return attr, wrapClientError(err, c, "GetDevicePath") + return attr, wrapClientError(err, c, "DevicePath") } -func (c *DeviceClient) GetState() (string, error) { +// State returns the connection state of the device (e.g. "device"). +func (c *Device) State() (string, error) { attr, err := c.getAttribute("get-state") - return attr, wrapClientError(err, c, "GetState") + return attr, wrapClientError(err, c, "State") } -func (c *DeviceClient) GetDeviceInfo() (*DeviceInfo, error) { +func (c *Device) DeviceInfo() (*DeviceInfo, error) { // Adb doesn't actually provide a way to get this for an individual device, // so we have to just list devices and find ourselves. - serial, err := c.GetSerial() + serial, err := c.Serial() if err != nil { return nil, wrapClientError(err, c, "GetDeviceInfo(GetSerial)") } devices, err := c.deviceListFunc() if err != nil { - return nil, wrapClientError(err, c, "GetDeviceInfo(ListDevices)") + return nil, wrapClientError(err, c, "DeviceInfo(ListDevices)") } for _, deviceInfo := range devices { @@ -79,7 +73,7 @@ func (c *DeviceClient) GetDeviceInfo() (*DeviceInfo, error) { } err = util.Errorf(util.DeviceNotFound, "device list doesn't contain serial %s", serial) - return nil, wrapClientError(err, c, "GetDeviceInfo") + return nil, wrapClientError(err, c, "DeviceInfo") } /* @@ -98,7 +92,7 @@ Source: https://android.googlesource.com/platform/system/core/+/master/adb/SERVI This method quotes the arguments for you, and will return an error if any of them contain double quotes. */ -func (c *DeviceClient) RunCommand(cmd string, args ...string) (string, error) { +func (c *Device) RunCommand(cmd string, args ...string) (string, error) { cmd, err := prepareCommandLine(cmd, args...) if err != nil { return "", wrapClientError(err, c, "RunCommand") @@ -127,7 +121,7 @@ func (c *DeviceClient) RunCommand(cmd string, args ...string) (string, error) { } /* -Remount, from the docs, +Remount, from the official adb command’s docs: Ask adbd to remount the device's filesystem in read-write mode, instead of read-only. This is usually necessary before performing an "adb sync" or "adb push" request. @@ -135,7 +129,7 @@ Remount, from the docs, that. Source: https://android.googlesource.com/platform/system/core/+/master/adb/SERVICES.TXT */ -func (c *DeviceClient) Remount() (string, error) { +func (c *Device) Remount() (string, error) { conn, err := c.dialDevice() if err != nil { return "", wrapClientError(err, c, "Remount") @@ -146,7 +140,7 @@ func (c *DeviceClient) Remount() (string, error) { return string(resp), wrapClientError(err, c, "Remount") } -func (c *DeviceClient) ListDirEntries(path string) (*DirEntries, error) { +func (c *Device) ListDirEntries(path string) (*DirEntries, error) { conn, err := c.getSyncConn() if err != nil { return nil, wrapClientError(err, c, "ListDirEntries(%s)", path) @@ -156,7 +150,7 @@ func (c *DeviceClient) ListDirEntries(path string) (*DirEntries, error) { return entries, wrapClientError(err, c, "ListDirEntries(%s)", path) } -func (c *DeviceClient) Stat(path string) (*DirEntry, error) { +func (c *Device) Stat(path string) (*DirEntry, error) { conn, err := c.getSyncConn() if err != nil { return nil, wrapClientError(err, c, "Stat(%s)", path) @@ -167,7 +161,7 @@ func (c *DeviceClient) Stat(path string) (*DirEntry, error) { return entry, wrapClientError(err, c, "Stat(%s)", path) } -func (c *DeviceClient) OpenRead(path string) (io.ReadCloser, error) { +func (c *Device) OpenRead(path string) (io.ReadCloser, error) { conn, err := c.getSyncConn() if err != nil { return nil, wrapClientError(err, c, "OpenRead(%s)", path) @@ -181,7 +175,7 @@ func (c *DeviceClient) OpenRead(path string) (io.ReadCloser, error) { // by perms if necessary, and returns a writer that writes to the file. // The files modification time will be set to mtime when the WriterCloser is closed. The zero value // is TimeOfClose, which will use the time the Close method is called as the modification time. -func (c *DeviceClient) OpenWrite(path string, perms os.FileMode, mtime time.Time) (io.WriteCloser, error) { +func (c *Device) OpenWrite(path string, perms os.FileMode, mtime time.Time) (io.WriteCloser, error) { conn, err := c.getSyncConn() if err != nil { return nil, wrapClientError(err, c, "OpenWrite(%s)", path) @@ -193,7 +187,7 @@ func (c *DeviceClient) OpenWrite(path string, perms os.FileMode, mtime time.Time // getAttribute returns the first message returned by the server by running // :, where host-prefix is determined from the DeviceDescriptor. -func (c *DeviceClient) getAttribute(attr string) (string, error) { +func (c *Device) getAttribute(attr string) (string, error) { resp, err := roundTripSingleResponse(c.server, fmt.Sprintf("%s:%s", c.descriptor.getHostPrefix(), attr)) if err != nil { @@ -202,7 +196,7 @@ func (c *DeviceClient) getAttribute(attr string) (string, error) { return string(resp), nil } -func (c *DeviceClient) getSyncConn() (*wire.SyncConn, error) { +func (c *Device) getSyncConn() (*wire.SyncConn, error) { conn, err := c.dialDevice() if err != nil { return nil, err @@ -221,7 +215,7 @@ func (c *DeviceClient) getSyncConn() (*wire.SyncConn, error) { // dialDevice switches the connection to communicate directly with the device // by requesting the transport defined by the DeviceDescriptor. -func (c *DeviceClient) dialDevice() (*wire.Conn, error) { +func (c *Device) dialDevice() (*wire.Conn, error) { conn, err := c.server.Dial() if err != nil { return nil, err diff --git a/device_client_test.go b/device_test.go similarity index 87% rename from device_client_test.go rename to device_test.go index d016e38..5df7416 100644 --- a/device_client_test.go +++ b/device_test.go @@ -13,7 +13,7 @@ func TestGetAttribute(t *testing.T) { Status: wire.StatusSuccess, Messages: []string{"value"}, } - client := NewDeviceClient(s, DeviceWithSerial("serial")) + client := (&Adb{s}).Device(DeviceWithSerial("serial")) v, err := client.getAttribute("attr") assert.Equal(t, "host-serial:serial:attr", s.Requests[0]) @@ -36,31 +36,28 @@ func TestGetDeviceInfo(t *testing.T) { } client := newDeviceClientWithDeviceLister("abc", deviceLister) - device, err := client.GetDeviceInfo() + device, err := client.DeviceInfo() assert.NoError(t, err) assert.Equal(t, "Foo", device.Product) client = newDeviceClientWithDeviceLister("def", deviceLister) - device, err = client.GetDeviceInfo() + device, err = client.DeviceInfo() assert.NoError(t, err) assert.Equal(t, "Bar", device.Product) client = newDeviceClientWithDeviceLister("serial", deviceLister) - device, err = client.GetDeviceInfo() + device, err = client.DeviceInfo() assert.True(t, util.HasErrCode(err, util.DeviceNotFound)) assert.EqualError(t, err.(*util.Err).Cause, "DeviceNotFound: device list doesn't contain serial serial") assert.Nil(t, device) } -func newDeviceClientWithDeviceLister(serial string, deviceLister func() ([]*DeviceInfo, error)) *DeviceClient { - client := NewDeviceClient( - &MockServer{ - Status: wire.StatusSuccess, - Messages: []string{serial}, - }, - DeviceWithSerial(serial), - ) +func newDeviceClientWithDeviceLister(serial string, deviceLister func() ([]*DeviceInfo, error)) *Device { + client := (&Adb{&MockServer{ + Status: wire.StatusSuccess, + Messages: []string{serial}, + }}).Device(DeviceWithSerial(serial)) client.deviceListFunc = deviceLister return client } @@ -70,7 +67,7 @@ func TestRunCommandNoArgs(t *testing.T) { Status: wire.StatusSuccess, Messages: []string{"output"}, } - client := NewDeviceClient(s, AnyDevice()) + client := (&Adb{s}).Device(AnyDevice()) v, err := client.RunCommand("cmd") assert.Equal(t, "host:transport-any", s.Requests[0]) diff --git a/device_watcher.go b/device_watcher.go index ad7a0e1..fe1866e 100644 --- a/device_watcher.go +++ b/device_watcher.go @@ -59,7 +59,7 @@ var deviceStateStrings = map[string]DeviceState{ } type deviceWatcherImpl struct { - server Server + server server // If an error occurs, it is stored here and eventChan is close immediately after. err atomic.Value @@ -67,7 +67,7 @@ type deviceWatcherImpl struct { eventChan chan DeviceStateChangedEvent } -func NewDeviceWatcher(server Server) *DeviceWatcher { +func newDeviceWatcher(server server) *DeviceWatcher { watcher := &DeviceWatcher{&deviceWatcherImpl{ server: server, eventChan: make(chan DeviceStateChangedEvent), @@ -165,7 +165,7 @@ func publishDevices(watcher *deviceWatcherImpl) { } } -func connectToTrackDevices(server Server) (wire.Scanner, error) { +func connectToTrackDevices(server server) (wire.Scanner, error) { conn, err := server.Dial() if err != nil { return nil, err diff --git a/server.go b/server.go index 82ecd17..2dc83b1 100644 --- a/server.go +++ b/server.go @@ -30,15 +30,17 @@ type ServerConfig struct { // 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 { +type server interface { Start() error Dial() (*wire.Conn, error) } -func roundTripSingleResponse(s Server, req string) ([]byte, error) { +func roundTripSingleResponse(s server, req string) ([]byte, error) { conn, err := s.Dial() if err != nil { return nil, err @@ -50,18 +52,12 @@ func roundTripSingleResponse(s Server, req string) ([]byte, error) { type realServer struct { config ServerConfig - fs *filesystem // Caches Host:Port so they don't have to be concatenated for every dial. address string } -// NewServer creates a new Server instance. -func NewServer(config ServerConfig) (Server, error) { - return newServer(config, localFilesystem) -} - -func newServer(config ServerConfig, fs *filesystem) (Server, error) { +func newServer(config ServerConfig) (server, error) { if config.Dialer == nil { config.Dialer = tcpDialer{} } @@ -73,20 +69,23 @@ func newServer(config ServerConfig, fs *filesystem) (Server, error) { config.Port = AdbPort } + if config.fs == nil { + config.fs = localFilesystem + } + if config.PathToAdb == "" { - path, err := fs.LookPath(AdbExecutableName) + path, err := config.fs.LookPath(AdbExecutableName) if err != nil { return nil, util.WrapErrorf(err, util.ServerNotAvailable, "could not find %s in PATH", AdbExecutableName) } config.PathToAdb = path } - if err := fs.IsExecutableFile(config.PathToAdb); err != nil { + if err := config.fs.IsExecutableFile(config.PathToAdb); err != nil { return nil, util.WrapErrorf(err, util.ServerNotAvailable, "invalid adb executable: %s", config.PathToAdb) } return &realServer{ config: config, - fs: fs, address: fmt.Sprintf("%s:%d", config.Host, config.Port), }, nil } @@ -111,7 +110,7 @@ func (s *realServer) Dial() (*wire.Conn, error) { // StartServer ensures there is a server running. func (s *realServer) Start() error { - output, err := s.fs.CmdCombinedOutput(s.config.PathToAdb, "start-server") + output, err := s.config.fs.CmdCombinedOutput(s.config.PathToAdb, "start-server") outputStr := strings.TrimSpace(string(output)) return util.WrapErrorf(err, util.ServerNotAvailable, "error starting server: %s\noutput:\n%s", err, outputStr) } diff --git a/server_mock_test.go b/server_mock_test.go index af7012e..0d13a57 100644 --- a/server_mock_test.go +++ b/server_mock_test.go @@ -28,7 +28,7 @@ type MockServer struct { Trace []string } -var _ Server = &MockServer{} +var _ server = &MockServer{} func (s *MockServer) Dial() (*wire.Conn, error) { s.logMethod("Dial") diff --git a/server_test.go b/server_test.go index c52ff53..a193f23 100644 --- a/server_test.go +++ b/server_test.go @@ -9,8 +9,7 @@ import ( ) func TestNewServer_ZeroConfig(t *testing.T) { - config := ServerConfig{} - fs := &filesystem{ + config := ServerConfig{fs: &filesystem{ LookPath: func(name string) (string, error) { if name == AdbExecutableName { return "/bin/adb", nil @@ -23,9 +22,9 @@ func TestNewServer_ZeroConfig(t *testing.T) { } return fmt.Errorf("wrong path: %s", path) }, - } + }} - serverIf, err := newServer(config, fs) + serverIf, err := newServer(config) server := serverIf.(*realServer) assert.NoError(t, err) assert.IsType(t, tcpDialer{}, server.config.Dialer) @@ -47,17 +46,17 @@ func TestNewServer_CustomConfig(t *testing.T) { Host: "foobar", Port: 1, PathToAdb: "/bin/adb", - } - fs := &filesystem{ - IsExecutableFile: func(path string) error { - if path == "/bin/adb" { - return nil - } - return fmt.Errorf("wrong path: %s", path) + fs: &filesystem{ + IsExecutableFile: func(path string) error { + if path == "/bin/adb" { + return nil + } + return fmt.Errorf("wrong path: %s", path) + }, }, } - serverIf, err := newServer(config, fs) + serverIf, err := newServer(config) server := serverIf.(*realServer) assert.NoError(t, err) assert.IsType(t, MockDialer{}, server.config.Dialer) @@ -68,13 +67,12 @@ func TestNewServer_CustomConfig(t *testing.T) { } func TestNewServer_AdbNotFound(t *testing.T) { - config := ServerConfig{} - fs := &filesystem{ + config := ServerConfig{fs: &filesystem{ LookPath: func(name string) (string, error) { return "", fmt.Errorf("executable not found: %s", name) }, - } + }} - _, err := newServer(config, fs) + _, err := newServer(config) assert.EqualError(t, err, "ServerNotAvailable: could not find adb in PATH") } diff --git a/wire/doc.go b/wire/doc.go index 887013f..b16d981 100644 --- a/wire/doc.go +++ b/wire/doc.go @@ -2,7 +2,7 @@ Package wire implements the low-level part of the client/server wire protocol. It also implements the "sync" wire format for file transfers. -This package is not intended to be used directly. adb.HostClient and adb.DeviceClient +This package is not intended to be used directly. adb.Adb and adb.Device use it to abstract away the bit-twiddling details of the protocol. You should only ever need to work with the goadb package. Also, this package's API may change more frequently than goadb's.