package com.amaze.filemanager.services.asynctasks;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;
import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.amaze.filemanager.R;
import com.amaze.filemanager.activities.BaseActivity;
import com.amaze.filemanager.activities.MainActivity;
import com.amaze.filemanager.filesystem.BaseFile;
import com.amaze.filemanager.filesystem.HFile;
import com.amaze.filemanager.fragments.MainFragment;
import com.amaze.filemanager.services.CopyService;
import com.amaze.filemanager.utils.DataUtils;
import com.amaze.filemanager.utils.MainActivityHelper;
import com.amaze.filemanager.utils.OpenMode;
import com.amaze.filemanager.utils.ServiceWatcherUtil;
import com.amaze.filemanager.utils.Utils;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
/**
* Created by arpitkh996 on 12-01-2016, modified by Emmanuel Messulam<emmanuelbendavid@gmail.com>
*
* This AsyncTask works by creating a tree where each folder that can be fusioned together with
* another in the destination is a node (CopyNode), 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 CopyFileCheck extends AsyncTask<ArrayList<BaseFile>, String, CopyFileCheck.CopyNode> {
private enum DO_FOR_ALL_ELEMENTS {
DO_NOT_REPLACE,
REPLACE
}
private MainFragment mainFrag;
private String path;
private Boolean move;
private int counter = 0;
private MainActivity mainActivity;
private Context context;
private boolean rootMode = false;
private OpenMode openMode = OpenMode.FILE;
private DO_FOR_ALL_ELEMENTS dialogState = null;
//causes folder containing filesToCopy to be deleted
private ArrayList<File> deleteCopiedFolder = null;
private CopyNode copyFolder;
private final ArrayList<String> paths = new ArrayList<>();
private final ArrayList<ArrayList<BaseFile>> filesToCopyPerFolder = new ArrayList<>();
private ArrayList<BaseFile> filesToCopy; // a copy of params sent to this
public CopyFileCheck(MainFragment ma, String path, Boolean move, MainActivity con, boolean rootMode) {
mainFrag = ma;
this.move = move;
mainActivity = con;
context = con;
openMode = mainFrag.openMode;
this.rootMode = rootMode;
this.path = path;
}
@Override
public void onProgressUpdate(String... message) {
Toast.makeText(context, message[0], Toast.LENGTH_LONG).show();
}
@Override
protected CopyNode doInBackground(ArrayList<BaseFile>... params) {
filesToCopy = params[0];
long totalBytes = 0;
if (openMode == OpenMode.OTG ||
openMode == OpenMode.DROPBOX
|| openMode == OpenMode.BOX
|| openMode == OpenMode.GDRIVE
|| openMode == OpenMode.ONEDRIVE
) {
// no helper method for OTG to determine storage space
return null;
}
for (int i = 0; i < filesToCopy.size(); i++) {
BaseFile file = filesToCopy.get(i);
if (file.isDirectory()) {
totalBytes = totalBytes + file.folderSize();
} else {
totalBytes = totalBytes + file.length();
}
}
HFile destination = new HFile(openMode, path);
if (destination.getUsableSpace() < totalBytes) {
publishProgress(context.getResources().getString(R.string.in_safe));
return null;
}
copyFolder = new CopyNode(path, filesToCopy);
return copyFolder;
}
private ArrayList<BaseFile> checkConflicts(ArrayList<BaseFile> filesToCopy, HFile destination) {
ArrayList<BaseFile> conflictingFiles = new ArrayList<>();
for (BaseFile k1 : destination.listFiles(rootMode)) {
for (BaseFile j : filesToCopy) {
if (k1.getName().equals((j).getName())) {
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) {
startService(filesToCopy, path, openMode);
} else {
onEndDialog(null, null, null);
}
}
private void startService(ArrayList<BaseFile> sourceFiles, String target, OpenMode openmode) {
Intent intent = new Intent(context, 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);
ServiceWatcherUtil.runService(context, intent);
}
private void showDialog(final String path, final ArrayList<BaseFile> filesToCopy,
final ArrayList<BaseFile> conflictingFiles) {
final MaterialDialog.Builder dialogBuilder = new MaterialDialog.Builder(context);
LayoutInflater layoutInflater =
(LayoutInflater) mainActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = layoutInflater.inflate(R.layout.copy_dialog, null);
dialogBuilder.customView(view, true);
// textView
TextView textView = (TextView) view.findViewById(R.id.textView);
textView.setText(context.getResources().getString(R.string.fileexist) + "\n" + conflictingFiles.get(counter).getName());
// checkBox
final CheckBox checkBox = (CheckBox) view.findViewById(R.id.checkBox);
Utils.setTint(checkBox, Color.parseColor(BaseActivity.accentSkin));
dialogBuilder.theme(mainActivity.getAppTheme().getMaterialDialogTheme());
dialogBuilder.title(context.getResources().getString(R.string.paste));
dialogBuilder.positiveText(R.string.skip);
dialogBuilder.negativeText(R.string.overwrite);
dialogBuilder.neutralText(R.string.cancel);
dialogBuilder.positiveColor(Color.parseColor(BaseActivity.accentSkin));
dialogBuilder.negativeColor(Color.parseColor(BaseActivity.accentSkin));
dialogBuilder.neutralColor(Color.parseColor(BaseActivity.accentSkin));
dialogBuilder.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
if (checkBox.isChecked())
dialogState = DO_FOR_ALL_ELEMENTS.DO_NOT_REPLACE;
doNotReplaceFiles(path, filesToCopy, conflictingFiles);
}
});
dialogBuilder.onNegative(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
if (checkBox.isChecked())
dialogState = DO_FOR_ALL_ELEMENTS.REPLACE;
replaceFiles(path, filesToCopy, conflictingFiles);
}
});
final MaterialDialog dialog = dialogBuilder.build();
dialog.show();
if (filesToCopy.get(0).getParent().equals(path)) {
View negative = dialog.getActionButton(DialogAction.NEGATIVE);
negative.setEnabled(false);
}
}
private void onEndDialog(String path, ArrayList<BaseFile> filesToCopy,
ArrayList<BaseFile> conflictingFiles) {
if (conflictingFiles != null && counter != conflictingFiles.size() && conflictingFiles.size() > 0) {
if (dialogState == null)
showDialog(path, filesToCopy, conflictingFiles);
else if (dialogState == DO_FOR_ALL_ELEMENTS.DO_NOT_REPLACE)
doNotReplaceFiles(path, filesToCopy, conflictingFiles);
else if (dialogState == DO_FOR_ALL_ELEMENTS.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 == null)
onEndDialog(c.path, c.filesToCopy, c.conflictingFiles);
else if (dialogState == DO_FOR_ALL_ELEMENTS.DO_NOT_REPLACE)
doNotReplaceFiles(c.path, c.filesToCopy, c.conflictingFiles);
else if (dialogState == DO_FOR_ALL_ELEMENTS.REPLACE)
replaceFiles(c.path, c.filesToCopy, c.conflictingFiles);
} else {
finishCopying(paths, filesToCopyPerFolder);
}
}
}
private void doNotReplaceFiles(String path, ArrayList<BaseFile> filesToCopy, ArrayList<BaseFile> conflictingFiles) {
if (counter < conflictingFiles.size()) {
if (dialogState != null) {
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<BaseFile> filesToCopy, ArrayList<BaseFile> conflictingFiles) {
if (counter < conflictingFiles.size()) {
if (dialogState != null) {
counter++;
} else {
counter = conflictingFiles.size();
}
}
onEndDialog(path, filesToCopy, conflictingFiles);
}
private void finishCopying(ArrayList<String> paths, ArrayList<ArrayList<BaseFile>> 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) {
int mode = mainActivity.mainActivityHelper.checkFolder(new File(path), context);
if (mode == MainActivityHelper.CAN_CREATE_FILES && !path.contains("otg:/")) {
//This is used because in newer devices the user has to accept a permission,
// see MainActivity.onActivityResult()
mainActivity.oparrayListList = filesToCopyPerFolder;
mainActivity.oparrayList = null;
mainActivity.operation = move ? DataUtils.MOVE : DataUtils.COPY;
mainActivity.oppatheList = paths;
} else {
if (!move) {
for (int i = 0; i < filesToCopyPerFolder.size(); i++) {
startService(filesToCopyPerFolder.get(i), paths.get(i), openMode);
}
} else {
new MoveFiles(filesToCopyPerFolder, mainFrag, context, openMode)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, paths);
}
}
} else {
Toast.makeText(context, context.getResources().getString(R.string.no_file_overwrite), Toast.LENGTH_SHORT).show();
}
}
class CopyNode {
private String path;
private ArrayList<BaseFile> filesToCopy, conflictingFiles;
private ArrayList<CopyNode> nextNodes = new ArrayList<>();
CopyNode(String p, ArrayList<BaseFile> filesToCopy) {
path = p;
this.filesToCopy = filesToCopy;
HFile destination = new HFile(openMode, path);
conflictingFiles = checkConflicts(filesToCopy, destination);
for (int i = 0; i < conflictingFiles.size(); i++) {
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(),
conflictingFiles.get(i).listFiles(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<CopyNode> queue = null;
private Set<CopyNode> visited = null;
CopyNode startCopy() {
queue = new LinkedList<>();
visited = new HashSet<>();
queue.add(this);
visited.add(this);
return this;
}
/**
* @return true if there are no more nodes
*/
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;
}
ArrayList<BaseFile> getFilesToCopy() {
return filesToCopy;
}
ArrayList<BaseFile> getConflictingFiles() {
return conflictingFiles;
}
private CopyNode getUnvisitedChildNode(Set<CopyNode> visited, CopyNode node) {
for (CopyNode n : node.nextNodes) {
if (!visited.contains(n)) {
return n;
}
}
return null;
}
}
}