/*
* Copyright (C) 2012 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.cyanogenmod.filemanager.ui.policy;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.text.Html;
import android.text.Spanned;
import com.cyanogenmod.filemanager.R;
import com.cyanogenmod.filemanager.console.ExecutionException;
import com.cyanogenmod.filemanager.console.RelaunchableException;
import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
import com.cyanogenmod.filemanager.listeners.OnSelectionListener;
import com.cyanogenmod.filemanager.model.FileSystemObject;
import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerResponder;
import com.cyanogenmod.filemanager.util.CommandHelper;
import com.cyanogenmod.filemanager.util.DialogHelper;
import com.cyanogenmod.filemanager.util.ExceptionUtil;
import com.cyanogenmod.filemanager.util.ExceptionUtil.OnRelaunchCommandResult;
import com.cyanogenmod.filemanager.util.FileHelper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* A class with the convenience methods for resolve delete related actions
*/
public final class DeleteActionPolicy extends ActionsPolicy {
/**
* Method that remove an existing file system object.
*
* @param ctx The current context
* @param fso The file system object to remove
* @param onSelectionListener The listener for obtain selection information (required)
* @param onRequestRefreshListener The listener for request a refresh (optional)
* @param onItemFlingerResponder The flinger responder, only if the action was initialized
* by a flinger gesture (optional)
*/
public static void removeFileSystemObject(
final Context ctx, final FileSystemObject fso,
final OnSelectionListener onSelectionListener,
final OnRequestRefreshListener onRequestRefreshListener,
final OnItemFlingerResponder onItemFlingerResponder) {
// Generate an array and invoke internal method
List<FileSystemObject> files = new ArrayList<FileSystemObject>(1);
files.add(fso);
removeFileSystemObjects(
ctx, files, onSelectionListener,
onRequestRefreshListener, onItemFlingerResponder);
}
/**
* Method that remove an existing file system object.
*
* @param ctx The current context
* @param files The list of files to remove
* @param onSelectionListener The listener for obtain selection information (required)
* @param onRequestRefreshListener The listener for request a refresh (optional)
* @param onItemFlingerResponder The flinger responder, only if the action was initialized
* by a flinger gesture (optional)
*/
public static void removeFileSystemObjects(
final Context ctx, final List<FileSystemObject> files,
final OnSelectionListener onSelectionListener,
final OnRequestRefreshListener onRequestRefreshListener,
final OnItemFlingerResponder onItemFlingerResponder) {
// Ask the user before remove
AlertDialog dialog = DialogHelper.createYesNoDialog(
ctx,
R.string.confirm_deletion,
R.string.actions_ask_undone_operation_msg,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface alertDialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
// Remove the items
removeFileSystemObjectsInBackground(
ctx,
files,
onSelectionListener,
onRequestRefreshListener,
onItemFlingerResponder);
} else {
// Flinger operation should be cancelled
if (onItemFlingerResponder != null) {
onItemFlingerResponder.cancel();
}
}
}
});
DialogHelper.delegateDialogShow(ctx, dialog);
}
/**
* Method that remove an existing file system object in background.
*
* @param ctx The current context
* @param files The list of files to remove
* @param onSelectionListener The listener for obtain selection information (optional)
* @param onRequestRefreshListener The listener for request a refresh (optional)
* @param onItemFlingerResponder The flinger responder, only if the action was initialized
* by a flinger gesture (optional)
* @hide
*/
static void removeFileSystemObjectsInBackground(
final Context ctx, final List<FileSystemObject> files,
final OnSelectionListener onSelectionListener,
final OnRequestRefreshListener onRequestRefreshListener,
final OnItemFlingerResponder onItemFlingerResponder) {
// Some previous checks prior to execute
// 1.- Check the operation consistency (only if it is viable)
if (onSelectionListener != null) {
final String currentDirectory = onSelectionListener.onRequestCurrentDir();
if (!checkRemoveConsistency(ctx, files, currentDirectory)) {
return;
}
}
// 2.- Sort the items by path to avoid delete parents fso prior to child fso
final List<FileSystemObject> sortedFsos = new ArrayList<FileSystemObject>(files);
Collections.sort(sortedFsos, new Comparator<FileSystemObject>() {
@Override
public int compare(FileSystemObject lhs, FileSystemObject rhs) {
return lhs.compareTo(rhs) * -1; //Reverse
}
});
// The callable interface
final BackgroundCallable callable = new BackgroundCallable() {
// The current items
private int mCurrent = 0;
final Context mCtx = ctx;
final List<FileSystemObject> mFiles = sortedFsos;
final OnRequestRefreshListener mOnRequestRefreshListener = onRequestRefreshListener;
final Object mSync = new Object();
Throwable mCause;
@Override
public int getDialogTitle() {
return R.string.waiting_dialog_deleting_title;
}
@Override
public int getDialogIcon() {
return 0;
}
@Override
public boolean isDialogCancellable() {
return false;
}
@Override
public Spanned requestProgress() {
FileSystemObject fso = this.mFiles.get(this.mCurrent);
// Return the current operation
String progress =
this.mCtx.getResources().
getString(
R.string.waiting_dialog_deleting_msg,
fso.getFullPath());
return Html.fromHtml(progress);
}
@Override
public void onSuccess() {
//Operation complete.
// Confirms flinger operation
if (onItemFlingerResponder != null) {
onItemFlingerResponder.accept();
}
// Refresh
if (this.mOnRequestRefreshListener != null) {
// The reference is not the same, so refresh the complete navigation view
if (files != null && files.size() == 1) {
this.mOnRequestRefreshListener.onRequestRemove(files.get(0), true);
} else {
this.mOnRequestRefreshListener.onRequestRemove(null, true);
}
}
ActionsPolicy.showOperationSuccessMsg(ctx);
}
@Override
public void doInBackground(Object... params) throws Throwable {
this.mCause = null;
// This method expect to receive
// 1.- BackgroundAsyncTask
BackgroundAsyncTask task = (BackgroundAsyncTask)params[0];
int cc = this.mFiles.size();
for (int i = 0; i < cc; i++) {
FileSystemObject fso = this.mFiles.get(i);
doOperation(this.mCtx, fso);
// Next file
this.mCurrent++;
if (this.mCurrent < this.mFiles.size()) {
task.onRequestProgress();
}
}
}
/**
* Method that deletes the file or directory
*
* @param ctx The current context
* @param fso The file or folder to be deleted
*/
@SuppressWarnings("hiding")
private void doOperation(
final Context ctx, final FileSystemObject fso) throws Throwable {
try {
// Remove the item
if (FileHelper.isDirectory(fso)) {
CommandHelper.deleteDirectory(ctx, fso.getFullPath(), null);
} else {
CommandHelper.deleteFile(ctx, fso.getFullPath(), null);
}
} catch (Exception e) {
// Need to be relaunched?
if (e instanceof RelaunchableException) {
OnRelaunchCommandResult rl = new OnRelaunchCommandResult() {
@Override
@SuppressWarnings("unqualified-field-access")
public void onSuccess() {
synchronized (mSync) {
mSync.notify();
}
}
@Override
@SuppressWarnings("unqualified-field-access")
public void onFailed(Throwable cause) {
mCause = cause;
synchronized (mSync) {
mSync.notify();
}
}
@Override
@SuppressWarnings("unqualified-field-access")
public void onCancelled() {
synchronized (mSync) {
mSync.notify();
}
}
};
// Translate the exception (and wait for the result)
ExceptionUtil.translateException(ctx, e, false, true, rl);
synchronized (this.mSync) {
this.mSync.wait();
}
// Persist the exception?
if (this.mCause != null) {
// Cancels the flinger
if (onItemFlingerResponder != null) {
onItemFlingerResponder.cancel();
}
// The exception must be elevated
throw this.mCause;
}
} else {
// Cancels the flinger
if (onItemFlingerResponder != null) {
onItemFlingerResponder.cancel();
}
// The exception must be elevated
throw e;
}
}
// Check that the operation was completed retrieving the deleted fso
boolean failed = false;
try {
CommandHelper.getFileInfo(ctx, fso.getFullPath(), false, null);
FileSystemObject fso2 =
CommandHelper.getFileInfo(ctx, fso.getFullPath(), false, null);
if (fso2 != null) {
// Failed. The file still exists
failed = true;
}
} catch (Throwable e) {
// Operation complete successfully
}
if (failed) {
// Cancels the flinger
if (onItemFlingerResponder != null) {
onItemFlingerResponder.cancel();
}
throw new ExecutionException(
String.format(
"Failed to delete file: %s", fso.getFullPath())); //$NON-NLS-1$
}
}
};
final BackgroundAsyncTask task = new BackgroundAsyncTask(ctx, callable);
// Execute background task
task.execute(task);
}
/**
* Method that check the consistency of delete operations.<br/>
* <br/>
* The method checks the following rules:<br/>
* <ul>
* <li>Any of the files of the move or delete operation can not include the
* current directory.</li>
* </ul>
*
* @param ctx The current context
* @param files The list of source/destination files
* @param currentDirectory The current directory
* @return boolean If the consistency is validate successfully
*/
private static boolean checkRemoveConsistency(
Context ctx, List<FileSystemObject> files, String currentDirectory) {
int cc = files.size();
for (int i = 0; i < cc; i++) {
FileSystemObject fso = files.get(i);
// 1.- Current directory can't be deleted
if (currentDirectory.startsWith(fso.getFullPath())) {
// Operation not allowed
AlertDialog dialog =
DialogHelper.createWarningDialog(
ctx,
R.string.warning_title,
R.string.msgs_unresolved_inconsistencies);
DialogHelper.delegateDialogShow(ctx, dialog);
return false;
}
}
return true;
}
}