package org.openintents.filemanager.search;
import java.io.File;
import java.io.FilenameFilter;
import org.openintents.filemanager.R;
import android.app.SearchManager;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
/**
* Provides the search core, used by every search subsystem that provides results.
*
* @author George Venios
*
*/
public class SearchCore {
private String mQuery;
private Uri mContentURI;
private Context mContext;
/** See {@link #setRoot(File)} */
private File root = Environment.getExternalStorageDirectory();
private int mResultCount = 0;
private int mMaxResults = -1;
private long mMaxNanos = -1;
private long mStart;
public SearchCore(Context context) {
mContext = context;
}
private FilenameFilter filter = new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
return mQuery == null ? false : filename.toLowerCase().contains(mQuery.toLowerCase());
}
};
public void setQuery(String q) {
mQuery = q;
}
public String getQuery() {
return mQuery;
}
/**
* Set the first directory to recursively search.
*
* @param root
* The directory to search first.
*/
public void setRoot(File root) {
this.root = root;
}
/**
* Set the content URI, of which the results are. Used for operations on the correct search content providers.
*
* @param URI
* The URI.
*/
public void setURI(Uri URI) {
mContentURI = URI;
}
/**
* Set the maximum number of results to get before search ends.
*
* @param i
* Zero or less will be ignored. The desired number of results.
*/
public void setMaxResults(int i) {
mMaxResults = i;
}
/**
* Call this to start the search stopwatch. First call of this should be right before the call to {@link #search(File)}.
* @param maxNanos The search duration in nanos.
*/
public void startClock(long maxNanos){
mMaxNanos = maxNanos;
mStart = System.nanoTime();
}
private void insertResult(File f) {
mResultCount++;
ContentValues values = new ContentValues();
if (mContentURI == SearchResultsProvider.CONTENT_URI) {
values.put(SearchResultsProvider.COLUMN_NAME, f.getName());
values.put(SearchResultsProvider.COLUMN_PATH, f.getAbsolutePath());
} else if (mContentURI == SearchSuggestionsProvider.CONTENT_URI) {
values.put(SearchManager.SUGGEST_COLUMN_ICON_1,
f.isDirectory() ? R.drawable.ic_launcher_folder
: R.drawable.ic_launcher_file);
values.put(SearchManager.SUGGEST_COLUMN_TEXT_1, f.getName());
values.put(SearchManager.SUGGEST_COLUMN_TEXT_2, f.getAbsolutePath());
values.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA,
f.getAbsolutePath());
}
mContext.getContentResolver().insert(mContentURI, values);
}
/**
* Reset the results of the previous queries.
*
* @return The previous result count.
*/
public int dropPreviousResults() {
mResultCount = 0;
return mContext.getContentResolver().delete(mContentURI, null, null);
}
/**
* Core search function. Recursively searches files from root of external storage to the leaves. Prioritizes {@link #root}'s subtree.
*
* @param dir
* The starting dir for the search. Callers outside of this class are highly encouraged to use the same as {@link #root}.
*/
public void search(File dir) {
// Results in root pass
for (File f : dir.listFiles(filter)) {
insertResult(f);
// Break search on result count and search time conditions.
if ((mMaxResults > 0 && mResultCount >= mMaxResults) || (mMaxNanos > 0 && System.nanoTime()-mStart > mMaxNanos)) {
return;
}
}
// Recursion pass
for (File f : dir.listFiles()) {
// Prevent us from re-searching the root directory, or trying to search invalid Files.
if (f.isDirectory() && f.canRead() && !isChildOf(f, root))
search(f);
}
// If we're on the parent of the recursion, and we're done searching, start searching the rest of the FS.
if (dir.equals(root) && !root.equals(Environment.getExternalStorageDirectory())) {
search(Environment.getExternalStorageDirectory());
}
}
/**
* @param f1
* @param f2
* @return If f1 is child of f2. Also true if f1 equals f2.
*/
private boolean isChildOf(File f1, File f2) {
return f2.getAbsolutePath().startsWith(f1.getAbsolutePath());
}
}