Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 81d466c7 authored by Matt Garnes's avatar Matt Garnes
Browse files

Improve copy/move performance with nio and reintroduce cancel.

Utilize FileChannel.transferFrom to copy files faster. Chunk the file
transfer so that we can check between each chunk if the user has
cancelled the copy.

In my tests copying a large file (650MB), this improved performance by 45%.

Also, bring the cancel feature back for non secure storage.

Fixes QRDL-976.

Change-Id: I112cee7b9dfe682a438516f7f938dfd7538f1efb
parent ef030e9b
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -83,7 +83,7 @@ public class CopyCommand extends Program implements CopyExecutable {
        }

        //Copy recursively
        if (!FileHelper.copyRecursive(s, d, getBufferSize(), this)) {
        if (!FileHelper.copyRecursive(s, d, this)) {
            if (isTrace()) {
                Log.v(TAG, "Result: FAIL. InsufficientPermissionsException"); //$NON-NLS-1$
            }
+2 −2
Original line number Diff line number Diff line
@@ -83,7 +83,7 @@ public class MoveCommand extends Program implements MoveExecutable {

        //Move or copy recursively
        if (d.exists()) {
            if (!FileHelper.copyRecursive(s, d, getBufferSize(), this)) {
            if (!FileHelper.copyRecursive(s, d, this)) {
                if (isTrace()) {
                    Log.v(TAG, "Result: FAIL. InsufficientPermissionsException"); //$NON-NLS-1$
                }
@@ -97,7 +97,7 @@ public class MoveCommand extends Program implements MoveExecutable {
        } else {
            // Move between filesystem is not allow. If rename fails then use copy operation
            if (!s.renameTo(d)) {
                if (!FileHelper.copyRecursive(s, d, getBufferSize(), this)) {
                if (!FileHelper.copyRecursive(s, d, this)) {
                    if (isTrace()) {
                        Log.v(TAG, "Result: FAIL. InsufficientPermissionsException"); //$NON-NLS-1$
                    }
+3 −1
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import com.cyanogenmod.filemanager.R;
import com.cyanogenmod.filemanager.console.Console;
import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
import com.cyanogenmod.filemanager.console.RelaunchableException;
import com.cyanogenmod.filemanager.console.secure.SecureConsole;
import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
import com.cyanogenmod.filemanager.listeners.OnSelectionListener;
import com.cyanogenmod.filemanager.model.FileSystemObject;
@@ -282,7 +283,8 @@ public final class CopyMoveActionPolicy extends ActionsPolicy {
            }
            @Override
            public boolean isDialogCancellable() {
                return false;
                return !(mSrcConsole instanceof SecureConsole)
                        && !(mDstConsole instanceof SecureConsole);
            }

            @Override
+50 −34
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import android.content.res.Resources;
import android.system.ErrnoException;
import android.system.OsConstants;
import android.util.Log;

import com.cyanogenmod.filemanager.FileManagerApplication;
import com.cyanogenmod.filemanager.R;
import com.cyanogenmod.filemanager.commands.SyncResultExecutable;
@@ -57,12 +56,12 @@ import com.cyanogenmod.filemanager.preferences.ObjectStringIdentifier;
import com.cyanogenmod.filemanager.preferences.Preferences;
import com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.FileChannel;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
@@ -118,6 +117,13 @@ public final class FileHelper {
     */
    public static final String NEWLINE = System.getProperty("line.separator"); //$NON-NLS-1$

    /**
     * The size of chunks that we will copy with nio during a copy operation.
     *
     * Set to 1MiB.
     */
    public final static long NIO_COPY_CHUNK_SIZE = 1024000L;

    // The date/time formats objects
    /**
     * @hide
@@ -1087,12 +1093,11 @@ public final class FileHelper {
     *
     * @param src The source file or folder
     * @param dst The destination file or folder
     * @param bufferSize The buffer size for the operation
     * @return boolean If the operation complete successfully
     * @throws ExecutionException If a problem was detected in the operation
     */
    public static boolean copyRecursive(
            final File src, final File dst, int bufferSize, Program program)
            final File src, final File dst, Program program)
                throws ExecutionException, CancelledOperationException {
        if (src.isDirectory()) {
            // Create the directory
@@ -1115,7 +1120,7 @@ public final class FileHelper {
                        throw new CancelledOperationException();
                    }

                    if (!copyRecursive(files[i], new File(dst, files[i].getName()), bufferSize,
                    if (!copyRecursive(files[i], new File(dst, files[i].getName()),
                                       program)) {
                        return false;
                    }
@@ -1123,7 +1128,7 @@ public final class FileHelper {
            }
        } else {
            // Copy the directory
            if (!bufferedCopy(src, dst,bufferSize, program)) {
            if (!copyFileWithNio(src, dst, program)) {
                return false;
            }
        }
@@ -1131,40 +1136,46 @@ public final class FileHelper {
    }

    /**
     * Method that copies a file
     * Method that copies a file, using FileChannel.transferFrom from
     * the nio package.
     *
     * The file is chunked into chunks of size {@link #NIO_COPY_CHUNK_SIZE}
     * and each chunk is transferred until the file is completely copied. This
     * allows us to cancel the file transfer at any time.
     *
     * @param src The source file
     * @param dst The destination file
     * @param bufferSize The buffer size for the operation
     * @return boolean If the operation complete successfully
     * @return boolean Whether the operation completed successfully
     */
    public static boolean bufferedCopy(final File src, final File dst,
        int bufferSize, Program program)
            throws ExecutionException, CancelledOperationException {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
    public static boolean copyFileWithNio(final File src, final File dst,
            Program program) throws CancelledOperationException, ExecutionException {
        FileChannel inputChannel = null;
        FileChannel outputChannel = null;
        long currentPosition = 0;
        long count = NIO_COPY_CHUNK_SIZE;
        try {
            bis = new BufferedInputStream(new FileInputStream(src), bufferSize);
            bos = new BufferedOutputStream(new FileOutputStream(dst), bufferSize);
            int read = 0;
            byte[] data = new byte[bufferSize];
            while ((read = bis.read(data, 0, bufferSize)) != -1) {
            inputChannel = new FileInputStream(src).getChannel();
            outputChannel = new FileOutputStream(dst).getChannel();
            while (currentPosition < inputChannel.size()) {
                // Short circuit if we've been cancelled. Show's over :(
                if (program.isCancelled()) {
                    throw new CancelledOperationException();
                }
                bos.write(data, 0, read);
            }
            return true;

                if ((currentPosition + count) > inputChannel.size()) {
                    count = (inputChannel.size() - currentPosition);
                }
                outputChannel.transferFrom(inputChannel, currentPosition, count);
                currentPosition = currentPosition + count;
            }
        } catch (Throwable e) {
            Log.e(TAG,
                    String.format(TAG, "Failed to copy from %s to %d", src, dst), e); //$NON-NLS-1$

            try {
                // delete the destination file if it exists since the operation failed
                if (dst.exists()) {
                    dst.delete();
                // Delete the destination file upon failure
                if (!dst.delete()) {
                    Log.e(TAG, "Failed to delete the dest file: " + dst);
                }
            } catch (Throwable t) {/**NON BLOCK**/}

@@ -1173,25 +1184,30 @@ public final class FileHelper {
            if (e.getCause() instanceof ErrnoException
                        && ((ErrnoException)e.getCause()).errno == OsConstants.ENOSPC) {
                throw new ExecutionException(R.string.msgs_no_disk_space);
            } if (e instanceof CancelledOperationException) {
            } else if ((e instanceof CancelledOperationException)
                    || (e instanceof ClosedByInterruptException)) {
                // If the user cancelled this operation, let it through.
                throw (CancelledOperationException)e;
            }

            return false;
        } finally {
            try {
                if (bis != null) {
                    bis.close();
                if (inputChannel != null) {
                    inputChannel.close();
                }
            } catch (IOException e) {
                Log.e(TAG, "Error while closing input channel during copyFileWithNio");
            }
            } catch (Throwable e) {/**NON BLOCK**/}
            try {
                if (bos != null) {
                    bos.close();
                if (outputChannel != null) {
                    outputChannel.close();
                }
            } catch (Throwable e) {/**NON BLOCK**/}
            } catch (IOException e) {
                Log.e(TAG, "Error while closing output channel during copyFileWithNio");
            }
        }
        return true;
    }

    /**
     * Method that deletes a folder recursively