/* * Copyright (C) 2013 RoboVM AB * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>. */ package org.robovm.libimobiledevice; import static org.junit.Assert.*; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Random; import org.apache.commons.io.FileUtils; import org.junit.AfterClass; import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.robovm.libimobiledevice.binding.AfcError; import org.robovm.libimobiledevice.binding.AfcFileMode; import org.robovm.libimobiledevice.binding.AfcLinkType; import org.robovm.libimobiledevice.binding.LibIMobileDeviceConstants; /** * Tests {@link AfcClient}. */ public class AfcClientTest { static AfcClient client; static IDevice device; static LockdowndClient lockdowndClient; @BeforeClass public static void beforeClass() { if (NativeLibrary.supportedPlatform) { String[] udids = IDevice.listUdids(); if (udids.length > 0) { String udid = udids[0]; device = new IDevice(udid); lockdowndClient = new LockdowndClient(device, null, true); LockdowndServiceDescriptor service = lockdowndClient.startService(AfcClient.SERVICE_NAME); client = new AfcClient(device, service); } } } @AfterClass public static void cleanup() { if (client != null) { client.dispose(); client = null; } if (lockdowndClient != null) { lockdowndClient.dispose(); lockdowndClient = null; } if (device != null) { device.dispose(); device = null; } } @Before public void setUp() { Assume.assumeTrue(client != null); } @Test public void testReadDirectory() { List<String> dirs = Arrays.asList(client.readDirectory("/")); System.out.println(dirs); assertFalse(dirs.isEmpty()); assertTrue(dirs.contains("Books")); assertTrue(dirs.contains("DCIM")); assertTrue(dirs.contains("Safari")); } @Test public void testGetDeviceInfo() throws Exception { Map<String, String> devInfo = client.getDeviceInfo(); assertFalse(devInfo.isEmpty()); assertTrue(devInfo.containsKey(AfcClient.DEVICE_INFO_KEY_FS_BLOCK_SIZE)); assertTrue(devInfo.containsKey(AfcClient.DEVICE_INFO_KEY_FS_FREE_BYTES)); assertTrue(devInfo.containsKey(AfcClient.DEVICE_INFO_KEY_FS_TOTAL_BYTES)); assertTrue(devInfo.containsKey(AfcClient.DEVICE_INFO_KEY_MODEL)); assertEquals(devInfo.get(AfcClient.DEVICE_INFO_KEY_FS_BLOCK_SIZE), "" + client.getBlockSize()); assertEquals(devInfo.get(AfcClient.DEVICE_INFO_KEY_FS_FREE_BYTES), "" + client.getFreeBytes()); assertEquals(devInfo.get(AfcClient.DEVICE_INFO_KEY_FS_TOTAL_BYTES), "" + client.getTotalBytes()); assertEquals(devInfo.get(AfcClient.DEVICE_INFO_KEY_MODEL), "" + client.getModel()); } // @Test // @Ignore // public void testGetFileInfo() throws Exception { // list(client, "/", "", "", System.out); // } private void list() { list("/", "", "", new PrintWriter(System.out)); } private void list(String path, String filename, String indent, PrintWriter out) { Map<String, String> info = client.getFileInfo(path); if ("S_IFDIR".equals(info.get(AfcClient.FILE_INFO_KEY_ST_IFMT))) { out.println(indent + filename + "/"); out.flush(); for (String f : client.readDirectory(path)) { if (f.equals("..") || f.equals(".")) { continue; } String childPath = path + "/" + f; list(childPath, f, indent + " ", out); } } else if ("S_IFLNK".equals(info.get(AfcClient.FILE_INFO_KEY_ST_IFMT))) { out.println(indent + filename + " -> " + info.get(AfcClient.FILE_INFO_KEY_LINK_TARGET)); out.flush(); } else { out.println(indent + filename + " " + info.get(AfcClient.FILE_INFO_KEY_ST_SIZE) + " (" + info.get(AfcClient.FILE_INFO_KEY_ST_IFMT) + ")"); out.flush(); } } @Test public void testReadWriteRemove() throws Exception { byte[] buffer = new byte[4096]; int n = 0; long seed = System.currentTimeMillis(); int size = 1 << 20; long fd = 0; fd = client.fileOpen("/FOO.TXT", AfcFileMode.AFC_FOPEN_WRONLY); RandomInputStream ris = new RandomInputStream(size, seed); while ((n = ris.read(buffer)) != -1) { client.fileWrite(fd, buffer, 0, n); } ris.close(); client.fileClose(fd); Map<String, String> fileInfo = client.getFileInfo("/FOO.TXT"); assertEquals("" + size, fileInfo.get(AfcClient.FILE_INFO_KEY_ST_SIZE)); Random random = new Random(seed); fd = client.fileOpen("/FOO.TXT", AfcFileMode.AFC_FOPEN_RDONLY); int totalRead = 0; while ((n = client.fileRead(fd, buffer, 0, buffer.length)) != -1) { for (int i = 0; i < n; i++) { assertEquals(nextCharAZ(random), buffer[i] & 0xff); } totalRead += n; } client.fileClose(fd); assertEquals(size, totalRead); client.removePath("/FOO.TXT"); try { client.getFileInfo("/FOO.TXT"); fail("LibIMobileDeviceException expected"); } catch (LibIMobileDeviceException e) { assertEquals(AfcError.AFC_E_OBJECT_NOT_FOUND.swigValue(), e.getErrorCode()); } } @Test public void testWriteSpeed() throws Exception { byte[] buffer = new byte[64 * 1024]; long fd = client.fileOpen("/FOO.DAT", AfcFileMode.AFC_FOPEN_WRONLY); int n = 0; long start = System.currentTimeMillis(); while (n < (1 << 24)) { n += client.fileWrite(fd, buffer, 0, buffer.length); } long duration = System.currentTimeMillis() - start; client.fileClose(fd); System.out.format("%d bytes written in %d ms (%f kB/s)\n", n, duration, n / 1024.0 / (duration / 1000.0)); client.removePath("/FOO.DAT"); } @Test public void testMakeDirectory() throws Exception { client.makeDirectory("/FOO"); client.makeDirectory("/FOO/BAR"); client.makeDirectory("/FOO/BAR/WOOZ/BAZ"); assertEquals("S_IFDIR", client.getFileInfo("/FOO/BAR/WOOZ/BAZ").get(AfcClient.FILE_INFO_KEY_ST_IFMT)); try { client.removePath("/FOO"); fail("LibIMobileDeviceException expected"); } catch (LibIMobileDeviceException e) { assertEquals(AfcError.AFC_E_DIR_NOT_EMPTY.swigValue(), e.getErrorCode()); } client.removePath("/FOO", true); try { client.getFileInfo("/FOO"); fail("LibIMobileDeviceException expected"); } catch (LibIMobileDeviceException e) { assertEquals(AfcError.AFC_E_OBJECT_NOT_FOUND.swigValue(), e.getErrorCode()); } } @Test public void testMakeLink() throws Exception { try { client.makeDirectory("/FOOBAR"); client.makeLink(AfcLinkType.AFC_SYMLINK, "../Safari", "/FOOBAR/KAZ"); assertEquals("S_IFLNK", client.getFileInfo("/FOOBAR/KAZ").get(AfcClient.FILE_INFO_KEY_ST_IFMT)); assertEquals("../Safari", client.getFileInfo("/FOOBAR/KAZ").get(AfcClient.FILE_INFO_KEY_LINK_TARGET)); } finally { try { client.removePath("/FOOBAR", true); } catch (LibIMobileDeviceException e) {} } } @Test public void testUpload() throws Exception { Path dir = Files.createTempDirectory(AfcClientTest.class.getSimpleName()); try { Path foo = Files.createDirectory(dir.resolve("foo")); Path bar = Files.createDirectory(foo.resolve("bar")); Path wooz = Files.createFile(bar.resolve("wooz")); Files.createSymbolicLink(foo.resolve("rooz"), foo.relativize(wooz)); try { client.upload(foo.toFile(), "/baz"); StringWriter sw = new StringWriter(); list("/baz", "baz", "", new PrintWriter(sw)); assertEquals( "baz/\n" + " foo/\n" + " bar/\n" + " wooz 0 (S_IFREG)\n" + " rooz -> bar/wooz\n", sw.toString()); } finally { client.removePath("/baz", true); } } finally { FileUtils.deleteDirectory(dir.toFile()); } } @Test public void testUploadSpeed() throws Exception { Path dir = Files.createTempDirectory(AfcClientTest.class.getSimpleName()); try { Path foo = Files.createFile(dir.resolve("foo")); byte[] data = new byte[1 << 24]; Files.write(foo, data); long start = System.currentTimeMillis(); try { client.upload(foo.toFile(), "/wooz"); } finally { long duration = System.currentTimeMillis() - start; System.out.format("%d bytes uploaded in %d ms (%f kB/s)\n", data.length, duration, data.length / 1024.0 / (duration / 1000.0)); client.removePath("/wooz", true); } } finally { FileUtils.deleteDirectory(dir.toFile()); } } private static int nextCharAZ(Random r) { return r.nextInt('z' - 'a') + 'a'; } private static class RandomInputStream extends InputStream { private final int size; private final Random random; private int position = 0; RandomInputStream(int size, long seed) { this.size = size; random = new Random(seed); } @Override public int read() throws IOException { if (position < size) { position++; return nextCharAZ(random); } return -1; } } }