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

Commit 54e70eae authored by jruesga's avatar jruesga
Browse files

New fso action "Extract"

parent f3dd56e1
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -94,6 +94,10 @@
      android:id="@+id/mnu_actions_rename"
      android:showAsAction="ifRoom"
      android:title="@string/actions_menu_rename"/>
    <item
      android:id="@+id/mnu_actions_extract"
      android:showAsAction="ifRoom"
      android:title="@string/actions_menu_extract"/>
    <item
      android:id="@+id/mnu_actions_create_copy"
      android:showAsAction="ifRoom"
+18 −1
Original line number Diff line number Diff line
@@ -413,7 +413,15 @@
  <string name="waiting_dialog_deleting_title">Deleting&#8230;</string>
  <!-- Waiting dialog * Deleting message -->
  <string name="waiting_dialog_deleting_msg">
    <![CDATA[<b>File</b>]]> <xliff:g id="filr">%1$s</xliff:g></string>
    <![CDATA[<b>File</b>]]> <xliff:g id="file">%1$s</xliff:g></string>
  <!-- Waiting dialog * Extracting title -->
  <string name="waiting_dialog_extracting_title">Extracting&#8230;</string>
  <!-- Waiting dialog * Extracting message -->
  <string name="waiting_dialog_extracting_msg">
    <![CDATA[<b>File</b>]]> <xliff:g id="file">%1$s</xliff:g></string>
  <!-- Waiting dialog * Initializing the dialog -->
  <string name="waiting_dialog_extracting_analizing_msg">
    <![CDATA[<b>Analizing&#8230;</b>]]></string>

  <!-- Actions Dialog * Title -->
  <string name="actions_dialog_title">Actions</string>
@@ -451,6 +459,8 @@
  <string name="actions_menu_execute">Execute</string>
  <!-- Actions Dialog * Menu * Send -->
  <string name="actions_menu_send">Send</string>
  <!-- Actions Dialog * Menu * Extract -->
  <string name="actions_menu_extract">Extract</string>
  <!-- Actions Dialog * Menu * Share -->
  <string name="actions_menu_share">Share</string>
  <!-- Actions Dialog * Menu * Delete -->
@@ -672,4 +682,11 @@
  <!-- Preferences * Debug * Capture debug traces summary off -->
  <string name="pref_debug_traces_off">Capture of debug traces is disabled</string>

  <!-- Security * Extract relative or absolute files -->
  <string name="security_warning_extract">Warning!\n\n
    Extracting an archive file with relative or absolute paths may cause damage to your system,
    because the operation could overwrite of some system files.\n\n
    Continue?</string>


</resources>
 No newline at end of file
+17 −0
Original line number Diff line number Diff line
@@ -298,6 +298,14 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
                }
                break;

            //- Uncompress
            case R.id.mnu_actions_extract:
                ActionsPolicy.uncompress(
                            this.mContext,
                            this.mFso,
                            this.mOnRequestRefreshListener);
                break;

            //- Create copy
            case R.id.mnu_actions_create_copy:
                // Create a copy of the fso
@@ -549,6 +557,15 @@ public class ActionsDialog implements OnItemClickListener, OnItemLongClickListen
                }
            }
        }

        // Compress/Uncompress (only when selection is available)
        if (this.mOnSelectionListener != null) {
            //Uncompress
            if (!this.mGlobal && !FileHelper.isSupportedUncompressedFile(this.mFso)) {
                menu.removeItem(R.id.mnu_actions_extract);
            }
        }

    }

    /**
+316 −7
Original line number Diff line number Diff line
@@ -29,9 +29,12 @@ import android.text.Spanned;
import android.util.Log;
import android.widget.Toast;

import com.cyanogenmod.explorer.ExplorerApplication;
import com.cyanogenmod.explorer.R;
import com.cyanogenmod.explorer.commands.AsyncResultListener;
import com.cyanogenmod.explorer.commands.ExecExecutable;
import com.cyanogenmod.explorer.commands.UncompressExecutable;
import com.cyanogenmod.explorer.console.ConsoleBuilder;
import com.cyanogenmod.explorer.console.ExecutionException;
import com.cyanogenmod.explorer.console.RelaunchableException;
import com.cyanogenmod.explorer.listeners.OnRequestRefreshListener;
@@ -49,6 +52,7 @@ import com.cyanogenmod.explorer.util.DialogHelper;
import com.cyanogenmod.explorer.util.ExceptionUtil;
import com.cyanogenmod.explorer.util.ExceptionUtil.OnRelaunchCommandResult;
import com.cyanogenmod.explorer.util.FileHelper;
import com.cyanogenmod.explorer.util.FixedQueue;
import com.cyanogenmod.explorer.util.MimeTypeHelper;

import java.io.File;
@@ -255,6 +259,55 @@ public final class ActionsPolicy {
        }
    }

    /**
     * A class that holds a listener for compression/uncompression operations
     */
    private static class CompressListener implements AsyncResultListener {

        Object mSync;
        final FixedQueue<String> mQueue;
        boolean mEnd;
        Throwable mCause;

        /**
         * Constructor of <code>CompressListener</code>
         */
        public CompressListener() {
            super();
            this.mEnd = false;
            this.mSync = new Object();
            this.mQueue = new FixedQueue<String>(2); //Holds only one item
            this.mCause = null;
        }

        @Override
        public void onPartialResult(Object result) {
            synchronized (this.mSync) {
                this.mQueue.insert((String)result);
            }
        }

        @Override
        public void onException(Exception cause) {
            synchronized (this.mSync) {
                this.mCause = cause;
            }
        }

        @Override
        public void onAsyncStart() {/**NON BLOCK**/}

        @Override
        public void onAsyncEnd(boolean canceled) {/**NON BLOCK**/}

        @Override
        public void onAsyncExitCode(int exitCode) {
            synchronized (this.mSync) {
                this.mEnd = true;
            }
        }
    }

    /**
     * @hide
     */
@@ -495,7 +548,7 @@ public final class ActionsPolicy {

                        // Execute the script
                        ExecExecutable cmd =
                                (ExecExecutable)CommandHelper.exec(
                                CommandHelper.exec(
                                ctx, fso.getFullPath(), listener, null);
                        dialog.setCmd(cmd);
                    } catch (Exception e) {
@@ -850,12 +903,10 @@ public final class ActionsPolicy {
            }

            /**
             * Method that copies on file to other location
             * Method that deletes the file or directory
             *
             * @param ctx The current context
             * @param src The source file
             * @param dst The destination file
             * @param move Indicates if the files are going to be moved (true) or copied (false)
             * @param fso The file or folder to be deleted
             */
            @SuppressWarnings("hiding")
            private void doOperation(
@@ -1175,7 +1226,7 @@ public final class ActionsPolicy {
            }

            /**
             * Method that copies on file to other location
             * Method that copy or move the file to another location
             *
             * @param ctx The current context
             * @param src The source file
@@ -1289,6 +1340,264 @@ public final class ActionsPolicy {
        task.execute(task);
    }

    /**
     * Method that uncompress a compressed file.
     *
     * @param ctx The current context
     * @param fso The compressed file
     * @param onRequestRefreshListener The listener for request a refresh (optional)
     * @hide
     */
    public static void uncompress(
            final Context ctx, final FileSystemObject fso,
            final OnRequestRefreshListener onRequestRefreshListener) {

        // The callable interface
        final BackgroundCallable callable = new BackgroundCallable() {
            // The current items
            final Context mCtx = ctx;
            final FileSystemObject mFso = fso;
            final OnRequestRefreshListener mOnRequestRefreshListener = onRequestRefreshListener;

            final Object mSync = new Object();
            Throwable mCause;

            final CompressListener mListener =
                                new CompressListener();
            private String mMsg;
            private boolean mStarted = false;

            @Override
            public int getDialogTitle() {
                return R.string.waiting_dialog_extracting_title;
            }
            @Override
            public int getDialogIcon() {
                return R.drawable.ic_holo_light_operation;
            }

            @Override
            public Spanned requestProgress() {
                // Initializing the dialog
                if (!this.mStarted) {
                    String progress =
                            this.mCtx.getResources().
                                getString(
                                    R.string.waiting_dialog_extracting_analizing_msg);
                    return Html.fromHtml(progress);
                }

                // Return the current operation
                String msg = (this.mMsg == null) ? "" : this.mMsg; //$NON-NLS-1$
                String progress =
                      this.mCtx.getResources().
                          getString(
                              R.string.waiting_dialog_extracting_msg,
                              msg);
                return Html.fromHtml(progress);
            }

            @Override
            public void onSuccess() {
                //Operation complete. Refresh
                if (this.mOnRequestRefreshListener != null) {
                  // The reference is not the same, so refresh the complete navigation view
                  this.mOnRequestRefreshListener.onRequestRefresh(null);
                }
                ActionsPolicy.showOperationSuccessMsg(ctx);
            }

            @Override
            public void doInBackground(Object... params) throws Throwable {
                this.mCause = null;
                this.mStarted = true;

                // This method expect to receive
                // 1.- BackgroundAsyncTask
                BackgroundAsyncTask task = (BackgroundAsyncTask)params[0];
                String out = null;
                try {
                    UncompressExecutable cmd =
                            CommandHelper.uncompress(
                                    ctx,
                                    this.mFso.getFullPath(),
                                    this.mListener, null);
                    out = cmd.getOutUncompressedFile();

                    // Request paint the
                    this.mListener.mQueue.insert(out);
                    task.onRequestProgress();

                    // Don't use an active blocking because this suppose that all message
                    // will be processed by the UI. Instead, refresh with a delay and
                    // display the active file
                    while (!this.mListener.mEnd) {
                        // Sleep to don't saturate the UI thread
                        Thread.sleep(50L);

                        List<String> msgs = this.mListener.mQueue.peekAll();
                        if (msgs.size() > 0) {
                            this.mMsg = msgs.get(msgs.size()-1);
                            task.onRequestProgress();
                        }
                    }

                    // Dialog is ended. Force the last redraw
                    List<String> msgs = this.mListener.mQueue.peekAll();
                    if (msgs.size() > 0) {
                        this.mMsg = msgs.get(msgs.size()-1);
                        task.onRequestProgress();
                    }

                } 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 onCanceled() {
                                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) {
                            // The exception must be elevated
                            throw this.mCause;
                        }

                    } else {
                        // The exception must be elevated
                        throw e;
                    }
                }


                // Any exception?
                if (this.mListener.mCause != null) {
                    throw this.mListener.mCause;
                }

                // Check that the operation was completed retrieving the extracted file or folder
                boolean failed = true;
                try {
                    CommandHelper.getFileInfo(ctx, out, false, null);

                    // Failed. The file exists
                    failed = false;

                } catch (Throwable e) {
                    // Operation complete successfully
                }
                if (failed) {
                    throw new ExecutionException(
                            String.format(
                                    "Failed to extract file: %s", //$NON-NLS-1$
                                    this.mFso.getFullPath()));
                }
            }
        };
        final BackgroundAsyncTask task = new BackgroundAsyncTask(ctx, callable);

        // Check if the output exists
        boolean askUser = false;
        try {
            UncompressExecutable ucmd =
                    ExplorerApplication.getBackgroundConsole().
                        getExecutableFactory().newCreator().
                            createUncompressExecutable(fso.getFullPath(), null);
            String dst = ucmd.getOutUncompressedFile();
            FileSystemObject info = CommandHelper.getFileInfo(ctx, dst, null);
            if (info != null) {
                askUser = true;
            }
        } catch (Exception e) {/**NON BLOCK**/}

        // Ask the user because the destination file or folder exists
        if (askUser) {
            //Show a dialog asking the user for overwrite the files
            AlertDialog dialog =
                    DialogHelper.createTwoButtonsQuestionDialog(
                            ctx,
                            android.R.string.cancel,
                            R.string.overwrite,
                            ctx.getString(R.string.msgs_overwrite_files),
                            new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface alertDialog, int which) {
                                    // NEGATIVE (overwrite)  POSITIVE (cancel)
                                    if (which == DialogInterface.BUTTON_NEGATIVE) {
                                        // Check if the necessary to display a warning because
                                        // security issues
                                        checkZipSecurityWarning(ctx, task, fso);
                                    }
                                }
                           });
            dialog.show();
        } else {
            // Execute background task
            task.execute(task);
        }
    }

    /**
     * Method that checks if it is necessary to display a warning dialog because
     * the privileged extraction of a zip file.
     *
     * @param ctx The current context
     * @param task The task
     * @param fso The zip file
     * @hide
     */
    static void checkZipSecurityWarning(
            final Context ctx, final BackgroundAsyncTask task, FileSystemObject fso) {
        // WARNING! Extracting a ZIP file with relatives or absolutes path could break
        // the system and is need a security alert that the user can confirm prior to
        // make the extraction
        String ext = FileHelper.getExtension(fso);
        if (ConsoleBuilder.isPrivileged() && ext.compareTo("zip") == 0) { //$NON-NLS-1$
            AlertDialog dialog =DialogHelper.createYesNoDialog(
                ctx, R.string.security_warning_extract,
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface alertDialog, int which) {
                        if (which == DialogInterface.BUTTON_POSITIVE) {
                            // Execute background task
                            task.execute(task);
                        }
                    }
               });
            dialog.show();
        } else {
            // Execute background task
            task.execute(task);
        }
    }

    /**
     * Method that check if is needed to prompt the user for overwrite prior to do
     * the operation.