Blacklist spam, trash in search; fix #7067

This commit is contained in:
Charles Lindsay 2013-06-13 16:44:46 -07:00
parent 06cb693c0e
commit 7cf33f293e
2 changed files with 123 additions and 21 deletions

View file

@ -35,9 +35,12 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder {
private weak Account _account;
private SearchFolderProperties properties = new SearchFolderProperties(0, 0);
private Gee.HashSet<Geary.FolderPath> exclude_folders = new Gee.HashSet<Geary.FolderPath>();
private Geary.SpecialFolderType[] exclude_types = { Geary.SpecialFolderType.SPAM,
Geary.SpecialFolderType.TRASH };
private Gee.HashSet<Geary.FolderPath?> exclude_folders = new Gee.HashSet<Geary.FolderPath?>();
private Geary.SpecialFolderType[] exclude_types = {
Geary.SpecialFolderType.SPAM,
Geary.SpecialFolderType.TRASH,
// Orphan emails (without a folder) are also excluded; see ctor.
};
private Gee.TreeSet<Geary.Email> search_results;
private Geary.Nonblocking.Mutex result_mutex = new Geary.Nonblocking.Mutex();
@ -49,12 +52,28 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder {
public SearchFolder(Account account) {
_account = account;
account.folders_available_unavailable.connect(on_folders_available_unavailable);
clear_search_results();
// TODO: The exclusion system needs to watch for changes, since the special folders are
// not always ready by the time this c'tor executes.
foreach(Geary.SpecialFolderType type in exclude_types)
exclude_special_folder(type);
// We always want to exclude emails that don't live anywhere from
// search results.
exclude_orphan_emails();
}
~SearchFolder() {
account.folders_available_unavailable.disconnect(on_folders_available_unavailable);;
}
private void on_folders_available_unavailable(Gee.Collection<Geary.Folder>? available,
Gee.Collection<Geary.Folder>? unavailable) {
if (available != null) {
foreach (Geary.Folder folder in available) {
// Exclude it from searching if it's got the right special type.
if (folder.get_special_folder_type() in exclude_types)
exclude_folder(folder);
}
}
}
/**
@ -217,16 +236,12 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder {
return yield account.local_fetch_email_async(id, required_fields, cancellable);
}
private void exclude_special_folder(Geary.SpecialFolderType type) {
Geary.Folder? folder = null;
try {
folder = account.get_special_folder(type);
} catch (Error e) {
debug("Could not get special folder: %s", e.message);
}
if (folder != null)
exclude_folders.add(folder.get_path());
private void exclude_folder(Geary.Folder folder) {
exclude_folders.add(folder.get_path());
}
private void exclude_orphan_emails() {
exclude_folders.add(null);
}
private uint email_id_hash(Geary.Email a) {

View file

@ -587,22 +587,41 @@ private class Geary.ImapDB.Account : BaseObject {
return prepared_query.str.strip();
}
// Append each id in the collection to the StringBuilder, in a format
// suitable for use in an SQL statement IN (...) clause.
private void sql_append_ids(StringBuilder s, Gee.Collection<int64?> ids) {
bool first = true;
foreach (int64? id in ids) {
assert(id != null);
if (!first)
s.append(", ");
s.append(id.to_string());
first = false;
}
}
public async Gee.Collection<Geary.Email>? search_async(string prepared_query,
Geary.Email.Field requested_fields, bool partial_ok, int limit = 100, int offset = 0,
Gee.Collection<Geary.FolderPath?>? folder_blacklist = null,
Gee.Collection<Geary.EmailIdentifier>? search_ids = null, Cancellable? cancellable = null) throws Error {
Gee.Collection<Geary.Email> search_results = new Gee.HashSet<Geary.Email>();
// TODO: support blacklist, search_ids
// TODO: support search_ids
yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
string blacklisted_ids_sql = do_get_blacklisted_message_ids_sql(
folder_blacklist, cx, cancellable);
string sql = """
SELECT id
FROM MessageSearchTable
JOIN MessageTable USING (id)
WHERE MessageSearchTable MATCH ?
ORDER BY internaldate_time_t DESC
""";
if (blacklisted_ids_sql != "")
sql += " AND id NOT IN (%s)".printf(blacklisted_ids_sql);
sql += " ORDER BY internaldate_time_t DESC";
if (limit > 0)
sql += " LIMIT ? OFFSET ?";
Db.Statement stmt = cx.prepare(sql);
@ -618,8 +637,11 @@ private class Geary.ImapDB.Account : BaseObject {
MessageRow row = Geary.ImapDB.Folder.do_fetch_message_row(
cx, id, requested_fields, cancellable);
if (partial_ok || row.fields.fulfills(requested_fields))
search_results.add(row.to_email(-1, new Geary.ImapDB.EmailIdentifier(id)));
if (partial_ok || row.fields.fulfills(requested_fields)) {
Geary.Email email = row.to_email(-1, new Geary.ImapDB.EmailIdentifier(id));
Geary.ImapDB.Folder.do_add_attachments(cx, email, id, cancellable);
search_results.add(email);
}
result.next(cancellable);
}
@ -877,6 +899,71 @@ private class Geary.ImapDB.Account : BaseObject {
return do_fetch_folder_id(cx, path.get_parent(), create, out parent_id, cancellable);
}
// Turn the collection of folder paths into actual folder ids. As a
// special case, if "folderless" or orphan emails are to be blacklisted,
// set the out bool to true.
private Gee.Collection<int64?> do_get_blacklisted_folder_ids(Gee.Collection<Geary.FolderPath?>? folder_blacklist,
Db.Connection cx, out bool blacklist_folderless, Cancellable? cancellable) throws Error {
blacklist_folderless = false;
Gee.ArrayList<int64?> ids = new Gee.ArrayList<int64?>();
if (folder_blacklist != null) {
foreach (Geary.FolderPath? folder_path in folder_blacklist) {
if (folder_path == null) {
blacklist_folderless = true;
} else {
int64 id;
do_fetch_folder_id(cx, folder_path, true, out id, cancellable);
if (id != Db.INVALID_ROWID)
ids.add(id);
}
}
}
return ids;
}
// Return a parameterless SQL statement that selects any message ids that
// are in a blacklisted folder. This is used as a sub-select for the
// search query to omit results from blacklisted folders.
private string do_get_blacklisted_message_ids_sql(Gee.Collection<Geary.FolderPath?>? folder_blacklist,
Db.Connection cx, Cancellable? cancellable) throws Error {
bool blacklist_folderless;
Gee.Collection<int64?> blacklisted_ids = do_get_blacklisted_folder_ids(
folder_blacklist, cx, out blacklist_folderless, cancellable);
StringBuilder sql = new StringBuilder();
if (blacklisted_ids.size > 0 || blacklist_folderless) {
if (blacklist_folderless) {
// We select out of the MessageTable and join on the location
// table so we can recognize emails that aren't in any folders.
// This is slightly more complicated than the case below, where
// we can just select directly out of the location table.
sql.append("""
SELECT m.id
FROM MessageTable m
LEFT JOIN MessageLocationTable l ON l.message_id = m.id
WHERE folder_id IS NULL
""");
if (blacklisted_ids.size > 0) {
sql.append(" OR folder_id IN (");
sql_append_ids(sql, blacklisted_ids);
sql.append(")");
}
} else {
sql.append("""
SELECT message_id
FROM MessageLocationTable
WHERE folder_id IN (
""");
sql_append_ids(sql, blacklisted_ids);
sql.append(")");
}
}
return sql.str;
}
// For a message row id, return a set of all folders it's in, or null if
// it's not in any folders.
private Gee.Set<Geary.FolderPath>? do_find_email_folders(Db.Connection cx, int64 message_id,