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_system_util.h" 14 #include "content/public/browser/browser_thread.h" 15 #include "net/base/escape.h" 16 17 using content::BrowserThread; 18 19 namespace drive { 20 namespace internal { 21 22 namespace { 23 24 // Used to sort the result candidates per the last accessed/modified time. The 25 // recently accessed/modified files come first. 26 bool CompareByTimestamp(const ResourceEntry& a, const ResourceEntry& b) { 27 const PlatformFileInfoProto& a_file_info = a.file_info(); 28 const PlatformFileInfoProto& b_file_info = b.file_info(); 29 30 if (a_file_info.last_accessed() != b_file_info.last_accessed()) 31 return a_file_info.last_accessed() > b_file_info.last_accessed(); 32 33 // When the entries have the same last access time (which happens quite often 34 // because Drive server doesn't set the field until an entry is viewed via 35 // drive.google.com), we use last modified time as the tie breaker. 36 return a_file_info.last_modified() > b_file_info.last_modified(); 37 } 38 39 struct MetadataSearchResultComparator { 40 bool operator()(const MetadataSearchResult* a, 41 const MetadataSearchResult* b) const { 42 return CompareByTimestamp(a->entry, b->entry); 43 } 44 }; 45 46 // A wrapper of std::priority_queue which deals with pointers of values. 47 template<typename T, typename Compare> 48 class ScopedPriorityQueue { 49 public: 50 ScopedPriorityQueue() {} 51 52 ~ScopedPriorityQueue() { 53 while (!empty()) 54 pop(); 55 } 56 57 bool empty() const { return queue_.empty(); } 58 59 size_t size() const { return queue_.size(); } 60 61 const T* top() const { return queue_.top(); } 62 63 void push(T* x) { queue_.push(x); } 64 65 void pop() { 66 delete queue_.top(); 67 queue_.pop(); 68 } 69 70 private: 71 std::priority_queue<T*, std::vector<T*>, Compare> queue_; 72 73 DISALLOW_COPY_AND_ASSIGN(ScopedPriorityQueue); 74 }; 75 76 // Returns true if |entry| is eligible for the search |options| and should be 77 // tested for the match with the query. If 78 // SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS is requested, the hosted documents 79 // are skipped. If SEARCH_METADATA_EXCLUDE_DIRECTORIES is requested, the 80 // directories are skipped. If SEARCH_METADATA_SHARED_WITH_ME is requested, only 81 // the entries with shared-with-me label will be tested. If 82 // SEARCH_METADATA_OFFLINE is requested, only hosted documents and cached files 83 // match with the query. This option can not be used with other options. 84 bool IsEligibleEntry(const ResourceEntry& entry, 85 ResourceMetadata::Iterator* it, 86 int options) { 87 if ((options & SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS) && 88 entry.file_specific_info().is_hosted_document()) 89 return false; 90 91 if ((options & SEARCH_METADATA_EXCLUDE_DIRECTORIES) && 92 entry.file_info().is_directory()) 93 return false; 94 95 if (options & SEARCH_METADATA_SHARED_WITH_ME) 96 return entry.shared_with_me(); 97 98 if (options & SEARCH_METADATA_OFFLINE) { 99 if (entry.file_specific_info().is_hosted_document()) 100 return true; 101 FileCacheEntry cache_entry; 102 it->GetCacheEntry(&cache_entry); 103 return cache_entry.is_present(); 104 } 105 106 // Exclude "drive", "drive/root", and "drive/other". 107 if (entry.resource_id() == util::kDriveGrandRootSpecialResourceId || 108 entry.parent_resource_id() == util::kDriveGrandRootSpecialResourceId) { 109 return false; 110 } 111 112 return true; 113 } 114 115 // Used to implement SearchMetadata. 116 // Adds entry to the result when appropriate. 117 // In particular, if |query| is non-null, only adds files with the name matching 118 // the query. 119 void MaybeAddEntryToResult( 120 ResourceMetadata* resource_metadata, 121 ResourceMetadata::Iterator* it, 122 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents* query, 123 int options, 124 size_t at_most_num_matches, 125 ScopedPriorityQueue<MetadataSearchResult, 126 MetadataSearchResultComparator>* result_candidates) { 127 DCHECK_GE(at_most_num_matches, result_candidates->size()); 128 129 const ResourceEntry& entry = it->Get(); 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, it, options) || 143 (query && !FindAndHighlight(entry.base_name(), query, &highlighted))) 144 return; 145 146 // Make space for |entry| when appropriate. 147 if (result_candidates->size() == at_most_num_matches) 148 result_candidates->pop(); 149 result_candidates->push(new MetadataSearchResult(entry, highlighted)); 150 } 151 152 // Implements SearchMetadata(). 153 FileError SearchMetadataOnBlockingPool(ResourceMetadata* resource_metadata, 154 const std::string& query_text, 155 int options, 156 int at_most_num_matches, 157 MetadataSearchResultVector* results) { 158 ScopedPriorityQueue<MetadataSearchResult, 159 MetadataSearchResultComparator> result_candidates; 160 161 // Prepare data structure for searching. 162 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents query( 163 base::UTF8ToUTF16(query_text)); 164 165 // Iterate over entries. 166 scoped_ptr<ResourceMetadata::Iterator> it = resource_metadata->GetIterator(); 167 for (; !it->IsAtEnd(); it->Advance()) { 168 MaybeAddEntryToResult(resource_metadata, it.get(), 169 query_text.empty() ? NULL : &query, 170 options, 171 at_most_num_matches, &result_candidates); 172 } 173 174 // Prepare the result. 175 for (; !result_candidates.empty(); result_candidates.pop()) { 176 // The path field of entries in result_candidates are empty at this point, 177 // because we don't want to run the expensive metadata DB look up except for 178 // the final results. Hence, here we fill the part. 179 base::FilePath path = resource_metadata->GetFilePath( 180 result_candidates.top()->entry.resource_id()); 181 if (path.empty()) 182 return FILE_ERROR_FAILED; 183 results->push_back(*result_candidates.top()); 184 results->back().path = path; 185 } 186 187 // Reverse the order here because |result_candidates| puts the most 188 // uninteresting candidate at the top. 189 std::reverse(results->begin(), results->end()); 190 191 return FILE_ERROR_OK; 192 } 193 194 void RunSearchMetadataCallback(const SearchMetadataCallback& callback, 195 scoped_ptr<MetadataSearchResultVector> results, 196 FileError error) { 197 if (error != FILE_ERROR_OK) 198 results.reset(); 199 callback.Run(error, results.Pass()); 200 } 201 202 } // namespace 203 204 void SearchMetadata( 205 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, 206 ResourceMetadata* resource_metadata, 207 const std::string& query, 208 int options, 209 int at_most_num_matches, 210 const SearchMetadataCallback& callback) { 211 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 212 DCHECK_LE(0, at_most_num_matches); 213 DCHECK(!callback.is_null()); 214 215 scoped_ptr<MetadataSearchResultVector> results( 216 new MetadataSearchResultVector); 217 MetadataSearchResultVector* results_ptr = results.get(); 218 base::PostTaskAndReplyWithResult(blocking_task_runner.get(), 219 FROM_HERE, 220 base::Bind(&SearchMetadataOnBlockingPool, 221 resource_metadata, 222 query, 223 options, 224 at_most_num_matches, 225 results_ptr), 226 base::Bind(&RunSearchMetadataCallback, 227 callback, 228 base::Passed(&results))); 229 } 230 231 bool FindAndHighlight( 232 const std::string& text, 233 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents* query, 234 std::string* highlighted_text) { 235 DCHECK(query); 236 DCHECK(highlighted_text); 237 highlighted_text->clear(); 238 239 string16 text16 = base::UTF8ToUTF16(text); 240 size_t match_start = 0; 241 size_t match_length = 0; 242 if (!query->Search(text16, &match_start, &match_length)) 243 return false; 244 245 string16 pre = text16.substr(0, match_start); 246 string16 match = text16.substr(match_start, match_length); 247 string16 post = text16.substr(match_start + match_length); 248 highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(pre))); 249 highlighted_text->append("<b>"); 250 highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(match))); 251 highlighted_text->append("</b>"); 252 highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(post))); 253 return true; 254 } 255 256 } // namespace internal 257 } // namespace drive 258