Add new search logic to the MessagingController and LocalStore classes.

This commit is contained in:
Sander Bogaert 2012-10-13 08:53:00 -04:00
parent 5c6552cbf3
commit d27f909600
2 changed files with 98 additions and 217 deletions

View File

@ -69,6 +69,7 @@ import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.mail.store.LocalStore.PendingCommand;
import com.fsck.k9.mail.store.UnavailableAccountException;
import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchSpecification;
@ -619,136 +620,39 @@ public class MessagingController implements Runnable {
}
}
public void searchLocalMessages(SearchSpecification searchSpecification, final Message[] messages, final MessagingListener listener) {
searchLocalMessages(searchSpecification.getAccountUuids(), searchSpecification.getFolderNames(), messages,
searchSpecification.getQuery(), searchSpecification.isIntegrate(), searchSpecification.getRequiredFlags(), searchSpecification.getForbiddenFlags(), listener);
}
/**
* Find all messages in any local account which match the query 'query'
* @throws MessagingException
*/
public void searchLocalMessages(final String[] accountUuids, final String[] folderNames, final Message[] messages, final String query, final boolean integrate,
final Flag[] requiredFlags, final Flag[] forbiddenFlags, final MessagingListener listener) {
if (K9.DEBUG) {
Log.i(K9.LOG_TAG, "searchLocalMessages ("
+ "accountUuids=" + Utility.combine(accountUuids, ',')
+ ", folderNames = " + Utility.combine(folderNames, ',')
+ ", messages.size() = " + (messages != null ? messages.length : -1)
+ ", query = " + query
+ ", integrate = " + integrate
+ ", requiredFlags = " + Utility.combine(requiredFlags, ',')
+ ", forbiddenFlags = " + Utility.combine(forbiddenFlags, ',')
+ ")");
}
public void searchLocalMessages(final LocalSearch search, final MessagingListener listener) {
threadPool.execute(new Runnable() {
@Override
public void run() {
searchLocalMessagesSynchronous(accountUuids, folderNames, messages, query, integrate, requiredFlags, forbiddenFlags, listener);
searchLocalMessagesSynchronous(search, listener);
}
});
}
public void searchLocalMessagesSynchronous(final String[] accountUuids, final String[] folderNames, final Message[] messages, final String query, final boolean integrate, final Flag[] requiredFlags, final Flag[] forbiddenFlags, final MessagingListener listener) {
public void searchLocalMessagesSynchronous(final LocalSearch search, final MessagingListener listener) {
final AccountStats stats = new AccountStats();
final Set<String> accountUuidsSet = new HashSet<String>();
if (accountUuids != null) {
accountUuidsSet.addAll(Arrays.asList(accountUuids));
}
final Preferences prefs = Preferences.getPreferences(mApplication.getApplicationContext());
List<LocalFolder> foldersToSearch = null;
boolean displayableOnly = false;
boolean noSpecialFolders = true;
for (final Account account : prefs.getAvailableAccounts()) {
if (accountUuids != null && !accountUuidsSet.contains(account.getUuid())) {
continue;
}
if (accountUuids != null && accountUuidsSet.contains(account.getUuid())) {
displayableOnly = true;
noSpecialFolders = true;
} else if (!integrate && folderNames == null) {
Account.Searchable searchableFolders = account.getSearchableFolders();
switch (searchableFolders) {
case NONE:
continue;
case DISPLAYABLE:
displayableOnly = true;
break;
}
}
List<Message> messagesToSearch = null;
if (messages != null) {
messagesToSearch = new LinkedList<Message>();
for (Message message : messages) {
if (message.getFolder().getAccount().getUuid().equals(account.getUuid())) {
messagesToSearch.add(message);
}
}
if (messagesToSearch.isEmpty()) {
continue;
}
}
if (listener != null) {
listener.listLocalMessagesStarted(account, null);
}
if (integrate || displayableOnly || folderNames != null || noSpecialFolders) {
List<LocalFolder> tmpFoldersToSearch = new LinkedList<LocalFolder>();
try {
LocalStore store = account.getLocalStore();
List <? extends Folder > folders = store.getPersonalNamespaces(false);
Set<String> folderNameSet = null;
if (folderNames != null) {
folderNameSet = new HashSet<String>();
folderNameSet.addAll(Arrays.asList(folderNames));
}
for (Folder folder : folders) {
LocalFolder localFolder = (LocalFolder)folder;
boolean include = true;
folder.refresh(prefs);
String localFolderName = localFolder.getName();
if (integrate) {
include = localFolder.isIntegrate();
} else {
if (folderNameSet != null) {
if (!folderNameSet.contains(localFolderName))
{
include = false;
}
}
// Never exclude the INBOX (see issue 1817)
else if (noSpecialFolders && !localFolderName.equalsIgnoreCase(account.getInboxFolderName()) &&
!localFolderName.equals(account.getArchiveFolderName()) && account.isSpecialFolder(localFolderName)) {
include = false;
} else if (displayableOnly && modeMismatch(account.getFolderDisplayMode(), folder.getDisplayClass())) {
include = false;
}
}
if (include) {
tmpFoldersToSearch.add(localFolder);
}
}
if (tmpFoldersToSearch.size() < 1) {
continue;
}
foldersToSearch = tmpFoldersToSearch;
} catch (MessagingException me) {
Log.e(K9.LOG_TAG, "Unable to restrict search folders in Account " + account.getDescription() + ", searching all", me);
addErrorMessage(account, null, me);
}
}
final HashSet<String> uuidSet = new HashSet<String>(Arrays.asList(search.getAccountUuids()));
Account[] accounts = Preferences.getPreferences(mApplication.getApplicationContext()).getAccounts();
boolean allAccounts = uuidSet.contains(SearchSpecification.ALL_ACCOUNTS);
// for every account we want to search do the query in the localstore
for (final Account account : accounts) {
if (!allAccounts && !uuidSet.contains(account.getUuid())) {
continue;
}
// Collecting statistics of the search result
MessageRetrievalListener retrievalListener = new MessageRetrievalListener() {
@Override
public void messageStarted(String message, int number, int ofTotal) {}
@Override
public void messagesFinished(int number) {}
@Override
public void messageFinished(Message message, int number, int ofTotal) {
if (!isMessageSuppressed(message.getFolder().getAccount(), message.getFolder().getName(), message)) {
List<Message> messages = new ArrayList<Message>();
@ -760,22 +664,18 @@ public class MessagingController implements Runnable {
listener.listLocalMessagesAddMessages(account, null, messages);
}
}
}
@Override
public void messagesFinished(int number) {
}
};
// alert everyone the search has started
if (listener != null) {
listener.listLocalMessagesStarted(account, null);
}
// build and do the query in the localstore
try {
String[] queryFields = {"html_content", "subject", "sender_list"};
LocalStore localStore = account.getLocalStore();
localStore.searchForMessages(retrievalListener, queryFields
, query, foldersToSearch,
messagesToSearch == null ? null : messagesToSearch.toArray(EMPTY_MESSAGE_ARRAY),
requiredFlags, forbiddenFlags);
LocalStore localStore = account.getLocalStore();
localStore.searchForMessages(retrievalListener, search);
} catch (Exception e) {
if (listener != null) {
listener.listLocalMessagesFailed(account, null, e.getMessage());
@ -786,7 +686,9 @@ public class MessagingController implements Runnable {
listener.listLocalMessagesFinished(account, null);
}
}
}
}
// publish the total search statistics
if (listener != null) {
listener.searchStats(stats);
}

View File

@ -69,6 +69,9 @@ import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
import com.fsck.k9.provider.AttachmentProvider;
import com.fsck.k9.search.ConditionsTreeNode;
import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchSpecification.SEARCHFIELD;
/**
* <pre>
@ -658,6 +661,22 @@ public class LocalStore extends Store implements Serializable {
return new LocalFolder(name);
}
private long getFolderId(final String name) throws MessagingException {
return database.execute(false, new DbCallback<Long>() {
@Override
public Long doDbWork(final SQLiteDatabase db) {
Cursor cursor = null;
try {
cursor = db.rawQuery("SELECT id FROM folders WHERE name = '" + name + "'", null);
cursor.moveToFirst();
return cursor.getLong(0);
} finally {
Utility.closeQuietly(cursor);
}
}
});
}
// TODO this takes about 260-300ms, seems slow.
@Override
public List <? extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException {
@ -890,97 +909,57 @@ public class LocalStore extends Store implements Serializable {
return true;
}
public Message[] searchForMessages(MessageRetrievalListener listener, String[] queryFields, String queryString,
List<LocalFolder> folders, Message[] messages, final Flag[] requiredFlags, final Flag[] forbiddenFlags) throws MessagingException {
List<String> args = new LinkedList<String>();
StringBuilder whereClause = new StringBuilder();
if (queryString != null && queryString.length() > 0) {
boolean anyAdded = false;
String likeString = "%" + queryString + "%";
whereClause.append(" AND (");
for (String queryField : queryFields) {
if (anyAdded) {
whereClause.append(" OR ");
// TODO find beter solution
private static boolean isFolderId(String str) {
if (str == null) {
return false;
}
int length = str.length();
if (length == 0) {
return false;
}
int i = 0;
if (str.charAt(0) == '-') {
return false;
}
for (; i < length; i++) {
char c = str.charAt(i);
if (c <= '/' || c >= ':') {
return false;
}
whereClause.append(queryField).append(" LIKE ? ");
args.add(likeString);
anyAdded = true;
}
whereClause.append(" )");
}
if (folders != null && !folders.isEmpty()) {
whereClause.append(" AND folder_id in (");
boolean anyAdded = false;
for (LocalFolder folder : folders) {
if (anyAdded) {
whereClause.append(",");
}
anyAdded = true;
whereClause.append("?");
args.add(Long.toString(folder.getId()));
}
whereClause.append(" )");
}
if (messages != null && messages.length > 0) {
whereClause.append(" AND ( ");
boolean anyAdded = false;
for (Message message : messages) {
if (anyAdded) {
whereClause.append(" OR ");
}
anyAdded = true;
whereClause.append(" ( uid = ? AND folder_id = ? ) ");
args.add(message.getUid());
args.add(Long.toString(((LocalFolder)message.getFolder()).getId()));
}
whereClause.append(" )");
}
if (forbiddenFlags != null && forbiddenFlags.length > 0) {
whereClause.append(" AND (");
boolean anyAdded = false;
for (Flag flag : forbiddenFlags) {
if (anyAdded) {
whereClause.append(" AND ");
}
anyAdded = true;
whereClause.append(" flags NOT LIKE ?");
args.add("%" + flag.toString() + "%");
}
whereClause.append(" )");
}
if (requiredFlags != null && requiredFlags.length > 0) {
whereClause.append(" AND (");
boolean anyAdded = false;
for (Flag flag : requiredFlags) {
if (anyAdded) {
whereClause.append(" OR ");
}
anyAdded = true;
whereClause.append(" flags LIKE ?");
args.add("%" + flag.toString() + "%");
}
whereClause.append(" )");
}
if (K9.DEBUG) {
Log.v(K9.LOG_TAG, "whereClause = " + whereClause.toString());
Log.v(K9.LOG_TAG, "args = " + args);
}
return getMessages(
listener,
null,
"SELECT "
+ GET_MESSAGES_COLS
+ "FROM messages WHERE (empty IS NULL OR empty != 1) AND deleted = 0 " + whereClause.toString() + " ORDER BY date DESC"
, args.toArray(EMPTY_STRING_ARRAY)
);
return true;
}
public Message[] searchForMessages(MessageRetrievalListener retrievalListener,
LocalSearch search) throws MessagingException {
// update some references in the search that have to be bound to this one store
for (ConditionsTreeNode node : search.getLeafSet()) {
if (node.mCondition.field == SEARCHFIELD.FOLDER) {
// TODO find better solution
if (isFolderId(node.mCondition.value)) {
continue;
}
if (node.mCondition.value.equals(LocalSearch.GENERIC_INBOX_NAME)) {
node.mCondition.value = mAccount.getInboxFolderName();
}
node.mCondition.value = String.valueOf(getFolderId(node.mCondition.value));
}
}
// build sql query
String sqlQuery = "SELECT " + GET_MESSAGES_COLS + "FROM messages WHERE deleted = 0 "
+ (search.getConditions() != null ? "AND (" + search.getConditions() + ")" : "") + " ORDER BY date DESC";
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Query = " + sqlQuery);
}
return getMessages(retrievalListener, null, sqlQuery, new String[] {});
}
/*
* Given a query string, actually do the query for the messages and
* call the MessageRetrievalListener for each one