diff --git a/src/se/vidstige/jadb/SyncTransport.java b/src/se/vidstige/jadb/SyncTransport.java index 824e0bc..55f2f9f 100644 --- a/src/se/vidstige/jadb/SyncTransport.java +++ b/src/se/vidstige/jadb/SyncTransport.java @@ -19,8 +19,9 @@ public class SyncTransport { public void send(String syncCommand, String name) throws IOException { if (syncCommand.length() != 4) throw new IllegalArgumentException("sync commands must have length 4"); output.writeBytes(syncCommand); - output.writeInt(Integer.reverseBytes(name.length())); - output.writeBytes(name); + byte[] data = name.getBytes(StandardCharsets.UTF_8); + output.writeInt(Integer.reverseBytes(data.length)); + output.write(data); } public void sendStatus(String statusCode, int length) throws IOException { @@ -50,6 +51,21 @@ public class SyncTransport { return new String(buffer, StandardCharsets.UTF_8); } + public void sendDirectoryEntry(RemoteFile file) throws IOException { + output.writeBytes("DENT"); + output.writeInt(Integer.reverseBytes(0666 | (file.isDirectory() ? (1 << 14) : 0))); + output.writeInt(Integer.reverseBytes(file.getSize())); + output.writeInt(Integer.reverseBytes(Long.valueOf(file.getLastModified()).intValue())); + byte[] pathChars = file.getPath().getBytes(StandardCharsets.UTF_8); + output.writeInt(Integer.reverseBytes(pathChars.length)); + output.write(pathChars); + } + + public void sendDirectoryEntryDone() throws IOException { + output.writeBytes("DONE"); + output.writeBytes("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); // equivalent to the length of a "normal" dent + } + public RemoteFileRecord readDirectoryEntry() throws IOException { String id = readString(4); int mode = readInt(); diff --git a/src/se/vidstige/jadb/server/AdbDeviceResponder.java b/src/se/vidstige/jadb/server/AdbDeviceResponder.java index 459632c..b019f42 100644 --- a/src/se/vidstige/jadb/server/AdbDeviceResponder.java +++ b/src/se/vidstige/jadb/server/AdbDeviceResponder.java @@ -7,6 +7,7 @@ import java.io.ByteArrayOutputStream; import java.io.DataInput; import java.io.DataOutputStream; import java.io.IOException; +import java.util.List; /** * Created by vidstige on 20/03/14. @@ -19,4 +20,6 @@ public interface AdbDeviceResponder { void filePulled(RemoteFile path, ByteArrayOutputStream buffer) throws JadbException, IOException; void shell(String command, DataOutputStream stdout, DataInput stdin) throws IOException; + + List list(String path) throws IOException; } diff --git a/src/se/vidstige/jadb/server/AdbProtocolHandler.java b/src/se/vidstige/jadb/server/AdbProtocolHandler.java index cfb36da..7112a51 100644 --- a/src/se/vidstige/jadb/server/AdbProtocolHandler.java +++ b/src/se/vidstige/jadb/server/AdbProtocolHandler.java @@ -175,6 +175,8 @@ class AdbProtocolHandler implements Runnable { syncSend(output, input, length); } else if ("RECV".equals(id)) { syncRecv(output, input, length); + } else if ("LIST".equals(id)) { + syncList(output, input, length); } else throw new JadbException("Unknown sync id " + id); } catch (JadbException e) { // sync response with a different type of fail message SyncTransport sync = getSyncTransport(output, input); @@ -207,6 +209,15 @@ class AdbProtocolHandler implements Runnable { transport.sendStatus("OKAY", 0); // 0 = ignored } + private void syncList(DataOutput output, DataInput input, int length) throws IOException, JadbException { + String remotePath = readString(input, length); + SyncTransport transport = getSyncTransport(output, input); + for (RemoteFile file : selected.list(remotePath)) { + transport.sendDirectoryEntry(file); + } + transport.sendDirectoryEntryDone(); + } + private String getCommandLength(String command) { return String.format("%04x", command.length()); } diff --git a/test/se/vidstige/jadb/test/fakes/FakeAdbServer.java b/test/se/vidstige/jadb/test/fakes/FakeAdbServer.java index c817baa..bc1b7ab 100644 --- a/test/se/vidstige/jadb/test/fakes/FakeAdbServer.java +++ b/test/se/vidstige/jadb/test/fakes/FakeAdbServer.java @@ -13,6 +13,7 @@ import java.io.IOException; import java.net.ProtocolException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -86,6 +87,10 @@ public class FakeAdbServer implements AdbResponder { return findBySerial(serial).expectShell(commands); } + public DeviceResponder.ListExpectation expectList(String serial, String remotePath) { + return findBySerial(serial).expectList(remotePath); + } + @Override public List getDevices() { return new ArrayList(devices); @@ -96,6 +101,7 @@ public class FakeAdbServer implements AdbResponder { private final String type; private List fileExpectations = new ArrayList<>(); private List shellExpectations = new ArrayList<>(); + private List listExpectations = new ArrayList<>(); private DeviceResponder(String serial, String type) { this.serial = serial; @@ -150,9 +156,21 @@ public class FakeAdbServer implements AdbResponder { throw new ProtocolException("Unexpected shell to device " + serial + ": " + command); } + @Override + public List list(String path) throws IOException { + for (ListExpectation le : listExpectations) { + if (le.matches(path)) { + listExpectations.remove(le); + return le.getFiles(); + } + } + throw new ProtocolException("Unexpected list of device " + serial + " in dir " + path); + } + public void verifyExpectations() { org.junit.Assert.assertEquals(0, fileExpectations.size()); org.junit.Assert.assertEquals(0, shellExpectations.size()); + org.junit.Assert.assertEquals(0, listExpectations.size()); } private static class FileExpectation implements ExpectationBuilder { @@ -161,7 +179,6 @@ public class FakeAdbServer implements AdbResponder { private String failMessage; public FileExpectation(RemoteFile path) { - this.path = path; content = null; failMessage = null; @@ -220,6 +237,62 @@ public class FakeAdbServer implements AdbResponder { } } + public static class ListExpectation { + + private final String remotePath; + private final List files = new ArrayList<>(); + + public ListExpectation(String remotePath) { + this.remotePath = remotePath; + } + + public boolean matches(String remotePath) { + return remotePath.equals(this.remotePath); + } + + public ListExpectation withFile(String path, int size, long modifyTime) { + files.add(new MockFileEntry(path, size, modifyTime, false)); + return this; + } + + public ListExpectation withDir(String path, long modifyTime) { + files.add(new MockFileEntry(path, -1, modifyTime, true)); + return this; + } + + public List getFiles() { + return Collections.unmodifiableList(files); + } + + private static class MockFileEntry extends RemoteFile { + + private final int size; + private final long modifyTime; + private final boolean dir; + + MockFileEntry(String path, int size, long modifyTime, boolean dir) { + super(path); + this.size = size; + this.modifyTime = modifyTime; + this.dir = dir; + } + + public int getSize() { + return size; + } + + public long getLastModified() { + return modifyTime; + } + + public boolean isDirectory() { + return dir; + } + + } + + } + public ExpectationBuilder expectPush(RemoteFile path) { FileExpectation expectation = new FileExpectation(path); fileExpectations.add(expectation); @@ -237,5 +310,11 @@ public class FakeAdbServer implements AdbResponder { shellExpectations.add(expectation); return expectation; } + + public ListExpectation expectList(String remotePath) { + ListExpectation expectation = new ListExpectation(remotePath); + listExpectations.add(expectation); + return expectation; + } } } diff --git a/test/se/vidstige/jadb/test/unit/MockedTestCases.java b/test/se/vidstige/jadb/test/unit/MockedTestCases.java index 87d16a9..4c1edb7 100644 --- a/test/se/vidstige/jadb/test/unit/MockedTestCases.java +++ b/test/se/vidstige/jadb/test/unit/MockedTestCases.java @@ -16,6 +16,7 @@ import java.nio.charset.StandardCharsets; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.List; public class MockedTestCases { @@ -117,8 +118,59 @@ public class MockedTestCases { device.executeShell("echo", "newline2\r\nstring"); } - private long parseDate(String date) throws ParseException { + @Test + public void testFileList() throws Exception { + server.add("serial-123"); + server.expectList("serial-123", "/sdcard/Documents") + .withDir("school", 123456789L) + .withDir("finances", 7070707L) + .withDir("\u904A\u6232", 528491L) + .withFile("user_manual.pdf", 3000, 648649L) + .withFile("effective java vol. 7.epub", 0xCAFE, 0xBABEL) + .withFile("\uB9AC\uADF8 \uC624\uBE0C \uB808\uC804\uB4DC", 240, 9001L); + JadbDevice device = connection.getDevices().get(0); + List files = device.list("/sdcard/Documents"); + Assert.assertEquals(6, files.size()); + assertHasDir("school", 123456789L, files); + assertHasDir("finances", 7070707L, files); + assertHasDir("\u904A\u6232", 528491L, files); + assertHasFile("user_manual.pdf", 3000, 648649L, files); + assertHasFile("effective java vol. 7.epub", 0xCAFE, 0xBABEL, files); + assertHasFile("\uB9AC\uADF8 \uC624\uBE0C \uB808\uC804\uB4DC", 240, 9001L, files); + } + + private static long parseDate(String date) throws ParseException { DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); return dateFormat.parse(date).getTime(); } + + private static void assertHasFile(String expPath, int expSize, long expModifyTime, List actualFiles) { + for (RemoteFile file : actualFiles) { + if (expPath.equals(file.getPath())) { + if (file.isDirectory()) { + Assert.fail("File " + expPath + " was listed as a dir!"); + } else if (expSize != file.getSize() || expModifyTime != file.getLastModified()) { + Assert.fail("File " + expPath + " exists but has incorrect properties!"); + } else { + return; + } + } + } + Assert.fail("File " + expPath + " could not be found!"); + } + + private static void assertHasDir(String expPath, long expModifyTime, List actualFiles) { + for (RemoteFile file : actualFiles) { + if (expPath.equals(file.getPath())) { + if (!file.isDirectory()) { + Assert.fail("Dir " + expPath + " was listed as a file!"); + } else if (expModifyTime != file.getLastModified()) { + Assert.fail("Dir " + expPath + " exists but has incorrect properties!"); + } else { + return; + } + } + } + Assert.fail("Dir " + expPath + " could not be found!"); + } }