package cgeo.geocaching.files;
import cgeo.geocaching.Intents;
import cgeo.geocaching.R;
import cgeo.geocaching.activity.AbstractActionBarActivity;
import cgeo.geocaching.list.StoredList;
import cgeo.geocaching.storage.LocalStorage;
import cgeo.geocaching.ui.WeakReferenceHandler;
import cgeo.geocaching.ui.dialog.Dialogs;
import cgeo.geocaching.ui.recyclerview.RecyclerViewProvider;
import cgeo.geocaching.utils.EnvironmentUtils;
import cgeo.geocaching.utils.FileUtils;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.TextUtils;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
public abstract class AbstractFileListActivity<T extends RecyclerView.Adapter<? extends RecyclerView.ViewHolder>> extends AbstractActionBarActivity {
private static final int MSG_SEARCH_WHOLE_SD_CARD = 1;
private final List<File> files = new ArrayList<>();
private T adapter = null;
private ProgressDialog waitDialog = null;
private SearchFilesThread searchingThread = null;
protected int listId = StoredList.STANDARD_LIST_ID;
private String[] extensions;
private final Handler changeWaitDialogHandler = new ChangeWaitDialogHandler<>(this);
private final Handler loadFilesHandler = new LoadFilesHandler<>(this);
private static final class ChangeWaitDialogHandler<T extends RecyclerView.Adapter<? extends RecyclerView.ViewHolder>> extends WeakReferenceHandler<AbstractFileListActivity<T>> {
private String searchInfo;
ChangeWaitDialogHandler(final AbstractFileListActivity<T> activity) {
super(activity);
}
@Override
public void handleMessage(final Message msg) {
final AbstractFileListActivity<T> activity = getReference();
if (activity != null && msg.obj != null && activity.waitDialog != null) {
if (searchInfo == null) {
searchInfo = activity.res.getString(R.string.file_searching_in) + " ";
}
if (msg.what == MSG_SEARCH_WHOLE_SD_CARD) {
searchInfo = String.format(activity.res.getString(R.string.file_searching_sdcard_in), getDefaultFolders(activity));
}
activity.waitDialog.setMessage(searchInfo + msg.obj);
}
}
private String getDefaultFolders(@NonNull final AbstractFileListActivity<T> activity) {
final List<String> names = new ArrayList<>();
for (final File dir : activity.getExistingBaseFolders()) {
names.add(dir.getPath());
}
return StringUtils.join(names, '\n');
}
}
private static final class LoadFilesHandler<T extends RecyclerView.Adapter<? extends RecyclerView.ViewHolder>> extends WeakReferenceHandler<AbstractFileListActivity<T>> {
LoadFilesHandler(final AbstractFileListActivity<T> activity) {
super(activity);
}
@Override
public void handleMessage(final Message msg) {
final AbstractFileListActivity<T> activity = getReference();
if (activity != null) {
Dialogs.dismiss(activity.waitDialog);
if (CollectionUtils.isEmpty(activity.files) && activity.requireFiles()) {
activity.showToast(activity.res.getString(R.string.file_list_no_files));
activity.finish();
} else if (activity.adapter != null) {
activity.adapter.notifyDataSetChanged();
}
}
}
}
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme();
setContentView(R.layout.gpx);
final Bundle extras = getIntent().getExtras();
if (extras != null) {
listId = extras.getInt(Intents.EXTRA_LIST_ID);
}
if (listId <= StoredList.TEMPORARY_LIST.id) {
listId = StoredList.STANDARD_LIST_ID;
}
adapter = getAdapter(files);
final RecyclerView view = RecyclerViewProvider.provideRecyclerView(this, R.id.list, true, true);
view.setAdapter(adapter);
waitDialog = ProgressDialog.show(
this,
res.getString(R.string.file_title_searching),
res.getString(R.string.file_searching),
true,
true,
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(final DialogInterface arg0) {
if (searchingThread != null && searchingThread.isAlive()) {
searchingThread.notifyEnd();
}
if (files.isEmpty() && requireFiles()) {
finish();
}
}
}
);
searchingThread = new SearchFilesThread();
searchingThread.start();
}
@Override
public void onResume() {
super.onResume();
}
protected boolean requireFiles() {
return true;
}
protected abstract T getAdapter(List<File> files);
/**
* Gets the base folder for file searches
*
* @return The folder to start the recursive search in
*/
protected abstract List<File> getBaseFolders();
private class SearchFilesThread extends Thread {
private final FileListSelector selector = new FileListSelector();
public void notifyEnd() {
selector.setShouldEnd();
}
@Override
public void run() {
final List<File> list = new ArrayList<>();
try {
if (EnvironmentUtils.isExternalStorageAvailable()) {
boolean loaded = false;
for (final File dir : getExistingBaseFolders()) {
FileUtils.listDir(list, dir, selector, changeWaitDialogHandler);
if (!list.isEmpty()) {
loaded = true;
break;
}
}
if (!loaded) {
changeWaitDialogHandler.sendMessage(Message.obtain(changeWaitDialogHandler, MSG_SEARCH_WHOLE_SD_CARD, Environment.getExternalStorageDirectory().getName()));
listDirs(list, LocalStorage.getStorages(), selector, changeWaitDialogHandler);
}
} else {
Log.w("No external media mounted.");
}
} catch (final Exception e) {
Log.e("AbstractFileListActivity.loadFiles.run", e);
}
changeWaitDialogHandler.sendMessage(Message.obtain(changeWaitDialogHandler, 0, "loaded directories"));
files.addAll(list);
Collections.sort(files, new Comparator<File>() {
@Override
public int compare(final File lhs, final File rhs) {
return TextUtils.COLLATOR.compare(lhs.getName(), rhs.getName());
}
});
runOnUiThread(new Runnable() {
@Override
public void run() {
adapter.notifyDataSetChanged();
}
});
loadFilesHandler.sendMessage(Message.obtain(loadFilesHandler));
}
private void listDirs(final List<File> list, final List<File> directories, final FileListSelector selector, final Handler feedbackHandler) {
for (final File dir : directories) {
FileUtils.listDir(list, dir, selector, feedbackHandler);
}
}
}
/**
* Check if a filename belongs to the AbstractFileListActivity. This implementation checks for file extensions.
* Subclasses may override this method to filter out specific files.
*
* @return {@code true} if the filename belongs to the list
*/
protected boolean filenameBelongsToList(@NonNull final String filename) {
for (final String ext : extensions) {
if (StringUtils.endsWithIgnoreCase(filename, ext)) {
return true;
}
}
return false;
}
protected List<File> getExistingBaseFolders() {
final List<File> result = new ArrayList<>();
for (final File dir : getBaseFolders()) {
if (dir.exists() && dir.isDirectory()) {
result.add(dir);
}
}
return result;
}
protected AbstractFileListActivity(final String extension) {
setExtensions(new String[] { extension });
}
protected AbstractFileListActivity(final String[] extensions) {
setExtensions(extensions);
}
private void setExtensions(final String[] extensionsIn) {
extensions = extensionsIn;
for (int i = 0; i < extensions.length; i++) {
final String extension = extensions[i];
if (StringUtils.isEmpty(extension) || extension.charAt(0) != '.') {
extensions[i] = "." + extension;
}
}
}
private class FileListSelector implements FileUtils.FileSelector {
boolean shouldEnd = false;
@Override
public boolean isSelected(final File file) {
return filenameBelongsToList(file.getName());
}
@Override
public synchronized boolean shouldEnd() {
return shouldEnd;
}
public synchronized void setShouldEnd() {
this.shouldEnd = true;
}
}
@Override
public void finish() {
Dialogs.dismiss(waitDialog);
super.finish();
}
}