blob: b49f47fee0a5868682b7606c09063898d53409e5 [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/drive/search_metadata.h"
#include <algorithm>
#include <queue>
#include "base/bind.h"
#include "base/i18n/string_search.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/chromeos/drive/file_cache.h"
#include "chrome/browser/chromeos/drive/file_system_util.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/escape.h"
using content::BrowserThread;
namespace drive {
namespace internal {
namespace {
// Used to sort the result candidates per the last accessed/modified time. The
// recently accessed/modified files come first.
bool CompareByTimestamp(const ResourceEntry& a, const ResourceEntry& b) {
const PlatformFileInfoProto& a_file_info = a.file_info();
const PlatformFileInfoProto& b_file_info = b.file_info();
if (a_file_info.last_accessed() != b_file_info.last_accessed())
return a_file_info.last_accessed() > b_file_info.last_accessed();
// When the entries have the same last access time (which happens quite often
// because Drive server doesn't set the field until an entry is viewed via
// drive.google.com), we use last modified time as the tie breaker.
return a_file_info.last_modified() > b_file_info.last_modified();
}
struct MetadataSearchResultComparator {
bool operator()(const MetadataSearchResult* a,
const MetadataSearchResult* b) const {
return CompareByTimestamp(a->entry, b->entry);
}
};
// A wrapper of std::priority_queue which deals with pointers of values.
template<typename T, typename Compare>
class ScopedPriorityQueue {
public:
ScopedPriorityQueue() {}
~ScopedPriorityQueue() {
while (!empty())
pop();
}
bool empty() const { return queue_.empty(); }
size_t size() const { return queue_.size(); }
const T* top() const { return queue_.top(); }
void push(T* x) { queue_.push(x); }
void pop() {
delete queue_.top();
queue_.pop();
}
private:
std::priority_queue<T*, std::vector<T*>, Compare> queue_;
DISALLOW_COPY_AND_ASSIGN(ScopedPriorityQueue);
};
// Returns true if |entry| is eligible for the search |options| and should be
// tested for the match with the query. If
// SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS is requested, the hosted documents
// are skipped. If SEARCH_METADATA_EXCLUDE_DIRECTORIES is requested, the
// directories are skipped. If SEARCH_METADATA_SHARED_WITH_ME is requested, only
// the entries with shared-with-me label will be tested. If
// SEARCH_METADATA_OFFLINE is requested, only hosted documents and cached files
// match with the query. This option can not be used with other options.
bool IsEligibleEntry(const ResourceEntry& entry,
internal::FileCache* cache,
int options) {
if ((options & SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS) &&
entry.file_specific_info().is_hosted_document())
return false;
if ((options & SEARCH_METADATA_EXCLUDE_DIRECTORIES) &&
entry.file_info().is_directory())
return false;
if (options & SEARCH_METADATA_SHARED_WITH_ME)
return entry.shared_with_me();
if (options & SEARCH_METADATA_OFFLINE) {
if (entry.file_specific_info().is_hosted_document())
return true;
FileCacheEntry cache_entry;
cache->GetCacheEntry(entry.resource_id(),
std::string(),
&cache_entry);
return cache_entry.is_present();
}
// Exclude "drive", "drive/root", and "drive/other".
if (entry.resource_id() == util::kDriveGrandRootSpecialResourceId ||
entry.parent_resource_id() == util::kDriveGrandRootSpecialResourceId) {
return false;
}
return true;
}
// Used to implement SearchMetadata.
// Adds entry to the result when appropriate.
// In particular, if |query| is non-null, only adds files with the name matching
// the query.
void MaybeAddEntryToResult(
ResourceMetadata* resource_metadata,
FileCache* cache,
base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents* query,
int options,
size_t at_most_num_matches,
ScopedPriorityQueue<MetadataSearchResult,
MetadataSearchResultComparator>* result_candidates,
const ResourceEntry& entry) {
DCHECK_GE(at_most_num_matches, result_candidates->size());
// If the candidate set is already full, and this |entry| is old, do nothing.
// We perform this check first in order to avoid the costly find-and-highlight
// or FilePath lookup as much as possible.
if (result_candidates->size() == at_most_num_matches &&
!CompareByTimestamp(entry, result_candidates->top()->entry))
return;
// Add |entry| to the result if the entry is eligible for the given
// |options| and matches the query. The base name of the entry must
// contain |query| to match the query.
std::string highlighted;
if (!IsEligibleEntry(entry, cache, options) ||
(query && !FindAndHighlight(entry.base_name(), query, &highlighted)))
return;
// Make space for |entry| when appropriate.
if (result_candidates->size() == at_most_num_matches)
result_candidates->pop();
result_candidates->push(new MetadataSearchResult(entry, highlighted));
}
// Implements SearchMetadata().
scoped_ptr<MetadataSearchResultVector> SearchMetadataOnBlockingPool(
ResourceMetadata* resource_metadata,
FileCache* cache,
const std::string& query_text,
int options,
int at_most_num_matches) {
ScopedPriorityQueue<MetadataSearchResult,
MetadataSearchResultComparator> result_candidates;
// Prepare data structure for searching.
base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents query(
base::UTF8ToUTF16(query_text));
// Iterate over entries.
scoped_ptr<ResourceMetadata::Iterator> it = resource_metadata->GetIterator();
for (; !it->IsAtEnd(); it->Advance()) {
MaybeAddEntryToResult(resource_metadata, cache,
query_text.empty() ? NULL : &query,
options,
at_most_num_matches, &result_candidates, it->Get());
}
// Prepare the result.
scoped_ptr<MetadataSearchResultVector> results(
new MetadataSearchResultVector);
for (; !result_candidates.empty(); result_candidates.pop()) {
// The path field of entries in result_candidates are empty at this point,
// because we don't want to run the expensive metadata DB look up except for
// the final results. Hence, here we fill the part.
base::FilePath path = resource_metadata->GetFilePath(
result_candidates.top()->entry.resource_id());
if (path.empty())
continue;
results->push_back(*result_candidates.top());
results->back().path = path;
}
// Reverse the order here because |result_candidates| puts the most
// uninteresting candidate at the top.
std::reverse(results->begin(), results->end());
return results.Pass();
}
} // namespace
void SearchMetadata(
scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
ResourceMetadata* resource_metadata,
FileCache* cache,
const std::string& query,
int options,
int at_most_num_matches,
const SearchMetadataCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK_LE(0, at_most_num_matches);
DCHECK(!callback.is_null());
// TODO(hashimoto): Report error code from ResourceMetadata::IterateEntries
// and stop binding FILE_ERROR_OK to |callback|.
base::PostTaskAndReplyWithResult(blocking_task_runner.get(),
FROM_HERE,
base::Bind(&SearchMetadataOnBlockingPool,
resource_metadata,
cache,
query,
options,
at_most_num_matches),
base::Bind(callback, FILE_ERROR_OK));
}
bool FindAndHighlight(
const std::string& text,
base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents* query,
std::string* highlighted_text) {
DCHECK(query);
DCHECK(highlighted_text);
highlighted_text->clear();
string16 text16 = base::UTF8ToUTF16(text);
size_t match_start = 0;
size_t match_length = 0;
if (!query->Search(text16, &match_start, &match_length))
return false;
string16 pre = text16.substr(0, match_start);
string16 match = text16.substr(match_start, match_length);
string16 post = text16.substr(match_start + match_length);
highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(pre)));
highlighted_text->append("<b>");
highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(match)));
highlighted_text->append("</b>");
highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(post)));
return true;
}
} // namespace internal
} // namespace drive