OSDN Git Service

Merge "CMFM: Fix deadlock" into cm-11.0
[android-x86/packages-apps-CMFileManager.git] / src / com / cyanogenmod / filemanager / ui / policy / DeleteActionPolicy.java
1 /*
2  * Copyright (C) 2012 The CyanogenMod Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.cyanogenmod.filemanager.ui.policy;
18
19 import android.app.AlertDialog;
20 import android.content.Context;
21 import android.content.DialogInterface;
22 import android.text.Html;
23 import android.text.Spanned;
24
25 import com.cyanogenmod.filemanager.R;
26 import com.cyanogenmod.filemanager.console.ExecutionException;
27 import com.cyanogenmod.filemanager.console.RelaunchableException;
28 import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
29 import com.cyanogenmod.filemanager.listeners.OnSelectionListener;
30 import com.cyanogenmod.filemanager.model.FileSystemObject;
31 import com.cyanogenmod.filemanager.ui.widgets.FlingerListView.OnItemFlingerResponder;
32 import com.cyanogenmod.filemanager.util.CommandHelper;
33 import com.cyanogenmod.filemanager.util.DialogHelper;
34 import com.cyanogenmod.filemanager.util.ExceptionUtil;
35 import com.cyanogenmod.filemanager.util.ExceptionUtil.OnRelaunchCommandResult;
36 import com.cyanogenmod.filemanager.util.FileHelper;
37
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.Comparator;
41 import java.util.List;
42
43
44 /**
45  * A class with the convenience methods for resolve delete related actions
46  */
47 public final class DeleteActionPolicy extends ActionsPolicy {
48
49     /**
50      * Method that remove an existing file system object.
51      *
52      * @param ctx The current context
53      * @param fso The file system object to remove
54      * @param onSelectionListener The listener for obtain selection information (required)
55      * @param onRequestRefreshListener The listener for request a refresh (optional)
56      * @param onItemFlingerResponder The flinger responder, only if the action was initialized
57      * by a flinger gesture (optional)
58      */
59     public static void removeFileSystemObject(
60             final Context ctx, final FileSystemObject fso,
61             final OnSelectionListener onSelectionListener,
62             final OnRequestRefreshListener onRequestRefreshListener,
63             final OnItemFlingerResponder onItemFlingerResponder) {
64         // Generate an array and invoke internal method
65         List<FileSystemObject> files = new ArrayList<FileSystemObject>(1);
66         files.add(fso);
67         removeFileSystemObjects(
68                 ctx, files, onSelectionListener,
69                 onRequestRefreshListener, onItemFlingerResponder);
70     }
71
72     /**
73      * Method that remove an existing file system object.
74      *
75      * @param ctx The current context
76      * @param files The list of files to remove
77      * @param onSelectionListener The listener for obtain selection information (required)
78      * @param onRequestRefreshListener The listener for request a refresh (optional)
79      * @param onItemFlingerResponder The flinger responder, only if the action was initialized
80      * by a flinger gesture (optional)
81      */
82     public static void removeFileSystemObjects(
83             final Context ctx, final List<FileSystemObject> files,
84             final OnSelectionListener onSelectionListener,
85             final OnRequestRefreshListener onRequestRefreshListener,
86             final OnItemFlingerResponder onItemFlingerResponder) {
87
88         // Ask the user before remove
89         AlertDialog dialog = DialogHelper.createYesNoDialog(
90             ctx,
91             R.string.confirm_deletion,
92             R.string.actions_ask_undone_operation_msg,
93             new DialogInterface.OnClickListener() {
94                 @Override
95                 public void onClick(DialogInterface alertDialog, int which) {
96                     if (which == DialogInterface.BUTTON_POSITIVE) {
97                         // Remove the items
98                         removeFileSystemObjectsInBackground(
99                                 ctx,
100                                 files,
101                                 onSelectionListener,
102                                 onRequestRefreshListener,
103                                 onItemFlingerResponder);
104                     } else {
105                         // Flinger operation should be cancelled
106                         if (onItemFlingerResponder != null) {
107                             onItemFlingerResponder.cancel();
108                         }
109                     }
110                 }
111            });
112         DialogHelper.delegateDialogShow(ctx, dialog);
113     }
114
115     /**
116      * Method that remove an existing file system object in background.
117      *
118      * @param ctx The current context
119      * @param files The list of files to remove
120      * @param onSelectionListener The listener for obtain selection information (optional)
121      * @param onRequestRefreshListener The listener for request a refresh (optional)
122      * @param onItemFlingerResponder The flinger responder, only if the action was initialized
123      * by a flinger gesture (optional)
124      * @hide
125      */
126     static void removeFileSystemObjectsInBackground(
127             final Context ctx, final List<FileSystemObject> files,
128             final OnSelectionListener onSelectionListener,
129             final OnRequestRefreshListener onRequestRefreshListener,
130             final OnItemFlingerResponder onItemFlingerResponder) {
131
132         // Some previous checks prior to execute
133         // 1.- Check the operation consistency (only if it is viable)
134         if (onSelectionListener != null) {
135             final String currentDirectory = onSelectionListener.onRequestCurrentDir();
136             if (!checkRemoveConsistency(ctx, files, currentDirectory)) {
137                 return;
138             }
139         }
140         // 2.- Sort the items by path to avoid delete parents fso prior to child fso
141         final List<FileSystemObject> sortedFsos  = new ArrayList<FileSystemObject>(files);
142         Collections.sort(sortedFsos, new Comparator<FileSystemObject>() {
143             @Override
144             public int compare(FileSystemObject lhs, FileSystemObject rhs) {
145                 return lhs.compareTo(rhs) * -1; //Reverse
146             }
147         });
148
149         // The callable interface
150         final BackgroundCallable callable = new BackgroundCallable() {
151             // The current items
152             private int mCurrent = 0;
153             final Context mCtx = ctx;
154             final List<FileSystemObject> mFiles = sortedFsos;
155             final OnRequestRefreshListener mOnRequestRefreshListener = onRequestRefreshListener;
156
157             final Object mSync = new Object();
158             Throwable mCause;
159
160             @Override
161             public int getDialogTitle() {
162                 return R.string.waiting_dialog_deleting_title;
163             }
164             @Override
165             public int getDialogIcon() {
166                 return 0;
167             }
168             @Override
169             public boolean isDialogCancellable() {
170                 return false;
171             }
172
173             @Override
174             public Spanned requestProgress() {
175                 FileSystemObject fso = this.mFiles.get(this.mCurrent);
176
177                 // Return the current operation
178                 String progress =
179                       this.mCtx.getResources().
180                           getString(
181                               R.string.waiting_dialog_deleting_msg,
182                               fso.getFullPath());
183                 return Html.fromHtml(progress);
184             }
185
186             @Override
187             public void onSuccess() {
188                 //Operation complete.
189
190                 // Confirms flinger operation
191                 if (onItemFlingerResponder != null) {
192                     onItemFlingerResponder.accept();
193                 }
194
195                 // Refresh
196                 if (this.mOnRequestRefreshListener != null) {
197                     // The reference is not the same, so refresh the complete navigation view
198                     if (files != null && files.size() == 1) {
199                         this.mOnRequestRefreshListener.onRequestRemove(files.get(0), true);
200                     } else {
201                         this.mOnRequestRefreshListener.onRequestRemove(null, true);
202                     }
203                 }
204                 ActionsPolicy.showOperationSuccessMsg(ctx);
205             }
206
207             @Override
208             public void doInBackground(Object... params) throws Throwable {
209                 this.mCause = null;
210
211                 // This method expect to receive
212                 // 1.- BackgroundAsyncTask
213                 BackgroundAsyncTask task = (BackgroundAsyncTask)params[0];
214
215                 int cc = this.mFiles.size();
216                 for (int i = 0; i < cc; i++) {
217                     FileSystemObject fso = this.mFiles.get(i);
218
219                     doOperation(this.mCtx, fso);
220
221                     // Next file
222                     this.mCurrent++;
223                     if (this.mCurrent < this.mFiles.size()) {
224                         task.onRequestProgress();
225                     }
226                 }
227             }
228
229             /**
230              * Method that deletes the file or directory
231              *
232              * @param ctx The current context
233              * @param fso The file or folder to be deleted
234              */
235             @SuppressWarnings("hiding")
236             private void doOperation(
237                     final Context ctx, final FileSystemObject fso) throws Throwable {
238                 try {
239                     // Remove the item
240                     if (FileHelper.isDirectory(fso)) {
241                         CommandHelper.deleteDirectory(ctx, fso.getFullPath(), null);
242                     } else {
243                         CommandHelper.deleteFile(ctx, fso.getFullPath(), null);
244                     }
245                 } catch (Exception e) {
246                     // Need to be relaunched?
247                     if (e instanceof RelaunchableException) {
248                         OnRelaunchCommandResult rl = new OnRelaunchCommandResult() {
249                             @Override
250                             @SuppressWarnings("unqualified-field-access")
251                             public void onSuccess() {
252                                 synchronized (mSync) {
253                                     mSync.notify();
254                                 }
255                             }
256
257                             @Override
258                             @SuppressWarnings("unqualified-field-access")
259                             public void onFailed(Throwable cause) {
260                                 mCause = cause;
261                                 synchronized (mSync) {
262                                     mSync.notify();
263                                 }
264                             }
265                             @Override
266                             @SuppressWarnings("unqualified-field-access")
267                             public void onCancelled() {
268                                 synchronized (mSync) {
269                                     mSync.notify();
270                                 }
271                             }
272                         };
273
274                         // Translate the exception (and wait for the result)
275                         ExceptionUtil.translateException(ctx, e, false, true, rl);
276                         synchronized (this.mSync) {
277                             this.mSync.wait();
278                         }
279
280                         // Persist the exception?
281                         if (this.mCause != null) {
282                             // Cancels the flinger
283                             if (onItemFlingerResponder != null) {
284                                 onItemFlingerResponder.cancel();
285                             }
286
287                             // The exception must be elevated
288                             throw this.mCause;
289                         }
290
291                     } else {
292                         // Cancels the flinger
293                         if (onItemFlingerResponder != null) {
294                             onItemFlingerResponder.cancel();
295                         }
296
297                         // The exception must be elevated
298                         throw e;
299                     }
300                 }
301
302                 // Check that the operation was completed retrieving the deleted fso
303                 boolean failed = false;
304                 try {
305                     CommandHelper.getFileInfo(ctx, fso.getFullPath(), false, null);
306                     FileSystemObject fso2 =
307                             CommandHelper.getFileInfo(ctx, fso.getFullPath(), false, null);
308                     if (fso2 != null) {
309                         // Failed. The file still exists
310                         failed = true;
311                     }
312
313                 } catch (Throwable e) {
314                     // Operation complete successfully
315                 }
316                 if (failed) {
317                     // Cancels the flinger
318                     if (onItemFlingerResponder != null) {
319                         onItemFlingerResponder.cancel();
320                     }
321
322                     throw new ExecutionException(
323                             String.format(
324                                     "Failed to delete file: %s", fso.getFullPath())); //$NON-NLS-1$
325                 }
326             }
327         };
328         final BackgroundAsyncTask task = new BackgroundAsyncTask(ctx, callable);
329
330         // Execute background task
331         task.execute(task);
332     }
333
334     /**
335      * Method that check the consistency of delete operations.<br/>
336      * <br/>
337      * The method checks the following rules:<br/>
338      * <ul>
339      * <li>Any of the files of the move or delete operation can not include the
340      * current directory.</li>
341      * </ul>
342      *
343      * @param ctx The current context
344      * @param files The list of source/destination files
345      * @param currentDirectory The current directory
346      * @return boolean If the consistency is validate successfully
347      */
348     private static boolean checkRemoveConsistency(
349             Context ctx, List<FileSystemObject> files, String currentDirectory) {
350         int cc = files.size();
351         for (int i = 0; i < cc; i++) {
352             FileSystemObject fso = files.get(i);
353
354             // 1.- Current directory can't be deleted
355             if (currentDirectory.startsWith(fso.getFullPath())) {
356                 // Operation not allowed
357                 AlertDialog dialog =
358                         DialogHelper.createWarningDialog(
359                                 ctx,
360                                 R.string.warning_title,
361                                 R.string.msgs_unresolved_inconsistencies);
362                 DialogHelper.delegateDialogShow(ctx, dialog);
363                 return false;
364             }
365         }
366         return true;
367     }
368 }