/**
   * 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;
          UncompressExecutable cmd;

          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 0;
          }

          @Override
          public boolean isDialogCancellable() {
            return true;
          }

          @Override
          public Spanned requestProgress() {
            // Initializing the dialog
            if (!this.mStarted) {
              String progress =
                  this.mCtx.getResources().getString(R.string.waiting_dialog_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() {
            try {
              if (this.cmd != null && this.cmd.isCancellable() && !this.cmd.isCancelled()) {
                this.cmd.cancel();
              }
            } catch (Exception e) {
              /** NON BLOCK* */
            }

            // Operation complete. Refresh
            if (this.mOnRequestRefreshListener != null) {
              // The reference is not the same, so refresh the complete navigation view
              this.mOnRequestRefreshListener.onRequestRefresh(null);
            }
            if (this.cmd != null) {
              showOperationSuccessMsg(
                  ctx, R.string.msgs_extracting_success, this.cmd.getOutUncompressedFile());
            } else {
              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 {
              this.cmd =
                  CommandHelper.uncompress(
                      ctx, this.mFso.getFullPath(), null, this.mListener, null);
              out = this.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 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) {
                  // 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 uncompressed
            // file or folder
            boolean failed = false;
            try {
              FileSystemObject fso2 = CommandHelper.getFileInfo(ctx, out, false, null);
              if (fso2 == null) {
                // Failed. The file or folder not exists
                failed = true;
              }

              // Operation complete successfully

            } catch (Throwable e) {
              // Failed. The file or folder not exists
              failed = true;
            }
            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 =
          FileManagerApplication.getBackgroundConsole()
              .getExecutableFactory()
              .newCreator()
              .createUncompressExecutable(fso.getFullPath(), null, 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,
              R.string.confirm_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);
                  }
                }
              });
      DialogHelper.delegateDialogShow(ctx, dialog);
    } else {
      // Execute background task
      task.execute(task);
    }
  }
  /**
   * Method that compresses some uncompressed files or folders
   *
   * @param ctx The current context
   * @param mode The compression mode
   * @param fsos The list of files to compress
   * @param onSelectionListener The listener for obtain selection information (required)
   * @param onRequestRefreshListener The listener for request a refresh (optional)
   * @hide
   */
  static void compress(
      final Context ctx,
      final CompressionMode mode,
      final List<FileSystemObject> fsos,
      final OnSelectionListener onSelectionListener,
      final OnRequestRefreshListener onRequestRefreshListener) {

    // The callable interface
    final BackgroundCallable callable =
        new BackgroundCallable() {
          // The current items
          final Context mCtx = ctx;
          final CompressionMode mMode = mode;
          final List<FileSystemObject> mFsos = fsos;
          final OnRequestRefreshListener mOnRequestRefreshListener = onRequestRefreshListener;

          final Object mSync = new Object();
          Throwable mCause;
          CompressExecutable cmd = null;

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

          @Override
          public int getDialogTitle() {
            return R.string.waiting_dialog_compressing_title;
          }

          @Override
          public int getDialogIcon() {
            return 0;
          }

          @Override
          public boolean isDialogCancellable() {
            return true;
          }

          @Override
          public Spanned requestProgress() {
            // Initializing the dialog
            if (!this.mStarted) {
              String progress =
                  this.mCtx.getResources().getString(R.string.waiting_dialog_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_compressing_msg, msg);
            return Html.fromHtml(progress);
          }

          @Override
          public void onSuccess() {
            try {
              if (this.cmd != null && this.cmd.isCancellable() && !this.cmd.isCancelled()) {
                this.cmd.cancel();
              }
            } catch (Exception e) {
              /** NON BLOCK* */
            }

            // Operation complete. Refresh
            if (this.mOnRequestRefreshListener != null) {
              // The reference is not the same, so refresh the complete navigation view
              this.mOnRequestRefreshListener.onRequestRefresh(null);
            }
            if (this.cmd != null) {
              showOperationSuccessMsg(
                  ctx, R.string.msgs_compressing_success, this.cmd.getOutCompressedFile());
            } else {
              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 {
              // Archive or Archive-Compression
              if (this.mMode.mArchive) {
                // Convert the list to an array of full paths
                String[] src = new String[this.mFsos.size()];
                int cc = this.mFsos.size();
                for (int i = 0; i < cc; i++) {
                  src[i] = this.mFsos.get(i).getFullPath();
                }

                // Use the current directory name for create the compressed file
                String curDirName = new File(onSelectionListener.onRequestCurrentDir()).getName();
                if (src.length == 1) {
                  // But only one file is passed, then used the name of unique file
                  curDirName = FileHelper.getName(this.mFsos.get(0).getName());
                }
                String name =
                    String.format("%s.%s", curDirName, this.mMode.mExtension); // $NON-NLS-1$
                String newName =
                    FileHelper.createNonExistingName(
                        ctx,
                        onSelectionListener.onRequestCurrentItems(),
                        name,
                        R.string.create_new_compress_file_regexp);
                String newNameAbs =
                    new File(onSelectionListener.onRequestCurrentDir(), newName).getAbsolutePath();

                // Do the compression
                this.cmd =
                    CommandHelper.compress(ctx, this.mMode, newNameAbs, src, this.mListener, null);

                // Compression
              } else {
                // Only the first item from the list is valid. If there are more in the
                // list, then discard them
                String src = this.mFsos.get(0).getFullPath();

                // Do the compression
                this.cmd = CommandHelper.compress(ctx, this.mMode, src, this.mListener, null);
              }
              out = this.cmd.getOutCompressedFile();

              // 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 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) {
                  // 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 compressed file or folder
            boolean failed = false;
            try {
              FileSystemObject fso = CommandHelper.getFileInfo(ctx, out, false, null);
              if (fso == null) {
                // Failed. The file or folder not exists
                failed = true;
              }

              // Operation complete successfully

            } catch (Throwable e) {
              // Failed. The file or folder not exists
              failed = true;
            }
            if (failed) {
              throw new ExecutionException(
                  String.format("Failed to compress file(s) to: %s", out)); // $NON-NLS-1$
            }
          }
        };
    final BackgroundAsyncTask task = new BackgroundAsyncTask(ctx, callable);

    // Check if the output exists. When the mode is archive, this method generate a new
    // name based in the current directory. When the mode is compressed then the name
    // is the name of the file to compress without extension. In this case the name should
    // be validate prior to compress
    boolean askUser = false;
    try {
      if (!mode.mArchive) {
        // Only the first item from the list is valid. If there are more in the
        // list, then discard them
        String src = fsos.get(0).getFullPath();
        CompressExecutable ucmd =
            FileManagerApplication.getBackgroundConsole()
                .getExecutableFactory()
                .newCreator()
                .createCompressExecutable(mode, src, null);
        String dst = ucmd.getOutCompressedFile();
        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,
              R.string.confirm_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) {
                    // Execute background task
                    task.execute(task);
                  }
                }
              });
      DialogHelper.delegateDialogShow(ctx, dialog);
    } else {
      // Execute background task
      task.execute(task);
    }
  }