goadb/adb/sync_file_reader.go
2024-06-11 21:25:58 +05:00

109 lines
2.7 KiB
Go

package adb
import (
"io"
"github.com/timoxa0/goadb/internal/errors"
"github.com/timoxa0/goadb/wire"
)
// syncFileReader wraps a SyncConn that has requested to receive a file.
type syncFileReader struct {
// Reader used to read data from the adb connection.
scanner wire.SyncScanner
// Reader for the current chunk only.
chunkReader io.Reader
// False until the DONE chunk is encountered.
eof bool
}
var _ io.ReadCloser = &syncFileReader{}
func newSyncFileReader(s wire.SyncScanner) (r io.ReadCloser, err error) {
r = &syncFileReader{
scanner: s,
}
// Read the header for the first chunk to consume any errors.
if _, err = r.Read([]byte{}); err != nil {
if err == io.EOF {
// EOF means the file was empty. This still means the file was opened successfully,
// and the next time the caller does a read they'll get the EOF and handle it themselves.
err = nil
} else {
r.Close()
return nil, err
}
}
return
}
func (r *syncFileReader) Read(buf []byte) (n int, err error) {
if r.eof {
return 0, io.EOF
}
if r.chunkReader == nil {
chunkReader, err := readNextChunk(r.scanner)
if err != nil {
if err == io.EOF {
// We just read the last chunk, set our flag before passing it up.
r.eof = true
}
return 0, err
}
r.chunkReader = chunkReader
}
if len(buf) == 0 {
// Read can be called with an empty buffer to read the next chunk and check for errors.
// However, net.Conn.Read seems to return EOF when given an empty buffer, so we need to
// handle that case ourselves.
return 0, nil
}
n, err = r.chunkReader.Read(buf)
if err == io.EOF {
// End of current chunk, don't return an error, the next chunk will be
// read on the next call to this method.
r.chunkReader = nil
return n, nil
}
return n, err
}
func (r *syncFileReader) Close() error {
return r.scanner.Close()
}
// readNextChunk creates an io.LimitedReader for the next chunk of data,
// and returns io.EOF if the last chunk has been read.
func readNextChunk(r wire.SyncScanner) (io.Reader, error) {
status, err := r.ReadStatus("read-chunk")
if err != nil {
if wire.IsAdbServerErrorMatching(err, readFileNotFoundPredicate) {
return nil, errors.Errorf(errors.FileNoExistError, "no such file or directory")
}
return nil, err
}
switch status {
case wire.StatusSyncData:
return r.ReadBytes()
case wire.StatusSyncDone:
return nil, io.EOF
default:
return nil, errors.Errorf(errors.AssertionError, "expected chunk id '%s' or '%s', but got '%s'",
wire.StatusSyncData, wire.StatusSyncDone, []byte(status))
}
}
// readFileNotFoundPredicate returns true if s is the adb server error message returned
// when trying to open a file that doesn't exist.
func readFileNotFoundPredicate(s string) bool {
return s == "No such file or directory"
}