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/memory/scoped_vector.h" | |
12 #include "base/metrics/histogram.h" | 13 #include "base/metrics/histogram.h" |
14 #include "base/strings/string_split.h" | |
13 #include "base/strings/utf_string_conversions.h" | 15 #include "base/strings/utf_string_conversions.h" |
14 #include "base/time/time.h" | 16 #include "base/time/time.h" |
15 #include "components/drive/drive_api_util.h" | 17 #include "components/drive/drive_api_util.h" |
16 #include "components/drive/file_system_core_util.h" | 18 #include "components/drive/file_system_core_util.h" |
17 #include "net/base/escape.h" | 19 #include "net/base/escape.h" |
18 | 20 |
19 namespace drive { | 21 namespace drive { |
20 namespace internal { | 22 namespace internal { |
21 | 23 |
22 namespace { | 24 namespace { |
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
134 | 136 |
135 private: | 137 private: |
136 ResourceMetadata* metadata_; | 138 ResourceMetadata* metadata_; |
137 | 139 |
138 // local ID to is_hidden map. | 140 // local ID to is_hidden map. |
139 std::map<std::string, bool> is_hiding_child_; | 141 std::map<std::string, bool> is_hiding_child_; |
140 }; | 142 }; |
141 | 143 |
142 // Used to implement SearchMetadata. | 144 // Used to implement SearchMetadata. |
143 // Adds entry to the result when appropriate. | 145 // Adds entry to the result when appropriate. |
144 // In particular, if |query| is non-null, only adds files with the name matching | 146 // In particular, if size of |queries| is larger than 0, only adds files with |
145 // the query. | 147 // the name matching the query. |
146 FileError MaybeAddEntryToResult( | 148 FileError MaybeAddEntryToResult( |
147 ResourceMetadata* resource_metadata, | 149 ResourceMetadata* resource_metadata, |
148 ResourceMetadata::Iterator* it, | 150 ResourceMetadata::Iterator* it, |
149 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents* query, | 151 const ScopedVector< |
152 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents>& queries, | |
150 const SearchMetadataPredicate& predicate, | 153 const SearchMetadataPredicate& predicate, |
151 size_t at_most_num_matches, | 154 size_t at_most_num_matches, |
152 HiddenEntryClassifier* hidden_entry_classifier, | 155 HiddenEntryClassifier* hidden_entry_classifier, |
153 ScopedPriorityQueue<ResultCandidate, ResultCandidateComparator>* | 156 ScopedPriorityQueue<ResultCandidate, ResultCandidateComparator>* |
154 result_candidates) { | 157 result_candidates) { |
155 DCHECK_GE(at_most_num_matches, result_candidates->size()); | 158 DCHECK_GE(at_most_num_matches, result_candidates->size()); |
156 | 159 |
157 const ResourceEntry& entry = it->GetValue(); | 160 const ResourceEntry& entry = it->GetValue(); |
158 | 161 |
159 // If the candidate set is already full, and this |entry| is old, do nothing. | 162 // 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 | 163 // We perform this check first in order to avoid the costly find-and-highlight |
161 // or FilePath lookup as much as possible. | 164 // or FilePath lookup as much as possible. |
162 if (result_candidates->size() == at_most_num_matches && | 165 if (result_candidates->size() == at_most_num_matches && |
163 !CompareByTimestamp(entry, result_candidates->top()->entry)) | 166 !CompareByTimestamp(entry, result_candidates->top()->entry)) |
164 return FILE_ERROR_OK; | 167 return FILE_ERROR_OK; |
165 | 168 |
166 // Add |entry| to the result if the entry is eligible for the given | 169 // 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 | 170 // |options| and matches the query. The base name of the entry must |
168 // contain |query| to match the query. | 171 // contain |query| to match the query. |
169 std::string highlighted; | 172 std::string highlighted; |
170 if (!predicate.Run(entry) || | 173 if (!predicate.Run(entry) || |
171 (query && !FindAndHighlight(entry.base_name(), query, &highlighted))) | 174 (queries.size() > 0 && |
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), base::string16(1, ' '), |
hashimoto
2015/09/02 03:24:11
IIUC this code doesn't support whitespace characte
yawano
2015/09/04 09:33:55
No. This should be able to handle other types of w
| |
203 base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); | |
204 | |
205 ScopedVector<base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents> | |
206 queries; | |
207 for (const auto& keyword : keywords) { | |
208 queries.push_back( | |
209 new base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents( | |
210 keyword)); | |
211 } | |
199 | 212 |
200 // Prepare an object to filter out hidden entries. | 213 // Prepare an object to filter out hidden entries. |
201 ResourceEntry mydrive; | 214 ResourceEntry mydrive; |
202 FileError error = resource_metadata->GetResourceEntryByPath( | 215 FileError error = resource_metadata->GetResourceEntryByPath( |
203 util::GetDriveMyDriveRootPath(), &mydrive); | 216 util::GetDriveMyDriveRootPath(), &mydrive); |
204 if (error != FILE_ERROR_OK) | 217 if (error != FILE_ERROR_OK) |
205 return error; | 218 return error; |
206 HiddenEntryClassifier hidden_entry_classifier(resource_metadata, | 219 HiddenEntryClassifier hidden_entry_classifier(resource_metadata, |
207 mydrive.local_id()); | 220 mydrive.local_id()); |
208 | 221 |
209 // Iterate over entries. | 222 // Iterate over entries. |
210 scoped_ptr<ResourceMetadata::Iterator> it = resource_metadata->GetIterator(); | 223 scoped_ptr<ResourceMetadata::Iterator> it = resource_metadata->GetIterator(); |
211 for (; !it->IsAtEnd(); it->Advance()) { | 224 for (; !it->IsAtEnd(); it->Advance()) { |
212 FileError error = MaybeAddEntryToResult( | 225 FileError error = MaybeAddEntryToResult( |
213 resource_metadata, it.get(), query_text.empty() ? NULL : &query, | 226 resource_metadata, it.get(), queries, predicate, at_most_num_matches, |
214 predicate, at_most_num_matches, &hidden_entry_classifier, | 227 &hidden_entry_classifier, &result_candidates); |
215 &result_candidates); | |
216 if (error != FILE_ERROR_OK) | 228 if (error != FILE_ERROR_OK) |
217 return error; | 229 return error; |
218 } | 230 } |
219 | 231 |
220 // Prepare the result. | 232 // Prepare the result. |
221 for (; !result_candidates.empty(); result_candidates.pop()) { | 233 for (; !result_candidates.empty(); result_candidates.pop()) { |
222 const ResultCandidate& candidate = *result_candidates.top(); | 234 const ResultCandidate& candidate = *result_candidates.top(); |
223 // The path field of entries in result_candidates are empty at this point, | 235 // 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 | 236 // 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. | 237 // the final results. Hence, here we fill the part. |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
301 } else { | 313 } else { |
302 return entry.file_specific_info().cache_state().is_present(); | 314 return entry.file_specific_info().cache_state().is_present(); |
303 } | 315 } |
304 } | 316 } |
305 | 317 |
306 return true; | 318 return true; |
307 } | 319 } |
308 | 320 |
309 bool FindAndHighlight( | 321 bool FindAndHighlight( |
310 const std::string& text, | 322 const std::string& text, |
311 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents* query, | 323 const ScopedVector< |
324 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents>& queries, | |
312 std::string* highlighted_text) { | 325 std::string* highlighted_text) { |
313 DCHECK(query); | |
314 DCHECK(highlighted_text); | 326 DCHECK(highlighted_text); |
315 highlighted_text->clear(); | 327 highlighted_text->clear(); |
316 | 328 |
317 base::string16 text16 = base::UTF8ToUTF16(text); | |
318 size_t match_start = 0; | 329 size_t match_start = 0; |
319 size_t match_length = 0; | 330 size_t match_length = 0; |
320 if (!query->Search(text16, &match_start, &match_length)) | |
321 return false; | |
322 | 331 |
323 base::string16 pre = text16.substr(0, match_start); | 332 base::string16 text16 = base::UTF8ToUTF16(text); |
324 base::string16 match = text16.substr(match_start, match_length); | 333 std::vector<bool> highlights(text16.size(), false); |
325 base::string16 post = text16.substr(match_start + match_length); | 334 for (auto* query : queries) { |
326 highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(pre))); | 335 if (!query->Search(text16, &match_start, &match_length)) |
327 highlighted_text->append("<b>"); | 336 return false; |
328 highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(match))); | 337 |
329 highlighted_text->append("</b>"); | 338 int match_end = static_cast<int>(match_start + match_length); |
hashimoto
2015/09/02 03:24:11
You don't have to static_cast if you use size_t in
yawano
2015/09/04 09:33:55
Done.
| |
330 highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(post))); | 339 for (int i = static_cast<int>(match_start); i < match_end; i++) { |
hashimoto
2015/09/02 03:24:11
++i instead of i++
yawano
2015/09/04 09:33:55
Done.
| |
340 highlights[i] = true; | |
341 } | |
342 } | |
343 | |
344 // Generate highlighted text. | |
345 bool highlighting = false; | |
346 for (int i = 0; i < static_cast<int>(text16.size()); i++) { | |
hashimoto
2015/09/02 03:24:11
Use size_t, ++i
yawano
2015/09/04 09:33:55
Done.
| |
347 if (highlighting != highlights[i]) { | |
348 highlighted_text->append(highlighting ? "</b>" : "<b>"); | |
349 highlighting = highlights[i]; | |
350 } | |
351 | |
352 // TODO(yawano): Optimize this. Append as a chunk. | |
353 highlighted_text->append( | |
354 net::EscapeForHTML(base::UTF16ToUTF8(text16.substr(i, 1)))); | |
hashimoto
2015/09/02 03:24:11
Does this code work with a string which contains s
yawano
2015/09/04 09:33:55
No, this hasn't worked. Fixed and added unit test.
| |
355 } | |
356 | |
357 if (highlighting) | |
358 highlighted_text->append("</b>"); | |
359 | |
331 return true; | 360 return true; |
332 } | 361 } |
333 | 362 |
334 } // namespace internal | 363 } // namespace internal |
335 } // namespace drive | 364 } // namespace drive |
OLD | NEW |