From: Martin Brabham Date: Fri, 3 Apr 2015 03:33:41 +0000 (-0700) Subject: Implement ability to cancel file copy. X-Git-Tag: android-x86-6.0-r1~126 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=948e0d1d3407d8e611d0d82aac2f399703a494e3;p=android-x86%2Fpackages-apps-CMFileManager.git Implement ability to cancel file copy. - Short circuit Java CopyCommand for move/copy when the user cancels the dialog. - Bubble up CancelledOperationException when the Cancel/Move operation is cancelled. Handle cancellation differently than success. Change-Id: I3e4426aaccf42e12bf299041d489e72b3b76a626 (cherry picked from commit 7e13ec2fa4fc052c2a880a8dba8ed871b3bc10ca) --- diff --git a/src/com/cyanogenmod/filemanager/commands/java/CopyCommand.java b/src/com/cyanogenmod/filemanager/commands/java/CopyCommand.java index 0c7832c..5d8073f 100644 --- a/src/com/cyanogenmod/filemanager/commands/java/CopyCommand.java +++ b/src/com/cyanogenmod/filemanager/commands/java/CopyCommand.java @@ -19,6 +19,7 @@ package com.cyanogenmod.filemanager.commands.java; import android.util.Log; import com.cyanogenmod.filemanager.commands.CopyExecutable; +import com.cyanogenmod.filemanager.console.CancelledOperationException; import com.cyanogenmod.filemanager.console.ExecutionException; import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; @@ -64,7 +65,8 @@ public class CopyCommand extends Program implements CopyExecutable { */ @Override public void execute() - throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException { + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException, + CancelledOperationException { if (isTrace()) { Log.v(TAG, String.format("Moving from %s to %s", //$NON-NLS-1$ @@ -81,7 +83,7 @@ public class CopyCommand extends Program implements CopyExecutable { } //Copy recursively - if (!FileHelper.copyRecursive(s, d, getBufferSize())) { + if (!FileHelper.copyRecursive(s, d, getBufferSize(), this)) { if (isTrace()) { Log.v(TAG, "Result: FAIL. InsufficientPermissionsException"); //$NON-NLS-1$ } diff --git a/src/com/cyanogenmod/filemanager/commands/java/MoveCommand.java b/src/com/cyanogenmod/filemanager/commands/java/MoveCommand.java index b0c85fb..c4943b2 100644 --- a/src/com/cyanogenmod/filemanager/commands/java/MoveCommand.java +++ b/src/com/cyanogenmod/filemanager/commands/java/MoveCommand.java @@ -19,6 +19,7 @@ package com.cyanogenmod.filemanager.commands.java; import android.util.Log; import com.cyanogenmod.filemanager.commands.MoveExecutable; +import com.cyanogenmod.filemanager.console.CancelledOperationException; import com.cyanogenmod.filemanager.console.ExecutionException; import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; @@ -64,7 +65,8 @@ public class MoveCommand extends Program implements MoveExecutable { */ @Override public void execute() - throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException { + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException, + CancelledOperationException { if (isTrace()) { Log.v(TAG, String.format("Creating from %s to %s", this.mSrc, this.mDst)); //$NON-NLS-1$ @@ -81,7 +83,7 @@ public class MoveCommand extends Program implements MoveExecutable { //Move or copy recursively if (d.exists()) { - if (!FileHelper.copyRecursive(s, d, getBufferSize())) { + if (!FileHelper.copyRecursive(s, d, getBufferSize(), this)) { if (isTrace()) { Log.v(TAG, "Result: FAIL. InsufficientPermissionsException"); //$NON-NLS-1$ } @@ -95,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())) { + if (!FileHelper.copyRecursive(s, d, getBufferSize(), this)) { if (isTrace()) { Log.v(TAG, "Result: FAIL. InsufficientPermissionsException"); //$NON-NLS-1$ } diff --git a/src/com/cyanogenmod/filemanager/commands/java/Program.java b/src/com/cyanogenmod/filemanager/commands/java/Program.java index b5eeebd..34e70cc 100644 --- a/src/com/cyanogenmod/filemanager/commands/java/Program.java +++ b/src/com/cyanogenmod/filemanager/commands/java/Program.java @@ -17,6 +17,7 @@ package com.cyanogenmod.filemanager.commands.java; import com.cyanogenmod.filemanager.commands.Executable; +import com.cyanogenmod.filemanager.console.CancelledOperationException; import com.cyanogenmod.filemanager.console.ExecutionException; import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; @@ -29,6 +30,7 @@ public abstract class Program implements Executable { private boolean mTrace; private int mBufferSize; + private boolean mCancelled = false; /** * Constructor of Program @@ -92,6 +94,14 @@ public abstract class Program implements Executable { * @throws ExecutionException If the operation returns a invalid exit code */ public abstract void execute() - throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException; + throws InsufficientPermissionsException, NoSuchFileOrDirectory, ExecutionException, + CancelledOperationException; + public void requestCancel() { + mCancelled = true; + } + + public boolean isCancelled() { + return mCancelled; + } } diff --git a/src/com/cyanogenmod/filemanager/console/java/JavaConsole.java b/src/com/cyanogenmod/filemanager/console/java/JavaConsole.java index f59f746..741fc8f 100644 --- a/src/com/cyanogenmod/filemanager/console/java/JavaConsole.java +++ b/src/com/cyanogenmod/filemanager/console/java/JavaConsole.java @@ -23,6 +23,7 @@ import com.cyanogenmod.filemanager.commands.Executable; import com.cyanogenmod.filemanager.commands.ExecutableFactory; import com.cyanogenmod.filemanager.commands.java.JavaExecutableFactory; import com.cyanogenmod.filemanager.commands.java.Program; +import com.cyanogenmod.filemanager.console.CancelledOperationException; import com.cyanogenmod.filemanager.console.CommandNotFoundException; import com.cyanogenmod.filemanager.console.ConsoleAllocException; import com.cyanogenmod.filemanager.console.ExecutionException; @@ -43,6 +44,7 @@ public final class JavaConsole extends VirtualConsole { private static final String TAG = "JavaConsole"; //$NON-NLS-1$ private final int mBufferSize; + private Program mActiveProgram; /** * Constructor of JavaConsole @@ -78,8 +80,8 @@ public final class JavaConsole extends VirtualConsole { @Override public synchronized void execute(Executable executable, Context ctx) throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory, - OperationTimeoutException, ExecutionException, CommandNotFoundException, - ReadOnlyFilesystemException { + OperationTimeoutException, ExecutionException, CommandNotFoundException, + CancelledOperationException, ReadOnlyFilesystemException { // Check that the program is a java program try { Program p = (Program)executable; @@ -98,6 +100,7 @@ public final class JavaConsole extends VirtualConsole { // Execute the program final Program program = (Program)executable; + mActiveProgram = program; program.setTrace(isTrace()); program.setBufferSize(this.mBufferSize); if (program.isAsynchronous()) { @@ -123,4 +126,12 @@ public final class JavaConsole extends VirtualConsole { } } + /** + * {@inheritDoc} + */ + @Override + public boolean onCancel() { + mActiveProgram.requestCancel(); + return true; + } } \ No newline at end of file diff --git a/src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java b/src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java index 8800607..b9464d2 100644 --- a/src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java +++ b/src/com/cyanogenmod/filemanager/console/shell/ShellConsole.java @@ -1252,8 +1252,13 @@ public abstract class ShellConsole extends Console implements Program.ProgramLis } } catch (Throwable ex) { Log.w(TAG, - String.format("Unable to kill current program: %s", //$NON-NLS-1$ - this.mActiveCommand.getCommand()), ex); + String.format("Unable to kill current program: %s", + ( + (this.mActiveCommand == null) ? + "" : + this.mActiveCommand.getCommand() + ) + ), ex); } } } diff --git a/src/com/cyanogenmod/filemanager/ui/policy/ActionsPolicy.java b/src/com/cyanogenmod/filemanager/ui/policy/ActionsPolicy.java index 8964c5e..c127d3f 100644 --- a/src/com/cyanogenmod/filemanager/ui/policy/ActionsPolicy.java +++ b/src/com/cyanogenmod/filemanager/ui/policy/ActionsPolicy.java @@ -77,6 +77,12 @@ public abstract class ActionsPolicy { * Method invoked when the operation was successfully */ void onSuccess(); + + /** + * Method invoked to handle cancelling + */ + void onCancel(); + } /** @@ -117,7 +123,9 @@ public abstract class ActionsPolicy { this.mDialog.setOnCancelListener(new MessageProgressDialog.OnCancelListener() { @Override public boolean onCancel() { - return task.cancel(true); + mCallable.onCancel(); + task.cancel(true); + return true; } }); Spanned progress = this.mCallable.requestProgress(); @@ -155,8 +163,7 @@ public abstract class ActionsPolicy { @Override protected void onCancelled() { - //Operation complete. - this.mCallable.onSuccess(); + this.mCallable.onCancel(); } @Override @@ -182,4 +189,4 @@ public abstract class ActionsPolicy { protected static void showOperationSuccessMsg(Context ctx) { DialogHelper.showToast(ctx, R.string.msgs_success, Toast.LENGTH_SHORT); } -} \ No newline at end of file +} diff --git a/src/com/cyanogenmod/filemanager/ui/policy/CompressActionPolicy.java b/src/com/cyanogenmod/filemanager/ui/policy/CompressActionPolicy.java index c2c10e5..b5021b8 100644 --- a/src/com/cyanogenmod/filemanager/ui/policy/CompressActionPolicy.java +++ b/src/com/cyanogenmod/filemanager/ui/policy/CompressActionPolicy.java @@ -266,6 +266,11 @@ public final class CompressActionPolicy extends ActionsPolicy { } @Override + public void onCancel() { + // nop + } + + @Override public void doInBackground(Object... params) throws Throwable { this.mCause = null; this.mStarted = true; @@ -568,6 +573,11 @@ public final class CompressActionPolicy extends ActionsPolicy { } @Override + public void onCancel() { + // nop + } + + @Override public void doInBackground(Object... params) throws Throwable { this.mCause = null; this.mStarted = true; @@ -842,4 +852,4 @@ public final class CompressActionPolicy extends ActionsPolicy { protected static void showOperationSuccessMsg(Context ctx, int res, String dst) { DialogHelper.showToast(ctx, ctx.getString(res, dst), Toast.LENGTH_SHORT); } -} \ No newline at end of file +} diff --git a/src/com/cyanogenmod/filemanager/ui/policy/CopyMoveActionPolicy.java b/src/com/cyanogenmod/filemanager/ui/policy/CopyMoveActionPolicy.java index 33e0b98..c2dde32 100644 --- a/src/com/cyanogenmod/filemanager/ui/policy/CopyMoveActionPolicy.java +++ b/src/com/cyanogenmod/filemanager/ui/policy/CopyMoveActionPolicy.java @@ -19,10 +19,13 @@ package com.cyanogenmod.filemanager.ui.policy; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; +import android.content.DialogInterface.OnCancelListener; import android.text.Html; import android.text.Spanned; import com.cyanogenmod.filemanager.R; +import com.cyanogenmod.filemanager.console.CancelledOperationException; +import com.cyanogenmod.filemanager.console.Console; import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory; import com.cyanogenmod.filemanager.console.RelaunchableException; import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener; @@ -280,7 +283,7 @@ public final class CopyMoveActionPolicy extends ActionsPolicy { } @Override public boolean isDialogCancellable() { - return false; + return true; } @Override @@ -301,8 +304,7 @@ public final class CopyMoveActionPolicy extends ActionsPolicy { return Html.fromHtml(progress); } - @Override - public void onSuccess() { + private void refreshUIAfterCompletion() { // Remove orphan bookmark paths if (files != null) { for (LinkedResource linkedFiles : files) { @@ -315,6 +317,11 @@ public final class CopyMoveActionPolicy extends ActionsPolicy { // The reference is not the same, so refresh the complete navigation view this.mOnRequestRefreshListener.onRequestRefresh(null, true); } + } + + @Override + public void onSuccess() { + refreshUIAfterCompletion(); ActionsPolicy.showOperationSuccessMsg(ctx); } @@ -341,6 +348,21 @@ public final class CopyMoveActionPolicy extends ActionsPolicy { } } + @Override + public void onCancel() { + if (mSrcConsole != null) { + mSrcConsole.onCancel(); + } + if (mDstConsole != null) { + mDstConsole.onCancel(); + } + refreshUIAfterCompletion(); + } + + // Handles required for issuing command death to the consoles + private Console mSrcConsole; + private Console mDstConsole; + /** * Method that copy or move the file to another location * @@ -360,6 +382,21 @@ public final class CopyMoveActionPolicy extends ActionsPolicy { // under using absolute paths) Issue: CYAN-2791 String source = src.getAbsolutePath() + (src.isDirectory() ? File.separator : ""); + String dest = dst.getAbsolutePath() + + (dst.isDirectory() ? File.separator : ""); + + /* + There is a possibility that the src and dst can have different consoles. + A possible case: + - src is from sd card and dst is secure storage + This could happen with anything that goes from a real console to a virtual + console or visa versa. Here we grab a handle on the console such that we + may explicitly kill the actions happening in both consoles. + */ + // Need to derive the console for the source + mSrcConsole = CommandHelper.ensureConsoleForFile(ctx, null, source); + // Need to derive the console for the destination + mDstConsole = CommandHelper.ensureConsoleForFile(ctx, null, dest); // Copy or move? if (operation.compareTo(COPY_MOVE_OPERATION.MOVE) == 0 || @@ -368,13 +405,13 @@ public final class CopyMoveActionPolicy extends ActionsPolicy { ctx, source, dst.getAbsolutePath(), - null); + mSrcConsole); } else { CommandHelper.copy( ctx, source, dst.getAbsolutePath(), - null); + mSrcConsole); } } catch (Exception e) { // Need to be relaunched? @@ -545,4 +582,4 @@ public final class CopyMoveActionPolicy extends ActionsPolicy { } return true; } -} \ No newline at end of file +} diff --git a/src/com/cyanogenmod/filemanager/ui/policy/DeleteActionPolicy.java b/src/com/cyanogenmod/filemanager/ui/policy/DeleteActionPolicy.java index 843afe2..003ed46 100644 --- a/src/com/cyanogenmod/filemanager/ui/policy/DeleteActionPolicy.java +++ b/src/com/cyanogenmod/filemanager/ui/policy/DeleteActionPolicy.java @@ -213,6 +213,11 @@ public final class DeleteActionPolicy extends ActionsPolicy { } @Override + public void onCancel() { + + } + + @Override public void doInBackground(Object... params) throws Throwable { this.mCause = null; @@ -372,4 +377,4 @@ public final class DeleteActionPolicy extends ActionsPolicy { } return true; } -} \ No newline at end of file +} diff --git a/src/com/cyanogenmod/filemanager/util/CommandHelper.java b/src/com/cyanogenmod/filemanager/util/CommandHelper.java index de71798..33ddc40 100644 --- a/src/com/cyanogenmod/filemanager/util/CommandHelper.java +++ b/src/com/cyanogenmod/filemanager/util/CommandHelper.java @@ -2012,7 +2012,7 @@ public final class CommandHelper { * @throws IOException If initial directory couldn't be checked * @throws FileNotFoundException If the initial directory not exists */ - private static Console ensureConsoleForFile(Context context, Console console, String src) + public static Console ensureConsoleForFile(Context context, Console console, String src) throws FileNotFoundException, IOException, InvalidCommandDefinitionException, ConsoleAllocException, InsufficientPermissionsException { diff --git a/src/com/cyanogenmod/filemanager/util/FileHelper.java b/src/com/cyanogenmod/filemanager/util/FileHelper.java index 14059d6..0cf98fe 100644 --- a/src/com/cyanogenmod/filemanager/util/FileHelper.java +++ b/src/com/cyanogenmod/filemanager/util/FileHelper.java @@ -26,7 +26,9 @@ import android.util.Log; import com.cyanogenmod.filemanager.FileManagerApplication; import com.cyanogenmod.filemanager.R; import com.cyanogenmod.filemanager.commands.SyncResultExecutable; +import com.cyanogenmod.filemanager.commands.java.Program; import com.cyanogenmod.filemanager.commands.shell.ResolveLinkCommand; +import com.cyanogenmod.filemanager.console.CancelledOperationException; import com.cyanogenmod.filemanager.console.Console; import com.cyanogenmod.filemanager.console.ExecutionException; import com.cyanogenmod.filemanager.console.InsufficientPermissionsException; @@ -1105,7 +1107,8 @@ public final class FileHelper { * @throws ExecutionException If a problem was detected in the operation */ public static boolean copyRecursive( - final File src, final File dst, int bufferSize) throws ExecutionException { + final File src, final File dst, int bufferSize, Program program) + throws ExecutionException, CancelledOperationException { if (src.isDirectory()) { // Create the directory if (dst.exists() && !dst.isDirectory()) { @@ -1122,14 +1125,20 @@ public final class FileHelper { File[] files = src.listFiles(); if (files != null) { for (int i = 0; i < files.length; i++) { - if (!copyRecursive(files[i], new File(dst, files[i].getName()), bufferSize)) { + // Short circuit if we've been cancelled. Show's over :( + if (program.isCancelled()) { + throw new CancelledOperationException(); + } + + if (!copyRecursive(files[i], new File(dst, files[i].getName()), bufferSize, + program)) { return false; } } } } else { // Copy the directory - if (!bufferedCopy(src, dst,bufferSize)) { + if (!bufferedCopy(src, dst,bufferSize, program)) { return false; } } @@ -1145,7 +1154,8 @@ public final class FileHelper { * @return boolean If the operation complete successfully */ public static boolean bufferedCopy(final File src, final File dst, - int bufferSize) throws ExecutionException { + int bufferSize, Program program) + throws ExecutionException, CancelledOperationException { BufferedInputStream bis = null; BufferedOutputStream bos = null; try { @@ -1154,6 +1164,10 @@ public final class FileHelper { int read = 0; byte[] data = new byte[bufferSize]; while ((read = bis.read(data, 0, bufferSize)) != -1) { + // Short circuit if we've been cancelled. Show's over :( + if (program.isCancelled()) { + throw new CancelledOperationException(); + } bos.write(data, 0, read); } return true; @@ -1167,6 +1181,9 @@ 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) { + // If the user cancelled this operation, let it through. + throw (CancelledOperationException)e; } return false;