search_metadata.cc revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
1// Copyright (c) 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/chromeos/drive/search_metadata.h" 6 7#include <algorithm> 8#include <queue> 9 10#include "base/bind.h" 11#include "base/i18n/string_search.h" 12#include "base/strings/utf_string_conversions.h" 13#include "chrome/browser/chromeos/drive/file_cache.h" 14#include "chrome/browser/chromeos/drive/file_system_util.h" 15#include "content/public/browser/browser_thread.h" 16#include "net/base/escape.h" 17 18using content::BrowserThread; 19 20namespace drive { 21namespace internal { 22 23namespace { 24 25// Used to sort the result candidates per the last accessed/modified time. The 26// recently accessed/modified files come first. 27bool CompareByTimestamp(const ResourceEntry& a, const ResourceEntry& b) { 28 const PlatformFileInfoProto& a_file_info = a.file_info(); 29 const PlatformFileInfoProto& b_file_info = b.file_info(); 30 31 if (a_file_info.last_accessed() != b_file_info.last_accessed()) 32 return a_file_info.last_accessed() > b_file_info.last_accessed(); 33 34 // When the entries have the same last access time (which happens quite often 35 // because Drive server doesn't set the field until an entry is viewed via 36 // drive.google.com), we use last modified time as the tie breaker. 37 return a_file_info.last_modified() > b_file_info.last_modified(); 38} 39 40struct MetadataSearchResultComparator { 41 bool operator()(const MetadataSearchResult* a, 42 const MetadataSearchResult* b) const { 43 return CompareByTimestamp(a->entry, b->entry); 44 } 45}; 46 47// A wrapper of std::priority_queue which deals with pointers of values. 48template<typename T, typename Compare> 49class ScopedPriorityQueue { 50 public: 51 ScopedPriorityQueue() {} 52 53 ~ScopedPriorityQueue() { 54 while (!empty()) 55 pop(); 56 } 57 58 bool empty() const { return queue_.empty(); } 59 60 size_t size() const { return queue_.size(); } 61 62 const T* top() const { return queue_.top(); } 63 64 void push(T* x) { queue_.push(x); } 65 66 void pop() { 67 delete queue_.top(); 68 queue_.pop(); 69 } 70 71 private: 72 std::priority_queue<T*, std::vector<T*>, Compare> queue_; 73 74 DISALLOW_COPY_AND_ASSIGN(ScopedPriorityQueue); 75}; 76 77// Returns true if |entry| is eligible for the search |options| and should be 78// tested for the match with the query. If 79// SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS is requested, the hosted documents 80// are skipped. If SEARCH_METADATA_EXCLUDE_DIRECTORIES is requested, the 81// directories are skipped. If SEARCH_METADATA_SHARED_WITH_ME is requested, only 82// the entries with shared-with-me label will be tested. If 83// SEARCH_METADATA_OFFLINE is requested, only hosted documents and cached files 84// match with the query. This option can not be used with other options. 85bool IsEligibleEntry(const ResourceEntry& entry, 86 internal::FileCache* cache, 87 int options) { 88 if ((options & SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS) && 89 entry.file_specific_info().is_hosted_document()) 90 return false; 91 92 if ((options & SEARCH_METADATA_EXCLUDE_DIRECTORIES) && 93 entry.file_info().is_directory()) 94 return false; 95 96 if (options & SEARCH_METADATA_SHARED_WITH_ME) 97 return entry.shared_with_me(); 98 99 if (options & SEARCH_METADATA_OFFLINE) { 100 if (entry.file_specific_info().is_hosted_document()) 101 return true; 102 FileCacheEntry cache_entry; 103 cache->GetCacheEntry(entry.resource_id(), 104 std::string(), 105 &cache_entry); 106 return cache_entry.is_present(); 107 } 108 109 // Exclude "drive", "drive/root", and "drive/other". 110 if (entry.resource_id() == util::kDriveGrandRootSpecialResourceId || 111 entry.parent_resource_id() == util::kDriveGrandRootSpecialResourceId) { 112 return false; 113 } 114 115 return true; 116} 117 118// Used to implement SearchMetadata. 119// Adds entry to the result when appropriate. 120void MaybeAddEntryToResult( 121 ResourceMetadata* resource_metadata, 122 FileCache* cache, 123 const std::string& query, 124 int options, 125 size_t at_most_num_matches, 126 ScopedPriorityQueue<MetadataSearchResult, 127 MetadataSearchResultComparator>* result_candidates, 128 const ResourceEntry& entry) { 129 DCHECK_GE(at_most_num_matches, result_candidates->size()); 130 131 // If the candidate set is already full, and this |entry| is old, do nothing. 132 // We perform this check first in order to avoid the costly find-and-highlight 133 // or FilePath lookup as much as possible. 134 if (result_candidates->size() == at_most_num_matches && 135 !CompareByTimestamp(entry, result_candidates->top()->entry)) 136 return; 137 138 // Add |entry| to the result if the entry is eligible for the given 139 // |options| and matches the query. The base name of the entry must 140 // contain |query| to match the query. 141 std::string highlighted; 142 if (!IsEligibleEntry(entry, cache, options) || 143 !FindAndHighlight(entry.base_name(), query, &highlighted)) 144 return; 145 146 base::FilePath path = resource_metadata->GetFilePath(entry.resource_id()); 147 if (path.empty()) 148 return; 149 150 // Make space for |entry| when appropriate. 151 if (result_candidates->size() == at_most_num_matches) 152 result_candidates->pop(); 153 result_candidates->push(new MetadataSearchResult(path, entry, highlighted)); 154} 155 156// Implements SearchMetadata(). 157scoped_ptr<MetadataSearchResultVector> SearchMetadataOnBlockingPool( 158 ResourceMetadata* resource_metadata, 159 FileCache* cache, 160 const std::string& query, 161 int options, 162 int at_most_num_matches) { 163 ScopedPriorityQueue<MetadataSearchResult, 164 MetadataSearchResultComparator> result_candidates; 165 166 // Iterate over entries. 167 scoped_ptr<ResourceMetadata::Iterator> it = resource_metadata->GetIterator(); 168 for (; !it->IsAtEnd(); it->Advance()) { 169 MaybeAddEntryToResult(resource_metadata, cache, query, options, 170 at_most_num_matches, &result_candidates, it->Get()); 171 } 172 173 // Prepare the result. 174 scoped_ptr<MetadataSearchResultVector> results( 175 new MetadataSearchResultVector); 176 for (; !result_candidates.empty(); result_candidates.pop()) 177 results->push_back(*result_candidates.top()); 178 179 // Reverse the order here because |result_candidates| puts the most 180 // uninterested candidate at the top. 181 std::reverse(results->begin(), results->end()); 182 183 return results.Pass(); 184} 185} // namespace 186 187void SearchMetadata( 188 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, 189 ResourceMetadata* resource_metadata, 190 FileCache* cache, 191 const std::string& query, 192 int options, 193 int at_most_num_matches, 194 const SearchMetadataCallback& callback) { 195 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 196 DCHECK_LE(0, at_most_num_matches); 197 DCHECK(!callback.is_null()); 198 199 // TODO(hashimoto): Report error code from ResourceMetadata::IterateEntries 200 // and stop binding FILE_ERROR_OK to |callback|. 201 base::PostTaskAndReplyWithResult(blocking_task_runner.get(), 202 FROM_HERE, 203 base::Bind(&SearchMetadataOnBlockingPool, 204 resource_metadata, 205 cache, 206 query, 207 options, 208 at_most_num_matches), 209 base::Bind(callback, FILE_ERROR_OK)); 210} 211 212bool FindAndHighlight(const std::string& text, 213 const std::string& query, 214 std::string* highlighted_text) { 215 DCHECK(highlighted_text); 216 highlighted_text->clear(); 217 218 // For empty query, any filename matches with no highlighted text. 219 if (query.empty()) 220 return true; 221 222 string16 text16 = base::UTF8ToUTF16(text); 223 string16 query16 = base::UTF8ToUTF16(query); 224 size_t match_start = 0; 225 size_t match_length = 0; 226 if (!base::i18n::StringSearchIgnoringCaseAndAccents( 227 query16, text16, &match_start, &match_length)) { 228 return false; 229 } 230 string16 pre = text16.substr(0, match_start); 231 string16 match = text16.substr(match_start, match_length); 232 string16 post = text16.substr(match_start + match_length); 233 highlighted_text->append(net::EscapeForHTML(UTF16ToUTF8(pre))); 234 highlighted_text->append("<b>"); 235 highlighted_text->append(net::EscapeForHTML(UTF16ToUTF8(match))); 236 highlighted_text->append("</b>"); 237 highlighted_text->append(net::EscapeForHTML(UTF16ToUTF8(post))); 238 return true; 239} 240 241} // namespace internal 242} // namespace drive 243