From 7a2c28421fb466b3099243ac77193370594075b9 Mon Sep 17 00:00:00 2001 From: Yatik Date: Fri, 4 Aug 2023 14:26:08 +0530 Subject: [PATCH 1/9] Disabled file modification during copy/paste. Users can now paste files in the same folder. --- .../filemanager/adapters/RecyclerAdapter.java | 108 +++++++++++++----- .../asynctasks/movecopy/PrepareCopyTask.java | 73 ++++++++---- .../ui/activities/MainActivity.java | 2 +- .../utils/MainActivityActionMode.kt | 3 +- app/src/main/res/values/strings.xml | 1 + 5 files changed, 133 insertions(+), 54 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java b/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java index bffe1cdd6c..5e6dfc0e30 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java @@ -20,7 +20,23 @@ package com.amaze.filemanager.adapters; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.*; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtension7zip; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionApk; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionApks; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionBzip2; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionBzip2TarLong; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionBzip2TarShort; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionGz; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionGzipTarLong; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionGzipTarShort; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionJar; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionLzma; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionRar; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionTar; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionTarLzma; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionTarXz; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionXz; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionZip; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_COLORIZE_ICONS; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_FILE_SIZE; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_GOBACK_BUTTON; @@ -30,11 +46,32 @@ import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_THUMB; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_USE_CIRCULAR_IMAGES; -import java.util.ArrayList; -import java.util.List; +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import android.os.Handler; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.PopupMenu; +import android.widget.Toast; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.view.ActionMode; +import androidx.appcompat.view.ContextThemeWrapper; +import androidx.appcompat.widget.AppCompatImageView; +import androidx.appcompat.widget.AppCompatTextView; +import androidx.recyclerview.widget.RecyclerView; import com.amaze.filemanager.GlideApp; import com.amaze.filemanager.R; @@ -47,6 +84,7 @@ import com.amaze.filemanager.adapters.holders.SpecialViewHolder; import com.amaze.filemanager.application.AppConfig; import com.amaze.filemanager.fileoperations.filesystem.OpenMode; +import com.amaze.filemanager.filesystem.PasteHelper; import com.amaze.filemanager.filesystem.files.CryptUtil; import com.amaze.filemanager.ui.ItemPopupMenu; import com.amaze.filemanager.ui.activities.superclasses.PreferenceActivity; @@ -62,6 +100,7 @@ import com.amaze.filemanager.ui.views.CircleGradientDrawable; import com.amaze.filemanager.utils.AnimUtils; import com.amaze.filemanager.utils.GlideConstants; +import com.amaze.filemanager.utils.MainActivityActionMode; import com.amaze.filemanager.utils.Utils; import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader; import com.bumptech.glide.load.DataSource; @@ -69,31 +108,11 @@ import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.Target; -import android.app.Activity; -import android.content.Context; -import android.content.SharedPreferences; -import android.graphics.Color; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; -import android.os.Build; -import android.os.Handler; -import android.text.TextUtils; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.PopupMenu; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.view.ActionMode; -import androidx.appcompat.view.ContextThemeWrapper; -import androidx.appcompat.widget.AppCompatImageView; -import androidx.appcompat.widget.AppCompatTextView; -import androidx.recyclerview.widget.RecyclerView; +import java.util.ArrayList; +import java.util.List; /** * This class is the information that serves to load the files into a "list" (a RecyclerView). There @@ -763,6 +782,16 @@ private void bindViewHolderList(@NonNull final ItemViewHolder holder, int positi holder.baseItemView.setOnLongClickListener( p1 -> { + MainActivityActionMode mainActivityActionMode = mainFragment.getMainActivity().mainActivityActionMode; + PasteHelper pasteHelper = mainActivityActionMode.getPasteHelper(); + + if (pasteHelper != null && pasteHelper.getSnackbar() != null && pasteHelper.getSnackbar().isShown()) { + Toast.makeText( + mainFragment.requireContext(), + mainFragment.getString(R.string.complete_paste_warning), + Toast.LENGTH_LONG).show(); + return false; + } if (!isBackButton) { if (dragAndDropPreference == PreferencesConstants.PREFERENCE_DRAG_DEFAULT || (dragAndDropPreference == PreferencesConstants.PREFERENCE_DRAG_TO_MOVE_COPY @@ -976,6 +1005,16 @@ private void bindViewHolderGrid(@NonNull final ItemViewHolder holder, int positi holder.baseItemView.setOnLongClickListener( p1 -> { + MainActivityActionMode mainActivityActionMode = mainFragment.getMainActivity().mainActivityActionMode; + PasteHelper pasteHelper = mainActivityActionMode.getPasteHelper(); + + if (pasteHelper != null && pasteHelper.getSnackbar() != null && pasteHelper.getSnackbar().isShown()) { + Toast.makeText( + mainFragment.requireContext(), + mainFragment.getString(R.string.complete_paste_warning), + Toast.LENGTH_LONG).show(); + return false; + } if (!isBackButton) { if (dragAndDropPreference == PreferencesConstants.PREFERENCE_DRAG_DEFAULT || (dragAndDropPreference == PreferencesConstants.PREFERENCE_DRAG_TO_MOVE_COPY @@ -1366,6 +1405,17 @@ public boolean onResourceReady( } private void showPopup(@NonNull View view, @NonNull final LayoutElementParcelable rowItem) { + + MainActivityActionMode mainActivityActionMode = mainFragment.getMainActivity().mainActivityActionMode; + PasteHelper pasteHelper = mainActivityActionMode.getPasteHelper(); + + if (pasteHelper != null && pasteHelper.getSnackbar() != null && pasteHelper.getSnackbar().isShown()) { + Toast.makeText( + mainFragment.requireContext(), + mainFragment.getString(R.string.complete_paste_warning), + Toast.LENGTH_LONG).show(); + return; + } Context currentContext = this.context; if (mainFragment.getMainActivity().getAppTheme().getSimpleTheme(mainFragment.requireContext()) == AppTheme.BLACK) { diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java index 9d06ddb19d..a4561ad9dc 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java @@ -24,14 +24,16 @@ import static com.amaze.filemanager.fileoperations.filesystem.OperationTypeKt.COPY; import static com.amaze.filemanager.fileoperations.filesystem.OperationTypeKt.MOVE; -import java.io.File; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Set; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; +import android.view.LayoutInflater; +import android.widget.Toast; + +import androidx.annotation.IntDef; +import androidx.appcompat.widget.AppCompatCheckBox; -import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.amaze.filemanager.R; import com.amaze.filemanager.asynchronous.asynctasks.TaskKt; @@ -46,16 +48,12 @@ import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.utils.Utils; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.Toast; - -import androidx.annotation.IntDef; -import androidx.appcompat.widget.AppCompatCheckBox; +import java.io.File; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; /** * This AsyncTask works by creating a tree where each folder that can be fusioned together with @@ -88,6 +86,7 @@ public class PrepareCopyTask extends AsyncTask filesToCopy, ArrayList conflictingFiles) { - if (counter < conflictingFiles.size()) { + + if (doRenaming && dialogState != UNKNOWN) { + while (!conflictingFiles.isEmpty()) { + resolveByRenaming(filesToCopy, conflictingFiles); + } + } else if (doRenaming) { + resolveByRenaming(filesToCopy, conflictingFiles); + doRenaming = false; + } else if (counter < conflictingFiles.size()) { if (dialogState != UNKNOWN) { counter++; } else { @@ -321,6 +327,27 @@ private void replaceFiles( onEndDialog(path, filesToCopy, conflictingFiles); } + private void resolveByRenaming( + ArrayList filesToCopy, + ArrayList conflictingFiles + ) { + int appendInt = 1; + String newName; + File targetFile; + + int conflictingFileIndex = filesToCopy.indexOf(conflictingFiles.get(0)); + String oldName = filesToCopy.get(conflictingFileIndex).getName(); + do { + newName = oldName.substring(0, oldName.lastIndexOf(".")) + + "(" + appendInt + ")" + + oldName.substring(oldName.lastIndexOf(".")); + appendInt++; + targetFile = new File(path, newName); + } while (targetFile.exists()); + filesToCopy.get(conflictingFileIndex).setName(newName); + conflictingFiles.remove(0); + } + private void finishCopying( ArrayList paths, ArrayList> filesToCopyPerFolder) { for (int i = 0; i < filesToCopyPerFolder.size(); i++) { diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java index d268f41ad1..af2754c366 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java @@ -324,7 +324,7 @@ public class MainActivity extends PermissionsActivity public static final int REQUEST_CODE_CLOUD_LIST_KEY = 5472; private PasteHelper pasteHelper; - private MainActivityActionMode mainActivityActionMode; + public MainActivityActionMode mainActivityActionMode; private static final String DEFAULT_FALLBACK_STORAGE_PATH = "/storage/sdcard0"; private static final String INTERNAL_SHARED_STORAGE = "Internal shared storage"; diff --git a/app/src/main/java/com/amaze/filemanager/utils/MainActivityActionMode.kt b/app/src/main/java/com/amaze/filemanager/utils/MainActivityActionMode.kt index e84b8f3034..03f626a3f1 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/MainActivityActionMode.kt +++ b/app/src/main/java/com/amaze/filemanager/utils/MainActivityActionMode.kt @@ -49,6 +49,7 @@ class MainActivityActionMode(private val mainActivityReference: WeakReferenceTry Indexed Search! Recent Results + Please complete paste operation first From 69a86841a9871200c6b394ffc3b1f70497a6ec89 Mon Sep 17 00:00:00 2001 From: Yatik Date: Fri, 4 Aug 2023 16:10:39 +0530 Subject: [PATCH 2/9] Fixed issues which were causing spotlessCheck to fail. --- .../filemanager/adapters/RecyclerAdapter.java | 106 +++++++++--------- .../asynctasks/movecopy/PrepareCopyTask.java | 73 ++++++------ 2 files changed, 90 insertions(+), 89 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java b/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java index 5e6dfc0e30..a58e3a1044 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java @@ -20,23 +20,7 @@ package com.amaze.filemanager.adapters; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtension7zip; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionApk; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionApks; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionBzip2; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionBzip2TarLong; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionBzip2TarShort; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionGz; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionGzipTarLong; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionGzipTarShort; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionJar; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionLzma; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionRar; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionTar; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionTarLzma; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionTarXz; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionXz; -import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.fileExtensionZip; +import static com.amaze.filemanager.filesystem.compressed.CompressedHelper.*; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_COLORIZE_ICONS; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_FILE_SIZE; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_GOBACK_BUTTON; @@ -46,32 +30,11 @@ import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_THUMB; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_USE_CIRCULAR_IMAGES; -import android.app.Activity; -import android.content.Context; -import android.content.SharedPreferences; -import android.graphics.Color; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; -import android.os.Build; -import android.os.Handler; -import android.text.TextUtils; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.PopupMenu; -import android.widget.Toast; +import java.util.ArrayList; +import java.util.List; -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.view.ActionMode; -import androidx.appcompat.view.ContextThemeWrapper; -import androidx.appcompat.widget.AppCompatImageView; -import androidx.appcompat.widget.AppCompatTextView; -import androidx.recyclerview.widget.RecyclerView; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.amaze.filemanager.GlideApp; import com.amaze.filemanager.R; @@ -108,11 +71,32 @@ import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.Target; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import android.os.Handler; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.PopupMenu; +import android.widget.Toast; -import java.util.ArrayList; -import java.util.List; +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.view.ActionMode; +import androidx.appcompat.view.ContextThemeWrapper; +import androidx.appcompat.widget.AppCompatImageView; +import androidx.appcompat.widget.AppCompatTextView; +import androidx.recyclerview.widget.RecyclerView; /** * This class is the information that serves to load the files into a "list" (a RecyclerView). There @@ -782,14 +766,18 @@ private void bindViewHolderList(@NonNull final ItemViewHolder holder, int positi holder.baseItemView.setOnLongClickListener( p1 -> { - MainActivityActionMode mainActivityActionMode = mainFragment.getMainActivity().mainActivityActionMode; + MainActivityActionMode mainActivityActionMode = + mainFragment.getMainActivity().mainActivityActionMode; PasteHelper pasteHelper = mainActivityActionMode.getPasteHelper(); - if (pasteHelper != null && pasteHelper.getSnackbar() != null && pasteHelper.getSnackbar().isShown()) { + if (pasteHelper != null + && pasteHelper.getSnackbar() != null + && pasteHelper.getSnackbar().isShown()) { Toast.makeText( mainFragment.requireContext(), mainFragment.getString(R.string.complete_paste_warning), - Toast.LENGTH_LONG).show(); + Toast.LENGTH_LONG) + .show(); return false; } if (!isBackButton) { @@ -1005,14 +993,18 @@ private void bindViewHolderGrid(@NonNull final ItemViewHolder holder, int positi holder.baseItemView.setOnLongClickListener( p1 -> { - MainActivityActionMode mainActivityActionMode = mainFragment.getMainActivity().mainActivityActionMode; + MainActivityActionMode mainActivityActionMode = + mainFragment.getMainActivity().mainActivityActionMode; PasteHelper pasteHelper = mainActivityActionMode.getPasteHelper(); - if (pasteHelper != null && pasteHelper.getSnackbar() != null && pasteHelper.getSnackbar().isShown()) { + if (pasteHelper != null + && pasteHelper.getSnackbar() != null + && pasteHelper.getSnackbar().isShown()) { Toast.makeText( mainFragment.requireContext(), mainFragment.getString(R.string.complete_paste_warning), - Toast.LENGTH_LONG).show(); + Toast.LENGTH_LONG) + .show(); return false; } if (!isBackButton) { @@ -1406,14 +1398,18 @@ public boolean onResourceReady( private void showPopup(@NonNull View view, @NonNull final LayoutElementParcelable rowItem) { - MainActivityActionMode mainActivityActionMode = mainFragment.getMainActivity().mainActivityActionMode; + MainActivityActionMode mainActivityActionMode = + mainFragment.getMainActivity().mainActivityActionMode; PasteHelper pasteHelper = mainActivityActionMode.getPasteHelper(); - if (pasteHelper != null && pasteHelper.getSnackbar() != null && pasteHelper.getSnackbar().isShown()) { + if (pasteHelper != null + && pasteHelper.getSnackbar() != null + && pasteHelper.getSnackbar().isShown()) { Toast.makeText( mainFragment.requireContext(), mainFragment.getString(R.string.complete_paste_warning), - Toast.LENGTH_LONG).show(); + Toast.LENGTH_LONG) + .show(); return; } Context currentContext = this.context; diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java index a4561ad9dc..921afeb370 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java @@ -24,15 +24,12 @@ import static com.amaze.filemanager.fileoperations.filesystem.OperationTypeKt.COPY; import static com.amaze.filemanager.fileoperations.filesystem.OperationTypeKt.MOVE; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.view.LayoutInflater; -import android.widget.Toast; - -import androidx.annotation.IntDef; -import androidx.appcompat.widget.AppCompatCheckBox; +import java.io.File; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; import com.afollestad.materialdialogs.MaterialDialog; import com.amaze.filemanager.R; @@ -48,12 +45,15 @@ import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.utils.Utils; -import java.io.File; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Set; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; +import android.view.LayoutInflater; +import android.widget.Toast; + +import androidx.annotation.IntDef; +import androidx.appcompat.widget.AppCompatCheckBox; /** * This AsyncTask works by creating a tree where each folder that can be fusioned together with @@ -228,7 +228,10 @@ private void showDialog( dialogBuilder.positiveText(R.string.skip); if (filesToCopy.get(0).getParent(context.get()).equals(path)) { doRenaming = true; - dialogBuilder.negativeText(context.get().getString(R.string.rename) + " & " + context.get().getString(R.string.save)); + dialogBuilder.negativeText( + context.get().getString(R.string.rename) + + " & " + + context.get().getString(R.string.save)); } else dialogBuilder.negativeText(R.string.overwrite); dialogBuilder.neutralText(R.string.cancel); dialogBuilder.positiveColor(accentColor); @@ -328,24 +331,26 @@ private void replaceFiles( } private void resolveByRenaming( - ArrayList filesToCopy, - ArrayList conflictingFiles - ) { - int appendInt = 1; - String newName; - File targetFile; - - int conflictingFileIndex = filesToCopy.indexOf(conflictingFiles.get(0)); - String oldName = filesToCopy.get(conflictingFileIndex).getName(); - do { - newName = oldName.substring(0, oldName.lastIndexOf(".")) - + "(" + appendInt + ")" - + oldName.substring(oldName.lastIndexOf(".")); - appendInt++; - targetFile = new File(path, newName); - } while (targetFile.exists()); - filesToCopy.get(conflictingFileIndex).setName(newName); - conflictingFiles.remove(0); + ArrayList filesToCopy, + ArrayList conflictingFiles) { + int appendInt = 1; + String newName; + File targetFile; + + int conflictingFileIndex = filesToCopy.indexOf(conflictingFiles.get(0)); + String oldName = filesToCopy.get(conflictingFileIndex).getName(); + do { + newName = + oldName.substring(0, oldName.lastIndexOf(".")) + + "(" + + appendInt + + ")" + + oldName.substring(oldName.lastIndexOf(".")); + appendInt++; + targetFile = new File(path, newName); + } while (targetFile.exists()); + filesToCopy.get(conflictingFileIndex).setName(newName); + conflictingFiles.remove(0); } private void finishCopying( From ead2973d245abd9c134bfc4485a25bac71fd6eb4 Mon Sep 17 00:00:00 2001 From: Yatik Date: Mon, 7 Aug 2023 14:14:03 +0530 Subject: [PATCH 3/9] Created `hasPendingPasteOperation()` function to avoid code duplication --- .../filemanager/adapters/RecyclerAdapter.java | 72 ++++++++----------- 1 file changed, 29 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java b/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java index a58e3a1044..72c262372d 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java @@ -50,6 +50,7 @@ import com.amaze.filemanager.filesystem.PasteHelper; import com.amaze.filemanager.filesystem.files.CryptUtil; import com.amaze.filemanager.ui.ItemPopupMenu; +import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.ui.activities.superclasses.PreferenceActivity; import com.amaze.filemanager.ui.colors.ColorUtils; import com.amaze.filemanager.ui.drag.RecyclerAdapterDragListener; @@ -766,20 +767,7 @@ private void bindViewHolderList(@NonNull final ItemViewHolder holder, int positi holder.baseItemView.setOnLongClickListener( p1 -> { - MainActivityActionMode mainActivityActionMode = - mainFragment.getMainActivity().mainActivityActionMode; - PasteHelper pasteHelper = mainActivityActionMode.getPasteHelper(); - - if (pasteHelper != null - && pasteHelper.getSnackbar() != null - && pasteHelper.getSnackbar().isShown()) { - Toast.makeText( - mainFragment.requireContext(), - mainFragment.getString(R.string.complete_paste_warning), - Toast.LENGTH_LONG) - .show(); - return false; - } + if (hasPendingPasteOperation()) return false; if (!isBackButton) { if (dragAndDropPreference == PreferencesConstants.PREFERENCE_DRAG_DEFAULT || (dragAndDropPreference == PreferencesConstants.PREFERENCE_DRAG_TO_MOVE_COPY @@ -993,20 +981,7 @@ private void bindViewHolderGrid(@NonNull final ItemViewHolder holder, int positi holder.baseItemView.setOnLongClickListener( p1 -> { - MainActivityActionMode mainActivityActionMode = - mainFragment.getMainActivity().mainActivityActionMode; - PasteHelper pasteHelper = mainActivityActionMode.getPasteHelper(); - - if (pasteHelper != null - && pasteHelper.getSnackbar() != null - && pasteHelper.getSnackbar().isShown()) { - Toast.makeText( - mainFragment.requireContext(), - mainFragment.getString(R.string.complete_paste_warning), - Toast.LENGTH_LONG) - .show(); - return false; - } + if (hasPendingPasteOperation()) return false; if (!isBackButton) { if (dragAndDropPreference == PreferencesConstants.PREFERENCE_DRAG_DEFAULT || (dragAndDropPreference == PreferencesConstants.PREFERENCE_DRAG_TO_MOVE_COPY @@ -1397,21 +1372,7 @@ public boolean onResourceReady( } private void showPopup(@NonNull View view, @NonNull final LayoutElementParcelable rowItem) { - - MainActivityActionMode mainActivityActionMode = - mainFragment.getMainActivity().mainActivityActionMode; - PasteHelper pasteHelper = mainActivityActionMode.getPasteHelper(); - - if (pasteHelper != null - && pasteHelper.getSnackbar() != null - && pasteHelper.getSnackbar().isShown()) { - Toast.makeText( - mainFragment.requireContext(), - mainFragment.getString(R.string.complete_paste_warning), - Toast.LENGTH_LONG) - .show(); - return; - } + if (hasPendingPasteOperation()) return; Context currentContext = this.context; if (mainFragment.getMainActivity().getAppTheme().getSimpleTheme(mainFragment.requireContext()) == AppTheme.BLACK) { @@ -1474,6 +1435,31 @@ private void showPopup(@NonNull View view, @NonNull final LayoutElementParcelabl popupMenu.show(); } + /** + * Helps in deciding whether to allow file modification or not, depending on the state of the + * copy/paste operation. + * + * @return true if there is an unfinished copy/paste operation, false otherwise. + */ + private boolean hasPendingPasteOperation() { + MainActivity mainActivity = mainFragment.getMainActivity(); + if (mainActivity == null) return false; + MainActivityActionMode mainActivityActionMode = mainActivity.mainActivityActionMode; + PasteHelper pasteHelper = mainActivityActionMode.getPasteHelper(); + + if (pasteHelper != null + && pasteHelper.getSnackbar() != null + && pasteHelper.getSnackbar().isShown()) { + Toast.makeText( + mainFragment.requireContext(), + mainFragment.getString(R.string.complete_paste_warning), + Toast.LENGTH_LONG) + .show(); + return true; + } + return false; + } + private boolean getBoolean(String key) { return preferenceActivity.getBoolean(key); } From 5da1948bf7a6c0bd2f380795b03505a9de02d381 Mon Sep 17 00:00:00 2001 From: Yatik Date: Sun, 13 Aug 2023 23:07:04 +0530 Subject: [PATCH 4/9] Resolved the issue where child names were being changed instead of the directory. --- .../asynctasks/movecopy/PrepareCopyTask.java | 62 ++++++++++++++----- app/src/main/res/values/strings.xml | 1 + 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java index 921afeb370..48c99176b0 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java @@ -27,6 +27,7 @@ import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.Set; @@ -41,6 +42,7 @@ import com.amaze.filemanager.fileoperations.filesystem.OpenMode; import com.amaze.filemanager.filesystem.HybridFile; import com.amaze.filemanager.filesystem.HybridFileParcelable; +import com.amaze.filemanager.filesystem.MakeDirectoryOperation; import com.amaze.filemanager.filesystem.files.FileUtils; import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.utils.Utils; @@ -330,7 +332,7 @@ private void replaceFiles( onEndDialog(path, filesToCopy, conflictingFiles); } - private void resolveByRenaming( + private String resolveByRenaming( ArrayList filesToCopy, ArrayList conflictingFiles) { int appendInt = 1; @@ -338,19 +340,25 @@ private void resolveByRenaming( File targetFile; int conflictingFileIndex = filesToCopy.indexOf(conflictingFiles.get(0)); - String oldName = filesToCopy.get(conflictingFileIndex).getName(); + HybridFileParcelable originalFile = filesToCopy.get(conflictingFileIndex); + String oldName = originalFile.getName(); do { - newName = - oldName.substring(0, oldName.lastIndexOf(".")) - + "(" - + appendInt - + ")" - + oldName.substring(oldName.lastIndexOf(".")); + if (originalFile.isDirectory()) { + newName = oldName + "(" + appendInt + ")"; + } else { + newName = + oldName.substring(0, oldName.lastIndexOf(".")) + + "(" + + appendInt + + ")" + + oldName.substring(oldName.lastIndexOf(".")); + } appendInt++; targetFile = new File(path, newName); } while (targetFile.exists()); filesToCopy.get(conflictingFileIndex).setName(newName); conflictingFiles.remove(0); + return newName; } private void finishCopying( @@ -407,15 +415,37 @@ class CopyNode { conflictingFiles = checkConflicts(filesToCopy, destination); for (int i = 0; i < conflictingFiles.size(); i++) { + String currentPath = path + "/" + conflictingFiles.get(i).getName(); + + // If move operation is in same directory, do nothing. + if (conflictingFiles.get(i).getPath().equals(currentPath) && move) { + filesToCopy.clear(); + conflictingFiles.clear(); + publishProgress(context.get().getString(R.string.same_dir_move_error)); + break; + } if (conflictingFiles.get(i).isDirectory()) { if (deleteCopiedFolder == null) deleteCopiedFolder = new ArrayList<>(); - deleteCopiedFolder.add(new File(conflictingFiles.get(i).getPath())); - nextNodes.add( - new CopyNode( - path + "/" + conflictingFiles.get(i).getName(context.get()), - conflictingFiles.get(i).listFiles(context.get(), rootMode))); + if (conflictingFiles.get(i).getPath().equals(currentPath)) { + String newName = + resolveByRenaming( + new ArrayList<>(Collections.singletonList(conflictingFiles.get(i))), + new ArrayList<>(Collections.singletonList(conflictingFiles.get(i)))); + String newPath = path + "/" + newName; + + HybridFile hybridFile = + new HybridFile(conflictingFiles.get(i).getMode(), newPath, newName, true); + MakeDirectoryOperation.mkdirs(context.get(), hybridFile); + nextNodes.add( + new CopyNode(newPath, conflictingFiles.get(i).listFiles(context.get(), rootMode))); + } else { + nextNodes.add( + new CopyNode( + path + "/" + conflictingFiles.get(i).getName(context.get()), + conflictingFiles.get(i).listFiles(context.get(), rootMode))); + } filesToCopy.remove(filesToCopy.indexOf(conflictingFiles.get(i))); conflictingFiles.remove(i); @@ -424,7 +454,7 @@ class CopyNode { } } - /** The next 2 methods are a BFS that runs through one node at a time. */ + // The next 2 methods are a BFS that runs through one node at a time. private LinkedList queue = null; private Set visited = null; @@ -439,7 +469,9 @@ CopyNode startCopy() { } /** - * @return true if there are no more nodes + * Moves to the next unvisited node in tree. + * + * @return The next unvisited node if available, otherwise returns null. */ CopyNode goToNextNode() { if (queue.isEmpty()) return null; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 21f34a020a..27ecfc7187 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -814,5 +814,6 @@ You only need to do this once, until the next time you select a new location for Recent Results Please complete paste operation first + Destination and source folder shouldn\'t match to move. From aeb32863b5e06404e646ba7e0c8b7f2ca645c067 Mon Sep 17 00:00:00 2001 From: Yatik Date: Fri, 18 Aug 2023 00:24:44 +0530 Subject: [PATCH 5/9] Resolved duplicate subdirectory creation issue. --- .../asynchronous/asynctasks/movecopy/PrepareCopyTask.java | 3 +-- .../amaze/filemanager/filesystem/MakeDirectoryOperation.kt | 6 ++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java index 48c99176b0..c22ee3642f 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java @@ -435,8 +435,7 @@ class CopyNode { new ArrayList<>(Collections.singletonList(conflictingFiles.get(i)))); String newPath = path + "/" + newName; - HybridFile hybridFile = - new HybridFile(conflictingFiles.get(i).getMode(), newPath, newName, true); + HybridFile hybridFile = new HybridFile(conflictingFiles.get(i).getMode(), newPath); MakeDirectoryOperation.mkdirs(context.get(), hybridFile); nextNodes.add( new CopyNode(newPath, conflictingFiles.get(i).listFiles(context.get(), rootMode))); diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/MakeDirectoryOperation.kt b/app/src/main/java/com/amaze/filemanager/filesystem/MakeDirectoryOperation.kt index 5d44a4a697..1765130374 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/MakeDirectoryOperation.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/MakeDirectoryOperation.kt @@ -76,6 +76,12 @@ object MakeDirectoryOperation { } else false } + /** + * Creates the directories on given [file] path, including nonexistent parent directories. + * So use proper [HybridFile] constructor as per your need. + * + * @return true if successfully created directory, otherwise returns false. + */ @JvmStatic fun mkdirs(context: Context, file: HybridFile): Boolean { var isSuccessful = true From 47d9f473ac35b8fbcbb6d0543a8813ba85af26ba Mon Sep 17 00:00:00 2001 From: Yatik Date: Sun, 3 Sep 2023 20:15:28 +0530 Subject: [PATCH 6/9] New PreparePasteTask class as an alternate of PrepareCopyTask class --- .../asynctasks/movecopy/PreparePasteTask.kt | 479 ++++++++++++++++++ .../filemanager/filesystem/PasteHelper.java | 10 +- app/src/main/res/values/strings.xml | 1 + 3 files changed, 484 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt new file mode 100644 index 0000000000..8e0e8c33e8 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt @@ -0,0 +1,479 @@ +/* + * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.asynchronous.asynctasks.movecopy + +import android.app.ProgressDialog +import android.content.Intent +import android.view.LayoutInflater +import android.widget.Toast +import androidx.appcompat.widget.AppCompatCheckBox +import com.afollestad.materialdialogs.DialogAction +import com.afollestad.materialdialogs.MaterialDialog +import com.amaze.filemanager.R +import com.amaze.filemanager.asynchronous.asynctasks.fromTask +import com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil +import com.amaze.filemanager.asynchronous.services.CopyService +import com.amaze.filemanager.databinding.CopyDialogBinding +import com.amaze.filemanager.fileoperations.filesystem.CAN_CREATE_FILES +import com.amaze.filemanager.fileoperations.filesystem.COPY +import com.amaze.filemanager.fileoperations.filesystem.FolderState +import com.amaze.filemanager.fileoperations.filesystem.MOVE +import com.amaze.filemanager.fileoperations.filesystem.OpenMode +import com.amaze.filemanager.filesystem.DeleteOperation +import com.amaze.filemanager.filesystem.HybridFile +import com.amaze.filemanager.filesystem.HybridFileParcelable +import com.amaze.filemanager.filesystem.MakeDirectoryOperation +import com.amaze.filemanager.filesystem.files.FileUtils +import com.amaze.filemanager.ui.activities.MainActivity +import com.amaze.filemanager.utils.OnFileFound +import com.amaze.filemanager.utils.Utils +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File +import java.lang.ref.WeakReference +import java.util.LinkedList + +/** + * This helper class works by checking the conflicts during paste operation. After checking + * conflicts [MaterialDialog] is shown to user for each conflicting file. If the conflicting file + * is a directory, the conflicts are resolved by inserting a node in [CopyNode] tree and then doing + * BFS on this tree. + */ +class PreparePasteTask(strongRefMain: MainActivity) { + + private lateinit var targetPath: String + private var isMove = false + private var isRootMode = false + private lateinit var openMode: OpenMode + private lateinit var filesToCopy: MutableList + + private val pathsList = ArrayList() + private val filesToCopyPerFolder = ArrayList>() + + private val context = WeakReference(strongRefMain) + + @Suppress("DEPRECATION") + private var progressDialog: ProgressDialog? = null + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + + private lateinit var destination: HybridFile + private val conflictingFiles: MutableList = mutableListOf() + private val conflictingDirActionMap = HashMap() + + fun execute( + targetPath: String, + isMove: Boolean, + isRootMode: Boolean, + openMode: OpenMode, + filesToCopy: ArrayList + ) { + this.targetPath = targetPath + this.isMove = isMove + this.isRootMode = isRootMode + this.openMode = openMode + this.filesToCopy = filesToCopy + + if (openMode == OpenMode.OTG || + openMode == OpenMode.GDRIVE || + openMode == OpenMode.DROPBOX || + openMode == OpenMode.BOX || + openMode == OpenMode.ONEDRIVE || + openMode == OpenMode.ROOT + ) { + startService(filesToCopy, targetPath, openMode, isMove, isRootMode) + } + + val totalBytes = FileUtils.getTotalBytes(filesToCopy, context.get()) + destination = HybridFile(openMode, targetPath) + destination.generateMode(context.get()) + + if (filesToCopy.isNotEmpty() && + isMove && + filesToCopy[0].getParent(context.get()) == targetPath + ) { + Toast.makeText(context.get(), R.string.same_dir_move_error, Toast.LENGTH_SHORT).show() + return + } + + if (destination.usableSpace < totalBytes && + !( + isMove && + destination.mode == openMode && + MoveFiles.getOperationSupportedFileSystem().contains(openMode) + ) + ) { + Toast.makeText(context.get(), R.string.in_safe, Toast.LENGTH_SHORT).show() + return + } + @Suppress("DEPRECATION") + progressDialog = ProgressDialog.show( + context.get(), + "", + context.get()?.getString(R.string.checking_conflicts) + ) + checkConflicts(isRootMode, filesToCopy) + } + + private fun checkConflicts(isRootMode: Boolean, filesToCopy: ArrayList) { + coroutineScope.launch { + destination.forEachChildrenFile( + context.get(), + isRootMode, + object : OnFileFound { + override fun onFileFound(file: HybridFileParcelable) { + for (fileToCopy in filesToCopy) { + if (file.getName(context.get()) == fileToCopy.getName(context.get())) { + conflictingFiles.add(fileToCopy) + } + } + } + } + ) + withContext(Dispatchers.Main) { + showDialog() + @Suppress("DEPRECATION") + progressDialog?.setMessage(context.get()?.getString(R.string.copying)) + } + resolveConflict() + } + } + + private fun startService( + sourceFiles: ArrayList, + target: String, + openMode: OpenMode, + isMove: Boolean, + isRootMode: Boolean + ) { + val intent = Intent(context.get(), CopyService::class.java) + intent.putParcelableArrayListExtra(CopyService.TAG_COPY_SOURCES, sourceFiles) + intent.putExtra(CopyService.TAG_COPY_TARGET, target) + intent.putExtra(CopyService.TAG_COPY_OPEN_MODE, openMode.ordinal) + intent.putExtra(CopyService.TAG_COPY_MOVE, isMove) + intent.putExtra(CopyService.TAG_IS_ROOT_EXPLORER, isRootMode) + ServiceWatcherUtil.runService(context.get(), intent) + } + + private suspend fun showDialog() { + if (conflictingFiles.isEmpty()) return + + val contextRef = context.get() ?: return + + val accentColor = contextRef.accent + val dialogBuilder = MaterialDialog.Builder(contextRef) + val copyDialogBinding: CopyDialogBinding = + CopyDialogBinding.inflate(LayoutInflater.from(contextRef)) + dialogBuilder.customView(copyDialogBinding.root, true) + val checkBox: AppCompatCheckBox = copyDialogBinding.checkBox + + Utils.setTint(contextRef, checkBox, accentColor) + dialogBuilder.theme(contextRef.appTheme.getMaterialDialogTheme(contextRef)) + dialogBuilder.title(contextRef.resources.getString(R.string.paste)) + dialogBuilder.positiveText(R.string.rename) + dialogBuilder.neutralText(R.string.skip) + dialogBuilder.positiveColor(accentColor) + dialogBuilder.negativeColor(accentColor) + dialogBuilder.neutralColor(accentColor) + dialogBuilder.negativeText(R.string.overwrite) + + val iterator = conflictingFiles.iterator() + while (iterator.hasNext()) { + val hybridFileParcelable = iterator.next() + copyDialogBinding.fileNameText.text = hybridFileParcelable.name + + val dialog = dialogBuilder.build() + val resultDeferred = CompletableDeferred() + + dialogBuilder.onPositive { _, _ -> + resultDeferred.complete(DialogAction.POSITIVE) + } + + dialogBuilder.onNegative { _, _ -> + resultDeferred.complete(DialogAction.NEGATIVE) + } + + dialogBuilder.onNeutral { _, _ -> + resultDeferred.complete(DialogAction.NEUTRAL) + } + + dialog.show() + + when (resultDeferred.await()) { + DialogAction.POSITIVE -> { + if (checkBox.isChecked) { + Action.renameAll = true + return + } else conflictingDirActionMap[hybridFileParcelable] = Action.RENAME + } + + DialogAction.NEGATIVE -> { + if (hybridFileParcelable.getParent(contextRef) == targetPath) { + Toast.makeText( + contextRef, + R.string.same_dir_move_error, + Toast.LENGTH_SHORT + ).show() + if (checkBox.isChecked) { + filesToCopy.removeAll(conflictingFiles) + conflictingFiles.clear() + return + } + filesToCopy.remove(hybridFileParcelable) + iterator.remove() + } else if (checkBox.isChecked) { + Action.overwriteAll = true + return + } else { + conflictingDirActionMap[hybridFileParcelable] = Action.OVERWRITE + } + } + + DialogAction.NEUTRAL -> { + if (checkBox.isChecked) { + Action.skipAll = true + return + } else conflictingDirActionMap[hybridFileParcelable] = Action.SKIP + } + } + } + } + + private fun resolveConflict() = coroutineScope.launch { + var index = conflictingFiles.size - 1 + if (Action.renameAll) { + while (conflictingFiles.isNotEmpty()) { + conflictingDirActionMap[conflictingFiles[index]] = Action.RENAME + conflictingFiles.removeAt(index) + index-- + } + } else if (Action.overwriteAll) { + while (conflictingFiles.isNotEmpty()) { + conflictingDirActionMap[conflictingFiles[index]] = Action.OVERWRITE + conflictingFiles.removeAt(index) + index-- + } + } else { + while (conflictingFiles.isNotEmpty()) { + filesToCopy.remove(conflictingFiles.removeAt(index)) + index-- + } + } + + val rootNode = CopyNode(targetPath, ArrayList(filesToCopy)) + var currentNode: CopyNode? = rootNode.startCopy() + + while (currentNode != null) { + pathsList.add(currentNode.path) + filesToCopyPerFolder.add(currentNode.filesToCopy) + currentNode = rootNode.goToNextNode() + } + finishCopying() + } + + private suspend fun finishCopying() { + var index = 0 + while (index < filesToCopyPerFolder.size) { + if (filesToCopyPerFolder[index].size == 0) { + filesToCopyPerFolder.removeAt(index) + pathsList.removeAt(index) + index-- + } + index++ + } + if (filesToCopyPerFolder.isNotEmpty()) { + @FolderState + val mode: Int = context.get()?.mainActivityHelper!! + .checkFolder(targetPath, openMode, context.get()) + if (mode == CAN_CREATE_FILES && !targetPath.contains("otg:/")) { + // This is used because in newer devices the user has to accept a permission, + // see MainActivity.onActivityResult() + context.get()?.oparrayListList = filesToCopyPerFolder + context.get()?.oparrayList = null + context.get()?.operation = if (isMove) MOVE else COPY + context.get()?.oppatheList = pathsList + } else { + if (!isMove) { + for (foldersIndex in filesToCopyPerFolder.indices) + startService( + filesToCopyPerFolder[foldersIndex], + pathsList[foldersIndex], + openMode, + isMove, + isRootMode + ) + } else { + fromTask( + MoveFilesTask( + filesToCopyPerFolder, + isRootMode, + targetPath, + context.get()!!, + openMode, + pathsList + ) + ) + } + } + } else { + withContext(Dispatchers.Main) { + Toast.makeText( + context.get(), + context.get()!!.resources.getString(R.string.no_file_overwrite), + Toast.LENGTH_SHORT + ).show() + } + } + withContext(Dispatchers.Main) { + progressDialog?.dismiss() + } + coroutineScope.cancel() + } + + inner class CopyNode( + val path: String, + val filesToCopy: ArrayList + ) { + private val nextNodes: MutableList = mutableListOf() + private var queue: LinkedList? = null + private var visited: HashSet? = null + + init { + val iterator = filesToCopy.iterator() + while (iterator.hasNext()) { + val hybridFileParcelable = iterator.next() + if (conflictingDirActionMap.contains(hybridFileParcelable)) { + when (conflictingDirActionMap[hybridFileParcelable]) { + Action.RENAME -> { + if (hybridFileParcelable.isDirectory) { + val newName = resolveByRenaming(hybridFileParcelable) + val newPath = "$path/$newName" + val hybridFile = HybridFile(hybridFileParcelable.mode, newPath) + MakeDirectoryOperation.mkdirs(context.get()!!, hybridFile) + @Suppress("DEPRECATION") + nextNodes.add( + CopyNode( + newPath, + hybridFileParcelable.listFiles(context.get(), isRootMode) + ) + ) + iterator.remove() + } else { + filesToCopy[filesToCopy.indexOf(hybridFileParcelable)].name = + resolveByRenaming(hybridFileParcelable) + } + } + + Action.OVERWRITE -> { + DeleteOperation.deleteFile( + File("$path/${hybridFileParcelable.name}"), + context.get()!! + ) + } + + else -> iterator.remove() + } + } + } + } + + fun startCopy(): CopyNode { + queue = LinkedList() + visited = HashSet() + queue!!.add(this) + visited!!.add(this) + return this + } + + /** + * Moves to the next unvisited node in tree. + * + * @return The next unvisited node if available, otherwise returns null. + */ + fun goToNextNode(): CopyNode? = + if (queue.isNullOrEmpty()) null + else { + val node = queue!!.element() + val child = getUnvisitedChildNode(visited!!, node) + if (child != null) { + visited!!.add(child) + queue!!.add(child) + child + } else { + queue!!.remove() + goToNextNode() + } + } + + private fun getUnvisitedChildNode( + visited: Set, + node: CopyNode + ): CopyNode? { + for (currentNode in node.nextNodes) { + if (!visited.contains(currentNode)) { + return currentNode + } + } + return null + } + } + + private class Action { + companion object { + const val SKIP = "skip" + const val RENAME = "rename" + const val OVERWRITE = "overwrite" + var skipAll = false + var renameAll = false + var overwriteAll = false + } + } + + /** This is temporary function and should be replaced with + * [FilenameHelper](https://github.com/TeamAmaze/AmazeFileManager/pull/3913) before merging. + * */ + private fun resolveByRenaming(originalFile: HybridFileParcelable): String { + var appendInt = 1 + var newName: String + var targetFile: File + val oldName = originalFile.name + do { + newName = if (originalFile.isDirectory) { + "$oldName($appendInt)" + } else { + ( + oldName.substring(0, oldName.lastIndexOf(".")) + + "(" + + appendInt + + ")" + + oldName.substring(oldName.lastIndexOf(".")) + ) + } + appendInt++ + targetFile = File(targetPath, newName) + } while (targetFile.exists()) + return newName + } +} diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/PasteHelper.java b/app/src/main/java/com/amaze/filemanager/filesystem/PasteHelper.java index ac83ec6726..2113c1c220 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/PasteHelper.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/PasteHelper.java @@ -29,14 +29,13 @@ import org.slf4j.LoggerFactory; import com.amaze.filemanager.R; -import com.amaze.filemanager.asynchronous.asynctasks.movecopy.PrepareCopyTask; +import com.amaze.filemanager.asynchronous.asynctasks.movecopy.PreparePasteTask; import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.ui.fragments.MainFragment; import com.amaze.filemanager.utils.Utils; import com.google.android.material.snackbar.BaseTransientBottomBar; import com.google.android.material.snackbar.Snackbar; -import android.os.AsyncTask; import android.os.Parcel; import android.os.Parcelable; import android.text.Spanned; @@ -168,14 +167,13 @@ public void onSuccess(Spanned spanned) { ArrayList arrayList = new ArrayList<>(Arrays.asList(paths)); boolean move = operation == PasteHelper.OPERATION_CUT; - new PrepareCopyTask( + new PreparePasteTask(mainActivity) + .execute( path, move, - mainActivity, mainActivity.isRootExplorer(), mainFragment.getMainFragmentViewModel().getOpenMode(), - arrayList) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + arrayList); dismissSnackbar(true); }, () -> dismissSnackbar(true)); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 27ecfc7187..c020a686e7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -815,5 +815,6 @@ You only need to do this once, until the next time you select a new location for Results Please complete paste operation first Destination and source folder shouldn\'t match to move. + Checking for conflicts From 7b101b8a9f718434691d280e9edd8b98525d2d55 Mon Sep 17 00:00:00 2001 From: Yatik Date: Thu, 7 Sep 2023 18:41:37 +0530 Subject: [PATCH 7/9] Fixed issue where renaming was not working, removed PrepareCopyTask.java, renaming will be done using FilenameHelper --- .../asynctasks/movecopy/MoveFiles.java | 2 +- .../asynctasks/movecopy/PrepareCopyTask.java | 509 ------------------ .../asynctasks/movecopy/PreparePasteTask.kt | 128 ++--- .../ui/dialogs/DragAndDropDialog.kt | 22 +- 4 files changed, 63 insertions(+), 598 deletions(-) delete mode 100644 app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/MoveFiles.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/MoveFiles.java index a76c2b3a28..cfcabd66b1 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/MoveFiles.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/MoveFiles.java @@ -47,7 +47,7 @@ /** * AsyncTask that moves files from source to destination by trying to rename files first, if they're * in the same filesystem, else starting the copy service. Be advised - do not start this AsyncTask - * directly but use {@link PrepareCopyTask} instead + * directly but use {@link PreparePasteTask} instead */ public class MoveFiles implements Callable { diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java deleted file mode 100644 index c22ee3642f..0000000000 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java +++ /dev/null @@ -1,509 +0,0 @@ -/* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.amaze.filemanager.asynchronous.asynctasks.movecopy; - -import static com.amaze.filemanager.fileoperations.filesystem.FolderStateKt.CAN_CREATE_FILES; -import static com.amaze.filemanager.fileoperations.filesystem.OperationTypeKt.COPY; -import static com.amaze.filemanager.fileoperations.filesystem.OperationTypeKt.MOVE; - -import java.io.File; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Set; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.amaze.filemanager.R; -import com.amaze.filemanager.asynchronous.asynctasks.TaskKt; -import com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil; -import com.amaze.filemanager.asynchronous.services.CopyService; -import com.amaze.filemanager.databinding.CopyDialogBinding; -import com.amaze.filemanager.fileoperations.filesystem.FolderState; -import com.amaze.filemanager.fileoperations.filesystem.OpenMode; -import com.amaze.filemanager.filesystem.HybridFile; -import com.amaze.filemanager.filesystem.HybridFileParcelable; -import com.amaze.filemanager.filesystem.MakeDirectoryOperation; -import com.amaze.filemanager.filesystem.files.FileUtils; -import com.amaze.filemanager.ui.activities.MainActivity; -import com.amaze.filemanager.utils.Utils; - -import android.app.ProgressDialog; -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.view.LayoutInflater; -import android.widget.Toast; - -import androidx.annotation.IntDef; -import androidx.appcompat.widget.AppCompatCheckBox; - -/** - * This AsyncTask works by creating a tree where each folder that can be fusioned together with - * another in the destination is a node (CopyNode). While the tree is being created an indeterminate - * ProgressDialog is shown. Each node is copied when the conflicts are dealt with (the dialog is - * shown, and the tree is walked via a BFS). If the process is cancelled (via the button in the - * dialog) the dialog closes without any more code to be executed, finishCopying() is never executed - * so no changes are made. - */ -public class PrepareCopyTask extends AsyncTask { - - private final String path; - private final Boolean move; - private final WeakReference mainActivity; - private final WeakReference context; - private int counter = 0; - private ProgressDialog dialog; - private boolean rootMode = false; - private OpenMode openMode = OpenMode.FILE; - private @DialogState int dialogState = UNKNOWN; - private boolean isRenameMoveSupport = false; - - // causes folder containing filesToCopy to be deleted - private ArrayList deleteCopiedFolder = null; - private CopyNode copyFolder; - private final ArrayList paths = new ArrayList<>(); - private final ArrayList> filesToCopyPerFolder = new ArrayList<>(); - private final ArrayList filesToCopy; // a copy of params sent to this - - private static final int UNKNOWN = -1; - private static final int DO_NOT_REPLACE = 0; - private static final int REPLACE = 1; - private boolean doRenaming = false; - - @IntDef({UNKNOWN, DO_NOT_REPLACE, REPLACE}) - @interface DialogState {} - - public PrepareCopyTask( - String path, - Boolean move, - MainActivity con, - boolean rootMode, - OpenMode openMode, - ArrayList filesToCopy) { - this.move = move; - mainActivity = new WeakReference<>(con); - context = new WeakReference<>(con); - this.openMode = openMode; - this.rootMode = rootMode; - this.path = path; - this.filesToCopy = filesToCopy; - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - dialog = - ProgressDialog.show(context.get(), "", context.get().getString(R.string.processing), true); - } - - @Override - public void onProgressUpdate(String... message) { - Toast.makeText(context.get(), message[0], Toast.LENGTH_LONG).show(); - } - - @Override - protected CopyNode doInBackground(Void... params) { - long totalBytes = 0; - - if (openMode == OpenMode.OTG - || openMode == OpenMode.DROPBOX - || openMode == OpenMode.BOX - || openMode == OpenMode.GDRIVE - || openMode == OpenMode.ONEDRIVE - || openMode == OpenMode.ROOT) { - // no helper method for OTG to determine storage space - return null; - } - - HybridFile destination = new HybridFile(openMode, path); - destination.generateMode(context.get()); - - if (move - && destination.getMode() == openMode - && MoveFiles.getOperationSupportedFileSystem().contains(openMode)) { - // move/rename supported filesystems, skip checking for space - isRenameMoveSupport = true; - } - - totalBytes = FileUtils.getTotalBytes(filesToCopy, context.get()); - - if (destination.getUsableSpace() < totalBytes && !isRenameMoveSupport) { - publishProgress(context.get().getResources().getString(R.string.in_safe)); - return null; - } - - copyFolder = new CopyNode(path, filesToCopy); - - return copyFolder; - } - - private ArrayList checkConflicts( - final ArrayList filesToCopy, HybridFile destination) { - final ArrayList conflictingFiles = new ArrayList<>(); - destination.forEachChildrenFile( - context.get(), - rootMode, - file -> { - for (HybridFileParcelable j : filesToCopy) { - if (file.getName(context.get()).equals((j).getName(context.get()))) { - conflictingFiles.add(j); - } - } - }); - return conflictingFiles; - } - - @Override - protected void onPostExecute(CopyNode copyFolder) { - super.onPostExecute(copyFolder); - if (openMode == OpenMode.OTG - || openMode == OpenMode.GDRIVE - || openMode == OpenMode.DROPBOX - || openMode == OpenMode.BOX - || openMode == OpenMode.ONEDRIVE - || openMode == OpenMode.ROOT) { - - startService(filesToCopy, path, openMode); - } else { - - if (copyFolder == null) { - // not starting service as there's no sufficient space - dialog.dismiss(); - return; - } - - onEndDialog(null, null, null); - } - - dialog.dismiss(); - } - - private void startService( - ArrayList sourceFiles, String target, OpenMode openmode) { - Intent intent = new Intent(context.get(), CopyService.class); - intent.putParcelableArrayListExtra(CopyService.TAG_COPY_SOURCES, sourceFiles); - intent.putExtra(CopyService.TAG_COPY_TARGET, target); - intent.putExtra(CopyService.TAG_COPY_OPEN_MODE, openmode.ordinal()); - intent.putExtra(CopyService.TAG_COPY_MOVE, move); - intent.putExtra(CopyService.TAG_IS_ROOT_EXPLORER, rootMode); - ServiceWatcherUtil.runService(context.get(), intent); - } - - private void showDialog( - final String path, - final ArrayList filesToCopy, - final ArrayList conflictingFiles) { - int accentColor = mainActivity.get().getAccent(); - final MaterialDialog.Builder dialogBuilder = new MaterialDialog.Builder(context.get()); - CopyDialogBinding copyDialogBinding = - CopyDialogBinding.inflate(LayoutInflater.from(mainActivity.get())); - dialogBuilder.customView(copyDialogBinding.getRoot(), true); - - // textView - copyDialogBinding.fileNameText.setText(conflictingFiles.get(counter).getName(context.get())); - - // checkBox - final AppCompatCheckBox checkBox = copyDialogBinding.checkBox; - Utils.setTint(context.get(), checkBox, accentColor); - dialogBuilder.theme(mainActivity.get().getAppTheme().getMaterialDialogTheme(context.get())); - dialogBuilder.title(context.get().getResources().getString(R.string.paste)); - dialogBuilder.positiveText(R.string.skip); - if (filesToCopy.get(0).getParent(context.get()).equals(path)) { - doRenaming = true; - dialogBuilder.negativeText( - context.get().getString(R.string.rename) - + " & " - + context.get().getString(R.string.save)); - } else dialogBuilder.negativeText(R.string.overwrite); - dialogBuilder.neutralText(R.string.cancel); - dialogBuilder.positiveColor(accentColor); - dialogBuilder.negativeColor(accentColor); - dialogBuilder.neutralColor(accentColor); - dialogBuilder.onPositive( - (dialog, which) -> { - if (checkBox.isChecked()) dialogState = DO_NOT_REPLACE; - doNotReplaceFiles(path, filesToCopy, conflictingFiles); - }); - dialogBuilder.onNegative( - (dialog, which) -> { - if (checkBox.isChecked()) dialogState = REPLACE; - replaceFiles(path, filesToCopy, conflictingFiles); - }); - - final MaterialDialog dialog = dialogBuilder.build(); - dialog.show(); - } - - private void onEndDialog( - String path, - ArrayList filesToCopy, - ArrayList conflictingFiles) { - if (conflictingFiles != null - && counter != conflictingFiles.size() - && conflictingFiles.size() > 0) { - if (dialogState == UNKNOWN) { - showDialog(path, filesToCopy, conflictingFiles); - } else if (dialogState == DO_NOT_REPLACE) { - doNotReplaceFiles(path, filesToCopy, conflictingFiles); - } else if (dialogState == REPLACE) { - replaceFiles(path, filesToCopy, conflictingFiles); - } - } else { - CopyNode c = !copyFolder.hasStarted() ? copyFolder.startCopy() : copyFolder.goToNextNode(); - - if (c != null) { - counter = 0; - - paths.add(c.getPath()); - filesToCopyPerFolder.add(c.filesToCopy); - - if (dialogState == UNKNOWN) { - onEndDialog(c.path, c.filesToCopy, c.conflictingFiles); - } else if (dialogState == DO_NOT_REPLACE) { - doNotReplaceFiles(c.path, c.filesToCopy, c.conflictingFiles); - } else if (dialogState == REPLACE) { - replaceFiles(c.path, c.filesToCopy, c.conflictingFiles); - } - } else { - finishCopying(paths, filesToCopyPerFolder); - } - } - } - - private void doNotReplaceFiles( - String path, - ArrayList filesToCopy, - ArrayList conflictingFiles) { - if (counter < conflictingFiles.size()) { - if (dialogState != UNKNOWN) { - filesToCopy.remove(conflictingFiles.get(counter)); - counter++; - } else { - for (int j = counter; j < conflictingFiles.size(); j++) { - filesToCopy.remove(conflictingFiles.get(j)); - } - counter = conflictingFiles.size(); - } - } - - onEndDialog(path, filesToCopy, conflictingFiles); - } - - private void replaceFiles( - String path, - ArrayList filesToCopy, - ArrayList conflictingFiles) { - - if (doRenaming && dialogState != UNKNOWN) { - while (!conflictingFiles.isEmpty()) { - resolveByRenaming(filesToCopy, conflictingFiles); - } - } else if (doRenaming) { - resolveByRenaming(filesToCopy, conflictingFiles); - doRenaming = false; - } else if (counter < conflictingFiles.size()) { - if (dialogState != UNKNOWN) { - counter++; - } else { - counter = conflictingFiles.size(); - } - } - - onEndDialog(path, filesToCopy, conflictingFiles); - } - - private String resolveByRenaming( - ArrayList filesToCopy, - ArrayList conflictingFiles) { - int appendInt = 1; - String newName; - File targetFile; - - int conflictingFileIndex = filesToCopy.indexOf(conflictingFiles.get(0)); - HybridFileParcelable originalFile = filesToCopy.get(conflictingFileIndex); - String oldName = originalFile.getName(); - do { - if (originalFile.isDirectory()) { - newName = oldName + "(" + appendInt + ")"; - } else { - newName = - oldName.substring(0, oldName.lastIndexOf(".")) - + "(" - + appendInt - + ")" - + oldName.substring(oldName.lastIndexOf(".")); - } - appendInt++; - targetFile = new File(path, newName); - } while (targetFile.exists()); - filesToCopy.get(conflictingFileIndex).setName(newName); - conflictingFiles.remove(0); - return newName; - } - - private void finishCopying( - ArrayList paths, ArrayList> filesToCopyPerFolder) { - for (int i = 0; i < filesToCopyPerFolder.size(); i++) { - if (filesToCopyPerFolder.get(i) == null || filesToCopyPerFolder.get(i).size() == 0) { - filesToCopyPerFolder.remove(i); - paths.remove(i); - i--; - } - } - - if (filesToCopyPerFolder.size() != 0) { - @FolderState - int mode = mainActivity.get().mainActivityHelper.checkFolder(path, openMode, context.get()); - if (mode == CAN_CREATE_FILES && !path.contains("otg:/")) { - // This is used because in newer devices the user has to accept a permission, - // see MainActivity.onActivityResult() - mainActivity.get().oparrayListList = filesToCopyPerFolder; - mainActivity.get().oparrayList = null; - mainActivity.get().operation = move ? MOVE : COPY; - mainActivity.get().oppatheList = paths; - } else { - if (!move) { - for (int i = 0; i < filesToCopyPerFolder.size(); i++) { - startService(filesToCopyPerFolder.get(i), paths.get(i), openMode); - } - } else { - TaskKt.fromTask( - new MoveFilesTask( - filesToCopyPerFolder, rootMode, path, context.get(), openMode, paths)); - } - } - } else { - Toast.makeText( - context.get(), - context.get().getResources().getString(R.string.no_file_overwrite), - Toast.LENGTH_SHORT) - .show(); - } - } - - class CopyNode { - private final String path; - private final ArrayList filesToCopy; - private final ArrayList conflictingFiles; - private final ArrayList nextNodes = new ArrayList<>(); - - CopyNode(String p, ArrayList filesToCopy) { - path = p; - this.filesToCopy = filesToCopy; - - HybridFile destination = new HybridFile(openMode, path); - conflictingFiles = checkConflicts(filesToCopy, destination); - - for (int i = 0; i < conflictingFiles.size(); i++) { - String currentPath = path + "/" + conflictingFiles.get(i).getName(); - - // If move operation is in same directory, do nothing. - if (conflictingFiles.get(i).getPath().equals(currentPath) && move) { - filesToCopy.clear(); - conflictingFiles.clear(); - publishProgress(context.get().getString(R.string.same_dir_move_error)); - break; - } - if (conflictingFiles.get(i).isDirectory()) { - if (deleteCopiedFolder == null) deleteCopiedFolder = new ArrayList<>(); - deleteCopiedFolder.add(new File(conflictingFiles.get(i).getPath())); - - if (conflictingFiles.get(i).getPath().equals(currentPath)) { - String newName = - resolveByRenaming( - new ArrayList<>(Collections.singletonList(conflictingFiles.get(i))), - new ArrayList<>(Collections.singletonList(conflictingFiles.get(i)))); - String newPath = path + "/" + newName; - - HybridFile hybridFile = new HybridFile(conflictingFiles.get(i).getMode(), newPath); - MakeDirectoryOperation.mkdirs(context.get(), hybridFile); - nextNodes.add( - new CopyNode(newPath, conflictingFiles.get(i).listFiles(context.get(), rootMode))); - } else { - nextNodes.add( - new CopyNode( - path + "/" + conflictingFiles.get(i).getName(context.get()), - conflictingFiles.get(i).listFiles(context.get(), rootMode))); - } - - filesToCopy.remove(filesToCopy.indexOf(conflictingFiles.get(i))); - conflictingFiles.remove(i); - i--; - } - } - } - - // The next 2 methods are a BFS that runs through one node at a time. - private LinkedList queue = null; - - private Set visited = null; - - CopyNode startCopy() { - queue = new LinkedList<>(); - visited = new HashSet<>(); - - queue.add(this); - visited.add(this); - return this; - } - - /** - * Moves to the next unvisited node in tree. - * - * @return The next unvisited node if available, otherwise returns null. - */ - CopyNode goToNextNode() { - if (queue.isEmpty()) return null; - else { - CopyNode node = queue.element(); - CopyNode child; - if ((child = getUnvisitedChildNode(visited, node)) != null) { - visited.add(child); - queue.add(child); - return child; - } else { - queue.remove(); - return goToNextNode(); - } - } - } - - boolean hasStarted() { - return queue != null; - } - - String getPath() { - return path; - } - - private CopyNode getUnvisitedChildNode(Set visited, CopyNode node) { - for (CopyNode n : node.nextNodes) { - if (!visited.contains(n)) { - return n; - } - } - - return null; - } - } -} diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt index 8e0e8c33e8..8acce4abfb 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt @@ -37,7 +37,7 @@ import com.amaze.filemanager.fileoperations.filesystem.COPY import com.amaze.filemanager.fileoperations.filesystem.FolderState import com.amaze.filemanager.fileoperations.filesystem.MOVE import com.amaze.filemanager.fileoperations.filesystem.OpenMode -import com.amaze.filemanager.filesystem.DeleteOperation +import com.amaze.filemanager.filesystem.FilenameHelper import com.amaze.filemanager.filesystem.HybridFile import com.amaze.filemanager.filesystem.HybridFileParcelable import com.amaze.filemanager.filesystem.MakeDirectoryOperation @@ -48,11 +48,10 @@ import com.amaze.filemanager.utils.Utils import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.Job import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import java.io.File import java.lang.ref.WeakReference import java.util.LinkedList @@ -77,12 +76,35 @@ class PreparePasteTask(strongRefMain: MainActivity) { @Suppress("DEPRECATION") private var progressDialog: ProgressDialog? = null - private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + private val coroutineScope = CoroutineScope(Job() + Dispatchers.Default) private lateinit var destination: HybridFile private val conflictingFiles: MutableList = mutableListOf() private val conflictingDirActionMap = HashMap() + private var skipAll = false + private var renameAll = false + private var overwriteAll = false + + private fun startService( + sourceFiles: ArrayList, + target: String, + openMode: OpenMode, + isMove: Boolean, + isRootMode: Boolean + ) { + val intent = Intent(context.get(), CopyService::class.java) + intent.putParcelableArrayListExtra(CopyService.TAG_COPY_SOURCES, sourceFiles) + intent.putExtra(CopyService.TAG_COPY_TARGET, target) + intent.putExtra(CopyService.TAG_COPY_OPEN_MODE, openMode.ordinal) + intent.putExtra(CopyService.TAG_COPY_MOVE, isMove) + intent.putExtra(CopyService.TAG_IS_ROOT_EXPLORER, isRootMode) + ServiceWatcherUtil.runService(context.get(), intent) + } + + /** + * Starts execution of [PreparePasteTask] class. + */ fun execute( targetPath: String, isMove: Boolean, @@ -118,12 +140,12 @@ class PreparePasteTask(strongRefMain: MainActivity) { return } + val isMoveSupported = isMove && + destination.mode == openMode && + MoveFiles.getOperationSupportedFileSystem().contains(openMode) + if (destination.usableSpace < totalBytes && - !( - isMove && - destination.mode == openMode && - MoveFiles.getOperationSupportedFileSystem().contains(openMode) - ) + !isMoveSupported ) { Toast.makeText(context.get(), R.string.in_safe, Toast.LENGTH_SHORT).show() return @@ -161,27 +183,10 @@ class PreparePasteTask(strongRefMain: MainActivity) { } } - private fun startService( - sourceFiles: ArrayList, - target: String, - openMode: OpenMode, - isMove: Boolean, - isRootMode: Boolean - ) { - val intent = Intent(context.get(), CopyService::class.java) - intent.putParcelableArrayListExtra(CopyService.TAG_COPY_SOURCES, sourceFiles) - intent.putExtra(CopyService.TAG_COPY_TARGET, target) - intent.putExtra(CopyService.TAG_COPY_OPEN_MODE, openMode.ordinal) - intent.putExtra(CopyService.TAG_COPY_MOVE, isMove) - intent.putExtra(CopyService.TAG_IS_ROOT_EXPLORER, isRootMode) - ServiceWatcherUtil.runService(context.get(), intent) - } - private suspend fun showDialog() { if (conflictingFiles.isEmpty()) return val contextRef = context.get() ?: return - val accentColor = contextRef.accent val dialogBuilder = MaterialDialog.Builder(contextRef) val copyDialogBinding: CopyDialogBinding = @@ -210,25 +215,21 @@ class PreparePasteTask(strongRefMain: MainActivity) { dialogBuilder.onPositive { _, _ -> resultDeferred.complete(DialogAction.POSITIVE) } - dialogBuilder.onNegative { _, _ -> resultDeferred.complete(DialogAction.NEGATIVE) } - dialogBuilder.onNeutral { _, _ -> resultDeferred.complete(DialogAction.NEUTRAL) } - dialog.show() when (resultDeferred.await()) { DialogAction.POSITIVE -> { if (checkBox.isChecked) { - Action.renameAll = true + renameAll = true return } else conflictingDirActionMap[hybridFileParcelable] = Action.RENAME } - DialogAction.NEGATIVE -> { if (hybridFileParcelable.getParent(contextRef) == targetPath) { Toast.makeText( @@ -244,16 +245,15 @@ class PreparePasteTask(strongRefMain: MainActivity) { filesToCopy.remove(hybridFileParcelable) iterator.remove() } else if (checkBox.isChecked) { - Action.overwriteAll = true + overwriteAll = true return } else { conflictingDirActionMap[hybridFileParcelable] = Action.OVERWRITE } } - DialogAction.NEUTRAL -> { if (checkBox.isChecked) { - Action.skipAll = true + skipAll = true return } else conflictingDirActionMap[hybridFileParcelable] = Action.SKIP } @@ -263,19 +263,19 @@ class PreparePasteTask(strongRefMain: MainActivity) { private fun resolveConflict() = coroutineScope.launch { var index = conflictingFiles.size - 1 - if (Action.renameAll) { + if (renameAll) { while (conflictingFiles.isNotEmpty()) { conflictingDirActionMap[conflictingFiles[index]] = Action.RENAME conflictingFiles.removeAt(index) index-- } - } else if (Action.overwriteAll) { + } else if (overwriteAll) { while (conflictingFiles.isNotEmpty()) { conflictingDirActionMap[conflictingFiles[index]] = Action.OVERWRITE conflictingFiles.removeAt(index) index-- } - } else { + } else if (skipAll) { while (conflictingFiles.isNotEmpty()) { filesToCopy.remove(conflictingFiles.removeAt(index)) index-- @@ -352,7 +352,7 @@ class PreparePasteTask(strongRefMain: MainActivity) { coroutineScope.cancel() } - inner class CopyNode( + private inner class CopyNode( val path: String, val filesToCopy: ArrayList ) { @@ -368,7 +368,10 @@ class PreparePasteTask(strongRefMain: MainActivity) { when (conflictingDirActionMap[hybridFileParcelable]) { Action.RENAME -> { if (hybridFileParcelable.isDirectory) { - val newName = resolveByRenaming(hybridFileParcelable) + val newName = + FilenameHelper.increment( + hybridFileParcelable + ).getName(context.get()) val newPath = "$path/$newName" val hybridFile = HybridFile(hybridFileParcelable.mode, newPath) MakeDirectoryOperation.mkdirs(context.get()!!, hybridFile) @@ -382,23 +385,23 @@ class PreparePasteTask(strongRefMain: MainActivity) { iterator.remove() } else { filesToCopy[filesToCopy.indexOf(hybridFileParcelable)].name = - resolveByRenaming(hybridFileParcelable) + FilenameHelper.increment( + hybridFileParcelable + ).getName(context.get()) } } - Action.OVERWRITE -> { - DeleteOperation.deleteFile( - File("$path/${hybridFileParcelable.name}"), - context.get()!! - ) - } - - else -> iterator.remove() + Action.SKIP -> iterator.remove() } } } } + /** + * Starts BFS traversal of tree. + * + * @return Root node + */ fun startCopy(): CopyNode { queue = LinkedList() visited = HashSet() @@ -445,35 +448,6 @@ class PreparePasteTask(strongRefMain: MainActivity) { const val SKIP = "skip" const val RENAME = "rename" const val OVERWRITE = "overwrite" - var skipAll = false - var renameAll = false - var overwriteAll = false } } - - /** This is temporary function and should be replaced with - * [FilenameHelper](https://github.com/TeamAmaze/AmazeFileManager/pull/3913) before merging. - * */ - private fun resolveByRenaming(originalFile: HybridFileParcelable): String { - var appendInt = 1 - var newName: String - var targetFile: File - val oldName = originalFile.name - do { - newName = if (originalFile.isDirectory) { - "$oldName($appendInt)" - } else { - ( - oldName.substring(0, oldName.lastIndexOf(".")) + - "(" + - appendInt + - ")" + - oldName.substring(oldName.lastIndexOf(".")) - ) - } - appendInt++ - targetFile = File(targetPath, newName) - } while (targetFile.exists()) - return newName - } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/DragAndDropDialog.kt b/app/src/main/java/com/amaze/filemanager/ui/dialogs/DragAndDropDialog.kt index 88693d6111..a9992f0b2a 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/DragAndDropDialog.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/DragAndDropDialog.kt @@ -22,7 +22,6 @@ package com.amaze.filemanager.ui.dialogs import android.app.Dialog import android.content.Context -import android.os.AsyncTask import android.os.Bundle import androidx.appcompat.widget.AppCompatButton import androidx.appcompat.widget.AppCompatCheckBox @@ -31,7 +30,7 @@ import com.afollestad.materialdialogs.DialogAction import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.Theme import com.amaze.filemanager.R -import com.amaze.filemanager.asynchronous.asynctasks.movecopy.PrepareCopyTask +import com.amaze.filemanager.asynchronous.asynctasks.movecopy.PreparePasteTask import com.amaze.filemanager.filesystem.HybridFileParcelable import com.amaze.filemanager.ui.activities.MainActivity import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants @@ -106,15 +105,16 @@ class DragAndDropDialog : DialogFragment() { move: Boolean, mainActivity: MainActivity ) { - PrepareCopyTask( - pasteLocation, - move, - mainActivity, - mainActivity.isRootExplorer, - mainActivity.currentMainFragment?.mainFragmentViewModel?.openMode, - files - ) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + val openMode = + mainActivity.currentMainFragment?.mainFragmentViewModel?.openMode ?: return + PreparePasteTask(mainActivity) + .execute( + pasteLocation, + move, + mainActivity.isRootExplorer, + openMode, + files + ) } } From 4cf2a2d11baf4f200ac739827e1aae947b8b6030 Mon Sep 17 00:00:00 2001 From: Yatik Date: Tue, 19 Sep 2023 10:59:50 +0530 Subject: [PATCH 8/9] Fixes static code analysis issues, minor bugs. --- .../asynctasks/movecopy/PreparePasteTask.kt | 73 ++++++++++++++----- app/src/main/res/values/strings.xml | 1 + 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt index 8acce4abfb..7ad02a13a7 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt @@ -118,13 +118,14 @@ class PreparePasteTask(strongRefMain: MainActivity) { this.openMode = openMode this.filesToCopy = filesToCopy - if (openMode == OpenMode.OTG || + val isCloudOrRootMode = openMode == OpenMode.OTG || openMode == OpenMode.GDRIVE || openMode == OpenMode.DROPBOX || openMode == OpenMode.BOX || openMode == OpenMode.ONEDRIVE || openMode == OpenMode.ROOT - ) { + + if (isCloudOrRootMode) { startService(filesToCopy, targetPath, openMode, isMove, isRootMode) } @@ -156,10 +157,22 @@ class PreparePasteTask(strongRefMain: MainActivity) { "", context.get()?.getString(R.string.checking_conflicts) ) - checkConflicts(isRootMode, filesToCopy) + checkConflicts( + isRootMode, + filesToCopy, + destination, + conflictingFiles, + conflictingDirActionMap + ) } - private fun checkConflicts(isRootMode: Boolean, filesToCopy: ArrayList) { + private fun checkConflicts( + isRootMode: Boolean, + filesToCopy: ArrayList, + destination: HybridFile, + conflictingFiles: MutableList, + conflictingDirActionMap: HashMap + ) { coroutineScope.launch { destination.forEachChildrenFile( context.get(), @@ -175,15 +188,19 @@ class PreparePasteTask(strongRefMain: MainActivity) { } ) withContext(Dispatchers.Main) { - showDialog() + prepareDialog(conflictingFiles, conflictingDirActionMap, filesToCopy) @Suppress("DEPRECATION") progressDialog?.setMessage(context.get()?.getString(R.string.copying)) } - resolveConflict() + resolveConflict(conflictingFiles, conflictingDirActionMap, filesToCopy) } } - private suspend fun showDialog() { + private suspend fun prepareDialog( + conflictingFiles: MutableList, + conflictingDirActionMap: HashMap, + filesToCopy: ArrayList + ) { if (conflictingFiles.isEmpty()) return val contextRef = context.get() ?: return @@ -203,15 +220,30 @@ class PreparePasteTask(strongRefMain: MainActivity) { dialogBuilder.negativeColor(accentColor) dialogBuilder.neutralColor(accentColor) dialogBuilder.negativeText(R.string.overwrite) + showDialog( + conflictingFiles, + filesToCopy, + conflictingDirActionMap, + copyDialogBinding, + dialogBuilder, + checkBox + ) + } + private suspend fun showDialog( + conflictingFiles: MutableList, + filesToCopy: ArrayList, + conflictingDirActionMap: HashMap, + copyDialogBinding: CopyDialogBinding, + dialogBuilder: MaterialDialog.Builder, + checkBox: AppCompatCheckBox + ) { val iterator = conflictingFiles.iterator() while (iterator.hasNext()) { val hybridFileParcelable = iterator.next() copyDialogBinding.fileNameText.text = hybridFileParcelable.name - val dialog = dialogBuilder.build() val resultDeferred = CompletableDeferred() - dialogBuilder.onPositive { _, _ -> resultDeferred.complete(DialogAction.POSITIVE) } @@ -222,46 +254,49 @@ class PreparePasteTask(strongRefMain: MainActivity) { resultDeferred.complete(DialogAction.NEUTRAL) } dialog.show() - when (resultDeferred.await()) { DialogAction.POSITIVE -> { if (checkBox.isChecked) { renameAll = true return } else conflictingDirActionMap[hybridFileParcelable] = Action.RENAME + iterator.remove() } DialogAction.NEGATIVE -> { - if (hybridFileParcelable.getParent(contextRef) == targetPath) { + if (hybridFileParcelable.getParent(context.get()) == targetPath) { Toast.makeText( - contextRef, - R.string.same_dir_move_error, + context.get(), + R.string.same_dir_overwrite_error, Toast.LENGTH_SHORT ).show() if (checkBox.isChecked) { - filesToCopy.removeAll(conflictingFiles) + filesToCopy.removeAll(conflictingFiles.toSet()) conflictingFiles.clear() return } filesToCopy.remove(hybridFileParcelable) - iterator.remove() } else if (checkBox.isChecked) { overwriteAll = true return - } else { - conflictingDirActionMap[hybridFileParcelable] = Action.OVERWRITE - } + } else conflictingDirActionMap[hybridFileParcelable] = Action.OVERWRITE + iterator.remove() } DialogAction.NEUTRAL -> { if (checkBox.isChecked) { skipAll = true return } else conflictingDirActionMap[hybridFileParcelable] = Action.SKIP + iterator.remove() } } } } - private fun resolveConflict() = coroutineScope.launch { + private fun resolveConflict( + conflictingFiles: MutableList, + conflictingDirActionMap: HashMap, + filesToCopy: ArrayList + ) = coroutineScope.launch { var index = conflictingFiles.size - 1 if (renameAll) { while (conflictingFiles.isNotEmpty()) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c020a686e7..2ea7e00c20 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -816,5 +816,6 @@ You only need to do this once, until the next time you select a new location for Please complete paste operation first Destination and source folder shouldn\'t match to move. Checking for conflicts + Destination and source folder shouldn\'t match to overwrite. From c3e69cfc37d2ac3de207abaa1dc24e1a77dd94ce Mon Sep 17 00:00:00 2001 From: Yatik Date: Tue, 19 Sep 2023 11:14:35 +0530 Subject: [PATCH 9/9] =?UTF-8?q?Fixes=20new=20static=20code=20analysis=20is?= =?UTF-8?q?sue=20=F0=9F=A5=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../asynchronous/asynctasks/movecopy/PreparePasteTask.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt index 7ad02a13a7..3d28c24f83 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PreparePasteTask.kt @@ -188,7 +188,7 @@ class PreparePasteTask(strongRefMain: MainActivity) { } ) withContext(Dispatchers.Main) { - prepareDialog(conflictingFiles, conflictingDirActionMap, filesToCopy) + prepareDialog(conflictingFiles, filesToCopy) @Suppress("DEPRECATION") progressDialog?.setMessage(context.get()?.getString(R.string.copying)) } @@ -198,7 +198,6 @@ class PreparePasteTask(strongRefMain: MainActivity) { private suspend fun prepareDialog( conflictingFiles: MutableList, - conflictingDirActionMap: HashMap, filesToCopy: ArrayList ) { if (conflictingFiles.isEmpty()) return @@ -223,7 +222,6 @@ class PreparePasteTask(strongRefMain: MainActivity) { showDialog( conflictingFiles, filesToCopy, - conflictingDirActionMap, copyDialogBinding, dialogBuilder, checkBox @@ -233,7 +231,6 @@ class PreparePasteTask(strongRefMain: MainActivity) { private suspend fun showDialog( conflictingFiles: MutableList, filesToCopy: ArrayList, - conflictingDirActionMap: HashMap, copyDialogBinding: CopyDialogBinding, dialogBuilder: MaterialDialog.Builder, checkBox: AppCompatCheckBox