| OLD | NEW |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "components/drive/search_metadata.h" | 5 #include "components/drive/search_metadata.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <queue> | 8 #include <queue> |
| 9 | 9 |
| 10 #include "base/bind.h" | 10 #include "base/bind.h" |
| 11 #include "base/i18n/string_search.h" | 11 #include "base/i18n/string_search.h" |
| 12 #include "base/metrics/histogram.h" | 12 #include "base/metrics/histogram.h" |
| 13 #include "base/strings/string_piece.h" |
| 14 #include "base/strings/string_split.h" |
| 15 #include "base/strings/string_util.h" |
| 13 #include "base/strings/utf_string_conversions.h" | 16 #include "base/strings/utf_string_conversions.h" |
| 14 #include "base/time/time.h" | 17 #include "base/time/time.h" |
| 15 #include "components/drive/drive_api_util.h" | 18 #include "components/drive/drive_api_util.h" |
| 16 #include "components/drive/file_system_core_util.h" | 19 #include "components/drive/file_system_core_util.h" |
| 17 #include "net/base/escape.h" | 20 #include "net/base/escape.h" |
| 18 | 21 |
| 19 namespace drive { | 22 namespace drive { |
| 20 namespace internal { | 23 namespace internal { |
| 21 | 24 |
| 22 namespace { | 25 namespace { |
| (...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 134 | 137 |
| 135 private: | 138 private: |
| 136 ResourceMetadata* metadata_; | 139 ResourceMetadata* metadata_; |
| 137 | 140 |
| 138 // local ID to is_hidden map. | 141 // local ID to is_hidden map. |
| 139 std::map<std::string, bool> is_hiding_child_; | 142 std::map<std::string, bool> is_hiding_child_; |
| 140 }; | 143 }; |
| 141 | 144 |
| 142 // Used to implement SearchMetadata. | 145 // Used to implement SearchMetadata. |
| 143 // Adds entry to the result when appropriate. | 146 // Adds entry to the result when appropriate. |
| 144 // In particular, if |query| is non-null, only adds files with the name matching | 147 // In particular, if size of |queries| is larger than 0, only adds files with |
| 145 // the query. | 148 // the name matching the query. |
| 146 FileError MaybeAddEntryToResult( | 149 FileError MaybeAddEntryToResult( |
| 147 ResourceMetadata* resource_metadata, | 150 ResourceMetadata* resource_metadata, |
| 148 ResourceMetadata::Iterator* it, | 151 ResourceMetadata::Iterator* it, |
| 149 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents* query, | 152 const ScopedVector< |
| 153 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents>& queries, |
| 150 const SearchMetadataPredicate& predicate, | 154 const SearchMetadataPredicate& predicate, |
| 151 size_t at_most_num_matches, | 155 size_t at_most_num_matches, |
| 152 HiddenEntryClassifier* hidden_entry_classifier, | 156 HiddenEntryClassifier* hidden_entry_classifier, |
| 153 ScopedPriorityQueue<ResultCandidate, ResultCandidateComparator>* | 157 ScopedPriorityQueue<ResultCandidate, ResultCandidateComparator>* |
| 154 result_candidates) { | 158 result_candidates) { |
| 155 DCHECK_GE(at_most_num_matches, result_candidates->size()); | 159 DCHECK_GE(at_most_num_matches, result_candidates->size()); |
| 156 | 160 |
| 157 const ResourceEntry& entry = it->GetValue(); | 161 const ResourceEntry& entry = it->GetValue(); |
| 158 | 162 |
| 159 // If the candidate set is already full, and this |entry| is old, do nothing. | 163 // If the candidate set is already full, and this |entry| is old, do nothing. |
| 160 // We perform this check first in order to avoid the costly find-and-highlight | 164 // We perform this check first in order to avoid the costly find-and-highlight |
| 161 // or FilePath lookup as much as possible. | 165 // or FilePath lookup as much as possible. |
| 162 if (result_candidates->size() == at_most_num_matches && | 166 if (result_candidates->size() == at_most_num_matches && |
| 163 !CompareByTimestamp(entry, result_candidates->top()->entry)) | 167 !CompareByTimestamp(entry, result_candidates->top()->entry)) |
| 164 return FILE_ERROR_OK; | 168 return FILE_ERROR_OK; |
| 165 | 169 |
| 166 // Add |entry| to the result if the entry is eligible for the given | 170 // Add |entry| to the result if the entry is eligible for the given |
| 167 // |options| and matches the query. The base name of the entry must | 171 // |options| and matches the query. The base name of the entry must |
| 168 // contain |query| to match the query. | 172 // contain |query| to match the query. |
| 169 std::string highlighted; | 173 std::string highlighted; |
| 170 if (!predicate.Run(entry) || | 174 if (!predicate.Run(entry) || |
| 171 (query && !FindAndHighlight(entry.base_name(), query, &highlighted))) | 175 !FindAndHighlight(entry.base_name(), queries, &highlighted)) |
| 172 return FILE_ERROR_OK; | 176 return FILE_ERROR_OK; |
| 173 | 177 |
| 174 // Hidden entry should not be returned. | 178 // Hidden entry should not be returned. |
| 175 bool hidden = false; | 179 bool hidden = false; |
| 176 FileError error = hidden_entry_classifier->IsHidden(entry, &hidden); | 180 FileError error = hidden_entry_classifier->IsHidden(entry, &hidden); |
| 177 if (error != FILE_ERROR_OK || hidden) | 181 if (error != FILE_ERROR_OK || hidden) |
| 178 return error; | 182 return error; |
| 179 | 183 |
| 180 // Make space for |entry| when appropriate. | 184 // Make space for |entry| when appropriate. |
| 181 if (result_candidates->size() == at_most_num_matches) | 185 if (result_candidates->size() == at_most_num_matches) |
| 182 result_candidates->pop(); | 186 result_candidates->pop(); |
| 183 result_candidates->push(new ResultCandidate(it->GetID(), entry, highlighted)); | 187 result_candidates->push(new ResultCandidate(it->GetID(), entry, highlighted)); |
| 184 return FILE_ERROR_OK; | 188 return FILE_ERROR_OK; |
| 185 } | 189 } |
| 186 | 190 |
| 187 // Implements SearchMetadata(). | 191 // Implements SearchMetadata(). |
| 188 FileError SearchMetadataOnBlockingPool(ResourceMetadata* resource_metadata, | 192 FileError SearchMetadataOnBlockingPool(ResourceMetadata* resource_metadata, |
| 189 const std::string& query_text, | 193 const std::string& query_text, |
| 190 const SearchMetadataPredicate& predicate, | 194 const SearchMetadataPredicate& predicate, |
| 191 int at_most_num_matches, | 195 int at_most_num_matches, |
| 192 MetadataSearchResultVector* results) { | 196 MetadataSearchResultVector* results) { |
| 193 ScopedPriorityQueue<ResultCandidate, | 197 ScopedPriorityQueue<ResultCandidate, |
| 194 ResultCandidateComparator> result_candidates; | 198 ResultCandidateComparator> result_candidates; |
| 195 | 199 |
| 196 // Prepare data structure for searching. | 200 // Prepare data structure for searching. |
| 197 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents query( | 201 std::vector<base::string16> keywords = |
| 198 base::UTF8ToUTF16(query_text)); | 202 base::SplitString(base::UTF8ToUTF16(query_text), |
| 203 base::StringPiece16(base::kWhitespaceUTF16), |
| 204 base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| 205 |
| 206 ScopedVector<base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents> |
| 207 queries; |
| 208 for (const auto& keyword : keywords) { |
| 209 queries.push_back( |
| 210 new base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents( |
| 211 keyword)); |
| 212 } |
| 199 | 213 |
| 200 // Prepare an object to filter out hidden entries. | 214 // Prepare an object to filter out hidden entries. |
| 201 ResourceEntry mydrive; | 215 ResourceEntry mydrive; |
| 202 FileError error = resource_metadata->GetResourceEntryByPath( | 216 FileError error = resource_metadata->GetResourceEntryByPath( |
| 203 util::GetDriveMyDriveRootPath(), &mydrive); | 217 util::GetDriveMyDriveRootPath(), &mydrive); |
| 204 if (error != FILE_ERROR_OK) | 218 if (error != FILE_ERROR_OK) |
| 205 return error; | 219 return error; |
| 206 HiddenEntryClassifier hidden_entry_classifier(resource_metadata, | 220 HiddenEntryClassifier hidden_entry_classifier(resource_metadata, |
| 207 mydrive.local_id()); | 221 mydrive.local_id()); |
| 208 | 222 |
| 209 // Iterate over entries. | 223 // Iterate over entries. |
| 210 scoped_ptr<ResourceMetadata::Iterator> it = resource_metadata->GetIterator(); | 224 scoped_ptr<ResourceMetadata::Iterator> it = resource_metadata->GetIterator(); |
| 211 for (; !it->IsAtEnd(); it->Advance()) { | 225 for (; !it->IsAtEnd(); it->Advance()) { |
| 212 FileError error = MaybeAddEntryToResult( | 226 FileError error = MaybeAddEntryToResult( |
| 213 resource_metadata, it.get(), query_text.empty() ? NULL : &query, | 227 resource_metadata, it.get(), queries, predicate, at_most_num_matches, |
| 214 predicate, at_most_num_matches, &hidden_entry_classifier, | 228 &hidden_entry_classifier, &result_candidates); |
| 215 &result_candidates); | |
| 216 if (error != FILE_ERROR_OK) | 229 if (error != FILE_ERROR_OK) |
| 217 return error; | 230 return error; |
| 218 } | 231 } |
| 219 | 232 |
| 220 // Prepare the result. | 233 // Prepare the result. |
| 221 for (; !result_candidates.empty(); result_candidates.pop()) { | 234 for (; !result_candidates.empty(); result_candidates.pop()) { |
| 222 const ResultCandidate& candidate = *result_candidates.top(); | 235 const ResultCandidate& candidate = *result_candidates.top(); |
| 223 // The path field of entries in result_candidates are empty at this point, | 236 // The path field of entries in result_candidates are empty at this point, |
| 224 // because we don't want to run the expensive metadata DB look up except for | 237 // because we don't want to run the expensive metadata DB look up except for |
| 225 // the final results. Hence, here we fill the part. | 238 // the final results. Hence, here we fill the part. |
| (...skipping 20 matching lines...) Expand all Loading... |
| 246 scoped_ptr<MetadataSearchResultVector> results, | 259 scoped_ptr<MetadataSearchResultVector> results, |
| 247 FileError error) { | 260 FileError error) { |
| 248 if (error != FILE_ERROR_OK) | 261 if (error != FILE_ERROR_OK) |
| 249 results.reset(); | 262 results.reset(); |
| 250 callback.Run(error, results.Pass()); | 263 callback.Run(error, results.Pass()); |
| 251 | 264 |
| 252 UMA_HISTOGRAM_TIMES("Drive.SearchMetadataTime", | 265 UMA_HISTOGRAM_TIMES("Drive.SearchMetadataTime", |
| 253 base::TimeTicks::Now() - start_time); | 266 base::TimeTicks::Now() - start_time); |
| 254 } | 267 } |
| 255 | 268 |
| 269 // Appends substring of |original_text| to |highlighted_text| with highlight. |
| 270 void AppendStringWithHighlight(const base::string16& original_text, |
| 271 size_t start, |
| 272 size_t length, |
| 273 bool highlight, |
| 274 std::string* highlighted_text) { |
| 275 if (highlight) |
| 276 highlighted_text->append("<b>"); |
| 277 |
| 278 highlighted_text->append(net::EscapeForHTML( |
| 279 base::UTF16ToUTF8(original_text.substr(start, length)))); |
| 280 |
| 281 if (highlight) |
| 282 highlighted_text->append("</b>"); |
| 283 } |
| 284 |
| 256 } // namespace | 285 } // namespace |
| 257 | 286 |
| 258 void SearchMetadata( | 287 void SearchMetadata( |
| 259 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, | 288 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, |
| 260 ResourceMetadata* resource_metadata, | 289 ResourceMetadata* resource_metadata, |
| 261 const std::string& query, | 290 const std::string& query, |
| 262 const SearchMetadataPredicate& predicate, | 291 const SearchMetadataPredicate& predicate, |
| 263 size_t at_most_num_matches, | 292 size_t at_most_num_matches, |
| 264 const SearchMetadataCallback& callback) { | 293 const SearchMetadataCallback& callback) { |
| 265 DCHECK(!callback.is_null()); | 294 DCHECK(!callback.is_null()); |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 301 } else { | 330 } else { |
| 302 return entry.file_specific_info().cache_state().is_present(); | 331 return entry.file_specific_info().cache_state().is_present(); |
| 303 } | 332 } |
| 304 } | 333 } |
| 305 | 334 |
| 306 return true; | 335 return true; |
| 307 } | 336 } |
| 308 | 337 |
| 309 bool FindAndHighlight( | 338 bool FindAndHighlight( |
| 310 const std::string& text, | 339 const std::string& text, |
| 311 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents* query, | 340 const ScopedVector< |
| 341 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents>& queries, |
| 312 std::string* highlighted_text) { | 342 std::string* highlighted_text) { |
| 313 DCHECK(query); | |
| 314 DCHECK(highlighted_text); | 343 DCHECK(highlighted_text); |
| 315 highlighted_text->clear(); | 344 highlighted_text->clear(); |
| 316 | 345 |
| 317 base::string16 text16 = base::UTF8ToUTF16(text); | 346 // Check text matches with all queries. |
| 318 size_t match_start = 0; | 347 size_t match_start = 0; |
| 319 size_t match_length = 0; | 348 size_t match_length = 0; |
| 320 if (!query->Search(text16, &match_start, &match_length)) | |
| 321 return false; | |
| 322 | 349 |
| 323 base::string16 pre = text16.substr(0, match_start); | 350 base::string16 text16 = base::UTF8ToUTF16(text); |
| 324 base::string16 match = text16.substr(match_start, match_length); | 351 std::vector<bool> highlights(text16.size(), false); |
| 325 base::string16 post = text16.substr(match_start + match_length); | 352 for (auto* query : queries) { |
| 326 highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(pre))); | 353 if (!query->Search(text16, &match_start, &match_length)) |
| 327 highlighted_text->append("<b>"); | 354 return false; |
| 328 highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(match))); | 355 |
| 329 highlighted_text->append("</b>"); | 356 std::fill(highlights.begin() + match_start, |
| 330 highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(post))); | 357 highlights.begin() + match_start + match_length, true); |
| 358 } |
| 359 |
| 360 // Generate highlighted text. |
| 361 size_t start_current_segment = 0; |
| 362 |
| 363 for (size_t i = 0; i < text16.size(); ++i) { |
| 364 if (highlights[start_current_segment] == highlights[i]) |
| 365 continue; |
| 366 |
| 367 AppendStringWithHighlight( |
| 368 text16, start_current_segment, i - start_current_segment, |
| 369 highlights[start_current_segment], highlighted_text); |
| 370 |
| 371 start_current_segment = i; |
| 372 } |
| 373 |
| 374 DCHECK_GE(text16.size(), start_current_segment); |
| 375 AppendStringWithHighlight( |
| 376 text16, start_current_segment, text16.size() - start_current_segment, |
| 377 highlights[start_current_segment], highlighted_text); |
| 378 |
| 331 return true; | 379 return true; |
| 332 } | 380 } |
| 333 | 381 |
| 334 } // namespace internal | 382 } // namespace internal |
| 335 } // namespace drive | 383 } // namespace drive |
| OLD | NEW |