package com.openfarmanager.android.filesystem.actions.multi;
import android.content.Context;
import android.content.DialogInterface;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import com.openfarmanager.android.App;
import com.openfarmanager.android.BuildConfig;
import com.openfarmanager.android.R;
import com.openfarmanager.android.core.Settings;
import com.openfarmanager.android.core.bus.RxBus;
import com.openfarmanager.android.core.bus.TaskCancelledEvent;
import com.openfarmanager.android.core.bus.TaskErrorEvent;
import com.openfarmanager.android.core.bus.TaskOkEvent;
import com.openfarmanager.android.core.network.smb.SmbAPI;
import com.openfarmanager.android.dialogs.FileActionProgressDialog;
import com.openfarmanager.android.filesystem.FileProxy;
import com.openfarmanager.android.filesystem.actions.OnActionListener;
import com.openfarmanager.android.model.TaskStatusEnum;
import com.openfarmanager.android.utils.StorageUtils;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import jcifs.smb.SmbAuthException;
import static com.openfarmanager.android.utils.Extensions.*;
/**
* @author Vlad Namashko
*/
public abstract class MultiActionTask {
private static final int MSG_PROGRESS = 0;
private static final int MSG_POST_RESULT = 1;
private static final int MSG_SET_HEADER = 2;
private static InternalHandler sHandler;
protected List<File> mItems;
protected FileActionProgressDialog mProgressDialog;
protected String mCurrentFile;
// protected OnActionListener mListener;
protected long mTotalSize = 0;
protected long mDoneSize = 0;
protected String mSdCardPath;
protected Uri mBaseUri;
protected boolean mUseStorageApi;
protected boolean mIsCancelled;
/**
* Task start time in milliseconds. For debug purposes.
*/
private long mTaskStartTime;
private TaskResult mTaskResult;
private List<Future> mSubTasksAsynk = new LinkedList<>();
private Map<Future, String> mSubTasksAsynkLabels = new HashMap<>();
private StringBuilder mActiveSubTasksListBuilder = new StringBuilder();
private int mInvokedOnPanel;
private int mLabelType;
public MultiActionTask(final Context context, int invokedOnPanel, List<File> items) {
mItems = items;
mTaskResult = new TaskResult();
mTaskResult.task = this;
mTaskResult.invokedOnPanel = invokedOnPanel;
mInvokedOnPanel = invokedOnPanel;
mTaskResult.extra = getExtra();
mLabelType = App.sInstance.getSettings().getMultiActionLabelType();
init(context);
}
protected MultiActionTask() {
}
protected Object getExtra() {
return "";
}
protected void init(Context context) {
mProgressDialog = new FileActionProgressDialog(context, mProgressDialogDismissListener);
mProgressDialog.setIndeterminate(isIndeterminate());
mProgressDialog.show();
}
protected void calculateSize() {
for (File file : mItems) {
try {
mTotalSize += FileUtils.sizeOf(file);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void execute() {
if (BuildConfig.DEBUG) {
mTaskStartTime = System.currentTimeMillis();
}
runAsync(getActionRunnable());
}
public void runSubTaskAsynk(Callable subTask, File file) {
Future future = runAsync(subTask);
mSubTasksAsynk.add(future);
mSubTasksAsynkLabels.put(future, file.getName());
}
public void runSubTaskAsynk(Callable subTask, FileProxy file) {
Future future = runAsync(subTask);
mSubTasksAsynk.add(future);
mSubTasksAsynkLabels.put(future, file.getName());
}
protected String getProgressText() {
if (mLabelType == Settings.MULTI_ACTION_LABEL_TYPE_FILES_NUM) {
return getActiveSubTaskStatus();
} else {
return getActiveSubTaskFiles();
}
}
private String getActiveSubTaskFiles() {
mActiveSubTasksListBuilder.setLength(0);
for (String file : mSubTasksAsynkLabels.values()) {
mActiveSubTasksListBuilder.append(file).append(" ");
}
return mActiveSubTasksListBuilder.toString();
}
private String getActiveSubTaskStatus() {
return App.sInstance.getResources().getString(R.string.processing_files, mSubTasksAsynkLabels.size());
}
protected void setHeader(String header) {
mTaskResult.header = header;
getHandler().obtainMessage(MSG_SET_HEADER, mTaskResult).sendToTarget();
}
protected void publishProgress() {
mTaskResult.fileName = mCurrentFile == null ? "" : mCurrentFile;
getHandler().obtainMessage(MSG_PROGRESS, mTaskResult).sendToTarget();
}
protected void publishProgress(final int value) {
mTaskResult.fileName = mCurrentFile == null ? "" : mCurrentFile;
mTaskResult.progress = value;
getHandler().obtainMessage(MSG_PROGRESS, mTaskResult).sendToTarget();
}
protected void onTaskDone(final TaskStatusEnum status) {
if (BuildConfig.DEBUG) {
Log.d(getTag(), "execution time = " + (System.currentTimeMillis() - mTaskStartTime));
}
mTaskResult.status = status;
getHandler().obtainMessage(MSG_POST_RESULT, mTaskResult).sendToTarget();
}
/**
* Update progress on dialog.
*
* In of <code>mTotalSize</code> equals 0 most likely we have indeterminate and should update
* only title text on dialog (if text is not empty).
*/
protected void updateProgress() {
if (mTotalSize > 0) {
publishProgress((int) (100 * mDoneSize / mTotalSize));
} else if (!isNullOrEmpty(mCurrentFile)) {
publishProgress();
}
}
protected boolean isCancelled() {
return mIsCancelled;
}
protected boolean createDirectoryIfNotExists(String dir) {
File outputDir = new File(dir);
return outputDir.exists() || (mUseStorageApi ? StorageUtils.mkDir(mBaseUri, mSdCardPath, outputDir) : outputDir.mkdirs());
}
protected boolean createDirectoryIfNotExists(File outputDir) {
return outputDir.exists() || (mUseStorageApi ? StorageUtils.mkDir(mBaseUri, mSdCardPath, outputDir) : outputDir.mkdirs());
}
private DialogInterface.OnDismissListener mProgressDialogDismissListener = dialog -> {
cancel();
RxBus.getInstance().postEvent(new TaskCancelledEvent(mInvokedOnPanel));
};
public void cancel() {
mIsCancelled = true;
for (Future future : mSubTasksAsynk) {
if (!future.isDone()) {
future.cancel(true);
}
onSubTaskDone(future);
}
}
protected Runnable getActionRunnable() {
return mActionRunnable;
}
protected boolean hasSubTasks() {
return mSubTasksAsynk.size() > 0;
}
private Runnable mActionRunnable = () -> {
calculateSize();
TaskStatusEnum status = doAction();
if (hasSubTasks() && handleSubTasks(status)) {
return;
}
onTaskDone(status);
};
/**
* Process async sub tasks (if present): wait for execution and handle errors (if any).
*
* @param status result of common action execution.
* @return <code>true</code> if task was finished with error and doesn't need to be handled outside,<code>false</code> otherwise.
*/
protected boolean handleSubTasks(TaskStatusEnum status) {
if (status == TaskStatusEnum.OK) {
try {
for (Future future : mSubTasksAsynk) {
future.get();
onSubTaskDone(future);
}
} catch (ExecutionException e) {
handleExecutionException(e);
return true;
} catch (Exception e) {
onTaskDone(handleSubTaskException(e));
return true;
}
} else {
for (Future future : mSubTasksAsynk) {
future.cancel(true);
}
}
return false;
}
private void handleExecutionException(ExecutionException e) {
// attempt to extract execution exception
System.out.println("::::::::: " + e.getCause().toString());
String[] messages = e.getCause().toString().split(":");
try {
Class clazz = Class.forName(messages[0].trim());
// special case for access denied on LAN
onTaskDone(clazz.equals(SmbAuthException.class) && messages[1].trim().equals(SmbAPI.ACCESS_DENIED) ?
TaskStatusEnum.ERROR_ACCESS_DENIED : handleSubTaskException((Exception) clazz.newInstance()));
} catch (Exception ee) {
onTaskDone(handleSubTaskException(e));
}
}
private static InternalHandler getHandler() {
synchronized (MultiActionTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler();
}
return sHandler;
}
}
protected boolean isIndeterminate() {
return false;
}
protected String getTag() {
return "MultiActionTask";
}
public void onSubTaskDone(Future future) {
mSubTasksAsynkLabels.remove(future);
}
public abstract TaskStatusEnum doAction();
public abstract TaskStatusEnum handleSubTaskException(Exception e);
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
}
@Override
public void handleMessage(Message msg) {
TaskResult taskResult = (TaskResult) msg.obj;
switch (msg.what) {
case MSG_POST_RESULT:
try {
taskResult.task.mProgressDialog.dismiss();
} catch (Exception ignore) { }
// if (taskResult.task.mListener != null) {
// taskResult.task.mListener.onActionFinish(taskResult.status);
// }
TaskStatusEnum status = taskResult.status;
if (status == TaskStatusEnum.OK) {
RxBus.getInstance().postEvent(new TaskOkEvent(taskResult.invokedOnPanel));
} else {
RxBus.getInstance().postEvent(new TaskErrorEvent(taskResult.invokedOnPanel).setStatus(status).setExtra(taskResult.extra));
}
break;
case MSG_SET_HEADER:
taskResult.task.mProgressDialog.setHeader(taskResult.header);
break;
case MSG_PROGRESS:
taskResult.task.mProgressDialog.updateProgress(taskResult.fileName, taskResult.progress);
break;
}
}
}
private static class TaskResult {
MultiActionTask task;
int progress;
String fileName;
String header;
TaskStatusEnum status;
int invokedOnPanel;
Object extra;
}
}