All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robovm.libimobiledevice.AfcClient Maven / Gradle / Ivy

/*
 * 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 .
 */
package org.robovm.libimobiledevice;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.TreeMap;

import org.robovm.libimobiledevice.binding.AfcClientRef;
import org.robovm.libimobiledevice.binding.AfcClientRefOut;
import org.robovm.libimobiledevice.binding.AfcError;
import org.robovm.libimobiledevice.binding.AfcFileMode;
import org.robovm.libimobiledevice.binding.AfcLinkType;
import org.robovm.libimobiledevice.binding.IntOut;
import org.robovm.libimobiledevice.binding.LibIMobileDevice;
import org.robovm.libimobiledevice.binding.LibIMobileDeviceConstants;
import org.robovm.libimobiledevice.binding.LockdowndServiceDescriptorStruct;
import org.robovm.libimobiledevice.binding.LongOut;
import org.robovm.libimobiledevice.binding.StringArray;
import org.robovm.libimobiledevice.binding.StringArrayOut;

/**
 * Provides access to the device filesystem.
 */
public class AfcClient implements AutoCloseable {
    
    public static final String SERVICE_NAME = LibIMobileDeviceConstants.AFC_SERVICE_NAME;
    
    public static final String DEVICE_INFO_KEY_FS_TOTAL_BYTES = "FSTotalBytes";
    public static final String DEVICE_INFO_KEY_FS_FREE_BYTES = "FSFreeBytes";
    public static final String DEVICE_INFO_KEY_FS_BLOCK_SIZE = "FSBlockSize";
    public static final String DEVICE_INFO_KEY_MODEL = "Model";
    /**
     * Creation time in nanos.
     */
    public static final String FILE_INFO_KEY_ST_BIRTHTIME = "st_birthtime";
    /**
     * Last modification time in nanos.
     */
    public static final String FILE_INFO_KEY_ST_MTIME = "st_mtime";
    /**
     * Number of blocks allocated for a file.
     */
    public static final String FILE_INFO_KEY_ST_BLOCKS = "st_blocks";
    /**
     * Number of hard links.
     */
    public static final String FILE_INFO_KEY_ST_NLINK = "st_nlink";
    /**
     * File size in bytes.
     */
    public static final String FILE_INFO_KEY_ST_SIZE = "st_size";
    /**
     * File type. {@code S_IFREG} for regular files. {@code S_IFDIR} for 
     * directories. {@code S_IFLNK} for links.
     */
    public static final String FILE_INFO_KEY_ST_IFMT = "st_ifmt";
    /**
     * Link target.
     */
    public static final String FILE_INFO_KEY_LINK_TARGET = "LinkTarget";
    
    protected AfcClientRef ref;

    AfcClient(AfcClientRef ref) {
        this.ref = ref;
    }
    
    /**
     * Creates a new {@link AfcClient} and makes a connection to the 
     * {@code com.apple.afc} service on the device.
     * 
     * @param device the device to connect to.
     * @param service the service descriptor returned by {@link LockdowndClient#startService(String)}.
     */
    public AfcClient(IDevice device, LockdowndServiceDescriptor service) {
        if (device == null) {
            throw new NullPointerException("device");
        }
        if (service == null) {
            throw new NullPointerException("service");
        }
        AfcClientRefOut refOut = new AfcClientRefOut();
        LockdowndServiceDescriptorStruct serviceStruct = new LockdowndServiceDescriptorStruct();
        serviceStruct.setPort((short) service.getPort());
        serviceStruct.setSslEnabled(service.isSslEnabled());
        try {
            checkResult(LibIMobileDevice.afc_client_new(device.getRef(), serviceStruct, refOut));
            this.ref = refOut.getValue();
        } finally {
            serviceStruct.delete();
            refOut.delete();
        }
    }
    
    /**
     * Returns a directory listing of the specified directory.
     *
     * @param dir the directory to list. Must be a fully-qualified path.
     * @return the list of files in the specified directory.
     */
    public String[] readDirectory(String dir) {
        if (dir == null) {
            throw new NullPointerException("dir");
        }
        StringArrayOut listOut = new StringArrayOut();
        try {
            checkResult(LibIMobileDevice.afc_read_directory(getRef(), dir, listOut));
            StringArray list = listOut.getValue();
            ArrayList result = new ArrayList();
            if (list != null) {
                for (int i = 0;; i++) {
                    String s = list.get(i);
                    if (s == null) {
                        break;
                    }
                    result.add(s);
                }
            }
            return result.toArray(new String[result.size()]);
        } finally {
            LibIMobileDevice.delete_StringArray_values_z(listOut.getValue());
            listOut.delete();
        }
    }
    
    /**
     * Retrieves device information. The information returned is the device 
     * model as well as the free space, the total capacity and blocksize on the 
     * accessed disk partition.
     * 
     * @return the device info as key-value pairs. Possible keys are:
     * {@link #DEVICE_INFO_KEY_MODEL}, {@link #DEVICE_INFO_KEY_FS_FREE_BYTES},
     * {@link #DEVICE_INFO_KEY_FS_TOTAL_BYTES}, 
     * {@link #DEVICE_INFO_KEY_FS_BLOCK_SIZE}.
     */
    public Map getDeviceInfo() {
        StringArrayOut infosOut = new StringArrayOut();
        try {
            checkResult(LibIMobileDevice.afc_get_device_info(getRef(), infosOut));
            StringArray list = infosOut.getValue();
            Map result = new TreeMap();
            if (list != null) {
                int i = 0;
                while (true) {
                    String key = list.get(i++);
                    if (key == null) {
                        break;
                    }
                    String value = list.get(i++);
                    if (value == null) {
                        break;
                    }
                    result.put(key, value);
                }
            }
            return result;
        } finally {
            LibIMobileDevice.delete_StringArray_values_z(infosOut.getValue());
            infosOut.delete();
        }
    }
    
    /**
     * Returns the disk partition blocksize.
     * 
     * @return the blocksize.
     * @see #getDeviceInfo()
     */
    public int getBlockSize() {
        return Integer.parseInt(getDeviceInfo().get(DEVICE_INFO_KEY_FS_BLOCK_SIZE));
    }

    /**
     * Returns the free space on the device in bytes.
     * 
     * @return the free space in bytes.
     * @see #getDeviceInfo()
     */
    public long getFreeBytes() {
        return Long.parseLong(getDeviceInfo().get(DEVICE_INFO_KEY_FS_FREE_BYTES));
    }

    /**
     * Returns the total size of the device in bytes.
     * 
     * @return the total size in bytes.
     * @see #getDeviceInfo()
     */
    public long getTotalBytes() {
        return Long.parseLong(getDeviceInfo().get(DEVICE_INFO_KEY_FS_TOTAL_BYTES));
    }
    
    /**
     * Returns the name of the device model.
     * 
     * @return the device model name.
     * @see #getDeviceInfo()
     */
    public String getModel() {
        return getDeviceInfo().get(DEVICE_INFO_KEY_MODEL);
    }
    
    /**
     * Retrieves information for a specific file or directory.
     * 
     * @param path the path of the file or directory.
     * @return the file information as key-value pairs. Possible keys are:
     * {@link #FILE_INFO_KEY_ST_BIRTHTIME}, {@link #FILE_INFO_KEY_ST_BLOCKS},
     * {@link #FILE_INFO_KEY_ST_IFMT}, {@link #FILE_INFO_KEY_ST_MTIME},
     * {@link #FILE_INFO_KEY_ST_NLINK}, {@link #FILE_INFO_KEY_ST_SIZE}.
     */
    public Map getFileInfo(String path) {
        StringArrayOut infolistOut = new StringArrayOut();
        try {
            checkResult(LibIMobileDevice.afc_get_file_info(getRef(), path, infolistOut));
            StringArray list = infolistOut.getValue();
            Map result = new TreeMap();
            if (list != null) {
                int i = 0;
                while (true) {
                    String key = list.get(i++);
                    if (key == null) {
                        break;
                    }
                    String value = list.get(i++);
                    if (value == null) {
                        break;
                    }
                    result.put(key, value);
                }
            }
            return result;
        } finally {
            LibIMobileDevice.delete_StringArray_values_z(infolistOut.getValue());
            infolistOut.delete();
        }
    }
    
    /**
     * Opens a file on the device.
     * 
     * @param path the fully-qualified path of the file to open.
     * @param mode the mode to use to open the file.
     * @return the handle to the open file.
     */
    public long fileOpen(String path, AfcFileMode mode) {
        LongOut handleOut = new LongOut();
        try {
            checkResult(LibIMobileDevice.afc_file_open(getRef(), path, mode, handleOut));
            return handleOut.getValue();
        } finally {
            handleOut.delete();
        }
    }
    
    /**
     * Closes a file on the device.
     * 
     * @param handle file handle of a previously opened file.
     */
    public void fileClose(long handle) {
        checkResult(LibIMobileDevice.afc_file_close(getRef(), handle));
    }
    
    /**
     * Attempts to the read the given number of bytes from the given file.
     * 
     * @param handle file handle of a previously opened file
     * @param buffer the byte array in which to store the bytes read.
     * @param offset the initial position in {@code buffer} to store the bytes
     *               read from the file.
     * @param count the maximum number of bytes to store in {@code buffer}.
     * @return the number of bytes actually read or -1 if the end of the stream
     *         has been reached.
     */
    public int fileRead(long handle, byte[] buffer, int offset, int count) {
        if ((offset | count) < 0 || offset > buffer.length || buffer.length - offset < count) {
            throw new ArrayIndexOutOfBoundsException("length=" + buffer.length 
                    + "; regionStart=" + offset + "; regionLength=" + count);
        }
        
        if (count == 0) {
            return 0;
        }
        
        byte[] data = buffer;
        if (offset > 0) {
            data = new byte[count];
        }
        IntOut bytesReadOut = new IntOut();
        try {
            checkResult(LibIMobileDevice.afc_file_read(getRef(), handle, data, count, bytesReadOut));
            int bytesRead = bytesReadOut.getValue();
            if (bytesRead == 0) {
                // Assume EOF reached.
                return -1;
            }
            if (data != buffer) {
                System.arraycopy(data, 0, buffer, offset, bytesRead);
            }
            return bytesRead;
        } finally {
            bytesReadOut.delete();
        }
    }
    
    /**
     * Writes a given number of bytes to a file.
     * 
     * @param handle file handle of previously opened file. 
     * @param buffer the buffer to be written.
     * @param offset the start position in {@code buffer} from where to get bytes.
     * @param count the number of bytes from {@code buffer} to write to the file.
     * @return the number of bytes actually written to the file.
     */
    public int fileWrite(long handle, byte[] buffer, int offset, int count) {
        if ((offset | count) < 0 || offset > buffer.length || buffer.length - offset < count) {
            throw new ArrayIndexOutOfBoundsException("length=" + buffer.length 
                    + "; regionStart=" + offset + "; regionLength=" + count);
        }
        
        if (count == 0) {
            return 0;
        }
        
        byte[] data = buffer;
        if (offset > 0) {
            data = new byte[count];
            System.arraycopy(buffer, offset, data, 0, count);
        }
        IntOut bytesWrittenOut = new IntOut();
        try {
            checkResult(LibIMobileDevice.afc_file_write(getRef(), handle, data, count, bytesWrittenOut));
            int bytesWritten = bytesWrittenOut.getValue();
            return bytesWritten;
        } finally {
            bytesWrittenOut.delete();
        }
    }

    /**
     * Copies the specified local {@link File} to the specified remote file.
     * 
     * @param localFile the {@link File} to copy.
     * @param remoteFile the path to the remote file.
     */
    public void fileCopy(File localFile, String remoteFile) throws IOException {
        long handle = fileOpen(remoteFile, AfcFileMode.AFC_FOPEN_WRONLY);
        try (InputStream is = new FileInputStream(localFile)) {
            int n = 0;
            byte[] buffer = new byte[64 * 1024];
            while ((n = is.read(buffer)) != -1) {
                fileWrite(handle, buffer, 0, n);
            }
        } finally {
            fileClose(handle);
        }
    }
    
    /**
     * Deletes a file or an empty directory.
     * 
     * @param path the fully-qualified path to delete.
     */
    public void removePath(String path) {
        removePath(path, false);
    }
    
    /**
     * Deletes a file or a directory hierarchy.
     * 
     * @param path the fully-qualified path to delete.
     * @param recurse if true non-empty directories will be 
     *        deleted recursively.
     */
    public void removePath(String path, boolean recurse) {
        if (!recurse) {
            checkResult(LibIMobileDevice.afc_remove_path(getRef(), path));
        } else {
            AfcError rc = LibIMobileDevice.afc_remove_path(getRef(), path);
            if (rc == AfcError.AFC_E_DIR_NOT_EMPTY) {
                for (String child : readDirectory(path)) {
                    if (".".equals(child) || "..".equals(child)) {
                        continue;
                    }
                    removePath(stripDirSep(path) + "/" + child, true);
                }
                rc = LibIMobileDevice.afc_remove_path(getRef(), path);
            }
            checkResult(rc);
        }
    }

    /**
     * Same as removePath(recursive = true) but provided by lib itself
     * @param path the fully-qualified path to delete.
     */
    public void removePathAndContent(String path) {
        AfcError result = LibIMobileDevice.afc_remove_path_and_contents(getRef(), path);
        if (result != AfcError.AFC_E_SUCCESS && result != AfcError.AFC_E_OBJECT_NOT_FOUND) {
            throw new LibIMobileDeviceException(result.swigValue(), result.name());
        }
    }

    /**
     * Renames a file or directory on the device.
     * 
     * @param from the fully-qualified path of the file or directory to rename.
     * @param to the fully-qualified path the file or directory should be 
     *           renamed to.
     */
    public void renamePath(String from, String to) {
        checkResult(LibIMobileDevice.afc_rename_path(getRef(), from, to));
    }

    /**
     * Creates a directory on the device. Does nothing if the directory already
     * exists. Also creates parent directories recursively.
     * 
     * @param dir the fully-qualified path of the directory to create.
     */
    public void makeDirectory(String dir) {
        checkResult(LibIMobileDevice.afc_make_directory(getRef(), dir));
    }

    /**
     * Creates a hard link or symbolic link on the device. 
     * 
     * @param type the type of link to create.
     * @param target the absolute or relative path of the link target.
     * @param source the fully-qualified path where the link will be created.
     */
    public void makeLink(AfcLinkType type, String target, String source) {
        checkResult(LibIMobileDevice.afc_make_link(getRef(), type, target, source));
    }

    private String stripDirSep(String s) {
        int end = s.length();
        while (end > 0 && s.charAt(end - 1) == '/') {
            end--;
        }
        return s.substring(0, end);
    }
    
    private String toAbsoluteDevicePath(String root, Path path) {
        String child = toRelativeDevicePath(path);
        return stripDirSep(root) + (child.length() > 0 ? "/" + toRelativeDevicePath(path) : "");
    }

    private String toRelativeDevicePath(Path path) {
        StringBuilder sb = new StringBuilder();
        int count = path.getNameCount();
        for (int i = 0; i < count; i++) {
            if (i > 0) {
                sb.append('/');
            }
            sb.append(path.getName(i));
        }
        return sb.toString();
    }

    /**
     * Uploads a local file or directory to the device.
     * 
     * @param localFile the file or directory to upload.
     * @param targetPath the path of the directory on the device where to place 
     *                   the uploaded files.
     */
    public void upload(File localFile, final String targetPath) throws IOException {
        upload(localFile, targetPath, null);
    }
    
    /**
     * Uploads a local file or directory to the device.
     * 
     * @param localFile the file or directory to upload.
     * @param targetPath the path of the directory on the device where to place 
     *                   the uploaded files.
     * @param callback callback which will receive progress and status updates.
     *                 If null no progress will be reported.
     */
    public void upload(File localFile, final String targetPath, 
            final UploadProgressCallback callback) throws IOException {
        final Path root = localFile.toPath().getParent();

        // remove recursively destination folder as it might contain previous data
        // due failed attempt
        removePathAndContent(toAbsoluteDevicePath(targetPath, root.relativize(localFile.toPath())));
        makeDirectory(targetPath);

        // 64k seems to be a good buffer size. If smaller we will not get
        // acceptable write speeds.
        final byte[] buffer = new byte[64 * 1024];

        class FileCounterVisitor extends SimpleFileVisitor {
            int count;
            @Override
            public FileVisitResult preVisitDirectory(Path dir,
                    BasicFileAttributes attrs) throws IOException {
                count++;
                return FileVisitResult.CONTINUE;
            }
            @Override
            public FileVisitResult visitFile(Path file,
                    BasicFileAttributes attrs) throws IOException {
                count++;
                return FileVisitResult.CONTINUE;
            }
        }
        FileCounterVisitor visitor = new FileCounterVisitor();
        if (callback != null) {
            Files.walkFileTree(localFile.toPath(), visitor);
        }
        
        try {
            final int fileCount = visitor.count;
            Files.walkFileTree(localFile.toPath(), new SimpleFileVisitor() {
                int filesUploaded = 0;
                private void reportProgress(Path path) {
                    if (callback != null) {
                        callback.progress(path.toFile(), 100 * filesUploaded / fileCount);                    
                    }
                    filesUploaded++;
                }
                @Override
                public FileVisitResult preVisitDirectory(Path dir,
                        BasicFileAttributes attrs) throws IOException {
    
                    reportProgress(dir);
                    String deviceDir = toAbsoluteDevicePath(targetPath, root.relativize(dir));
                    makeDirectory(deviceDir);
                    
                    return FileVisitResult.CONTINUE;
                }
                @Override
                public FileVisitResult visitFile(Path file,
                        BasicFileAttributes attrs) throws IOException {
    
                    reportProgress(file);
                    String deviceFile = toAbsoluteDevicePath(targetPath, root.relativize(file));
                    if (Files.isSymbolicLink(file)) {
                        Path linkTargetPath = Files.readSymbolicLink(file);
                        makeLink(AfcLinkType.AFC_SYMLINK, toRelativeDevicePath(linkTargetPath), deviceFile);
                    } else if (Files.isRegularFile(file, LinkOption.NOFOLLOW_LINKS)) {
                        long fd = fileOpen(deviceFile, AfcFileMode.AFC_FOPEN_WRONLY);
                        try (InputStream is = Files.newInputStream(file)) {
                            int n = 0;
                            while ((n = is.read(buffer)) != -1) {
                                fileWrite(fd, buffer, 0, n);
                            }
                        } finally {
                            fileClose(fd);
                        }
                    }
                    
                    return FileVisitResult.CONTINUE;
                }
            });
            
            if (callback != null) {
                callback.success();
            }
        } catch (IOException e) {
            if (callback != null) {
                callback.error(e.getMessage());
            }
            throw e;
        } catch (LibIMobileDeviceException e) {
            if (callback != null) {
                callback.error(e.getMessage());
            }
            throw e;
        }
    }
    
    protected AfcClientRef getRef() {
        checkDisposed();
        return ref;
    }
    
    protected final void checkDisposed() {
        if (ref == null) {
            throw new LibIMobileDeviceException("Already disposed");
        }
    }
    
    public synchronized void dispose() {
        checkDisposed();
        LibIMobileDevice.afc_client_free(ref);
        ref = null;
    }
    
    @Override
    public void close() {
        dispose();
    }
    
    private static void checkResult(AfcError result) {
        if (result != AfcError.AFC_E_SUCCESS) {
            throw new LibIMobileDeviceException(result.swigValue(), result.name());
        }
    }
    
    public interface UploadProgressCallback {
        /**
         * Reports the progress of an upload to a device.
         * 
         * @param path the path currently being uploaded.
         * @param percentComplete the progress in percent.
         */
        void progress(File path, int percentComplete);
        /**
         * Called once the upload has been completed successfully.
         */
        void success();
        /**
         * Called if the upload fails.
         * 
         * @param message the error message.
         */
        void error(String message);
    }
    
    private void list(String path, boolean recurse) {
        list(path, stripDirSep(path).replaceAll(".*?([^/]+)$", "$1"), "", recurse, new PrintWriter(System.out));
    }

    private void list(String path, String filename, String indent, boolean recurse, PrintWriter out) {
        Map info = getFileInfo(path);
        DateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss");
        long birthTime = Long.parseLong(info.get(FILE_INFO_KEY_ST_BIRTHTIME)) / 1000 / 1000;
        long mtime = Long.parseLong(info.get(FILE_INFO_KEY_ST_MTIME)) / 1000 / 1000;
        long size = Long.parseLong(info.get(AfcClient.FILE_INFO_KEY_ST_SIZE));
        if ("S_IFDIR".equals(info.get(AfcClient.FILE_INFO_KEY_ST_IFMT))) {
            out.format("%s %s %9d (%s)\t%s%s/\n", 
                    df.format(new Date(birthTime)),
                    df.format(new Date(mtime)),
                    size,
                    info.get(AfcClient.FILE_INFO_KEY_ST_IFMT),
                    indent, filename);
            out.flush();
            for (String f : readDirectory(path)) {
                if (f.equals("..") || f.equals(".")) {
                    continue;
                }
                if (recurse) {
                    String childPath = path + "/" + f;
                    list(childPath, f, indent  + "  ", recurse, out);
                }
            }
        } else if ("S_IFLNK".equals(info.get(AfcClient.FILE_INFO_KEY_ST_IFMT))) {
            out.format("%s %s %9d (%s)\t%s%s -> %s\n", 
                    df.format(new Date(birthTime)),
                    df.format(new Date(mtime)),
                    size,
                    info.get(AfcClient.FILE_INFO_KEY_ST_IFMT),
                    indent, filename, info.get(AfcClient.FILE_INFO_KEY_LINK_TARGET));
            out.flush();
        } else {
            out.format("%s %s %9d (%s)\t%s%s\n", 
                    df.format(new Date(birthTime)),
                    df.format(new Date(mtime)),
                    size,
                    info.get(AfcClient.FILE_INFO_KEY_ST_IFMT),
                    indent, filename);
            out.flush();
        }
    }
    
    private static void printUsageAndExit() {
        System.err.println(AfcClient.class.getName() + " [deviceid]  ...");
        System.err.println("  Actions:");
        System.err.println("    deviceinfo       Prints device file system information.");
        System.err.println("    rm [-f]    Deletes  from the device. Deletes non-empty dirs if -f is specified.");
        System.err.println("    ls [-r]    Lists the contents of the specified dir.");
        System.err.println("    mkdir       Creates the  on the device.");
        System.err.println("    mv     Moves (renames) the remote path  to .");
        System.err.println("    upload  \n" 
                         + "                   Uploads the local file or dir at  to the remote dir .");
        System.exit(0);
    }
    
    public static void main(String[] args) throws Exception {
        String deviceId = null;
        String action = null;

        int index = 0;
        try {
            action = args[index++];
            if (action.matches("[0-9a-f]{40}")) {
                deviceId = action;
                action = args[index++];
            }
        
            if (!action.matches("deviceinfo|rm|ls|mkdir|mv|upload")) {
                System.err.println("Unknown action: " + action);
                printUsageAndExit();
            }
            
            if (deviceId == null) {
                String[] udids = IDevice.listUdids();
                if (udids.length == 0) {
                    System.err.println("No device connected");
                    return;
                }
                if (udids.length > 1) {
                    System.err.println("More than 1 device connected ("
                            + Arrays.asList(udids) + "). Using " + udids[0]);
                }
                deviceId = udids[0];
            }
            
            try (IDevice device = new IDevice(deviceId)) {
                try (LockdowndClient lockdowndClient = new LockdowndClient(device, AfcClient.class.getSimpleName(), true)) {
                    LockdowndServiceDescriptor service = lockdowndClient.startService(SERVICE_NAME);
                    try (AfcClient client = new AfcClient(device, service)) {
                        boolean recurse = false;
                        switch (action) {
                        case "deviceinfo":
                            System.out.println(client.getDeviceInfo());
                            break;
                        case "rm":
                            if ("-r".equals(args[index])) {
                                recurse = true;
                                index++;
                            }
                            client.removePath(args[index], recurse);
                            break;
                        case "ls":
                            if ("-r".equals(args[index])) {
                                recurse = true;
                                index++;
                            }
                            client.list(args[index], recurse);
                            break;
                        case "mkdir":
                            client.makeDirectory(args[index]);
                            break;
                        case "mv":
                            client.renamePath(args[index++], args[index]);
                            break;
                        case "upload":
                            client.upload(new File(args[index++]), args[index], new UploadProgressCallback() {
                                public void progress(File path, int percentComplete) {
                                    System.out.format("[%3d%%] Uploading %s\n", percentComplete, path);
                                }
                                public void success() {
                                    System.out.format("[100%%] Upload done!\n");
                                }
                                public void error(String message) {
                                    System.out.format("Error: %s\n", message);
                                }
                            });
                            break;
                        }                    
                    }
                }
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            printUsageAndExit();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy