Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / chromeos / drive / search_metadata.cc
blobe7a7da936b219092b141981c96b38dfde2b2a436
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.
5 #include "chrome/browser/chromeos/drive/search_metadata.h"
7 #include <algorithm>
8 #include <queue>
10 #include "base/bind.h"
11 #include "base/i18n/string_search.h"
12 #include "base/metrics/histogram.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/time/time.h"
15 #include "chrome/browser/chromeos/drive/file_system_util.h"
16 #include "chrome/browser/drive/drive_api_util.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "net/base/escape.h"
20 using content::BrowserThread;
22 namespace drive {
23 namespace internal {
25 namespace {
27 struct ResultCandidate {
28 ResultCandidate(const std::string& local_id,
29 const ResourceEntry& entry,
30 const std::string& highlighted_base_name)
31 : local_id(local_id),
32 entry(entry),
33 highlighted_base_name(highlighted_base_name) {
36 std::string local_id;
37 ResourceEntry entry;
38 std::string highlighted_base_name;
41 // Used to sort the result candidates per the last accessed/modified time. The
42 // recently accessed/modified files come first.
43 bool CompareByTimestamp(const ResourceEntry& a, const ResourceEntry& b) {
44 const PlatformFileInfoProto& a_file_info = a.file_info();
45 const PlatformFileInfoProto& b_file_info = b.file_info();
47 if (a_file_info.last_accessed() != b_file_info.last_accessed())
48 return a_file_info.last_accessed() > b_file_info.last_accessed();
50 // When the entries have the same last access time (which happens quite often
51 // because Drive server doesn't set the field until an entry is viewed via
52 // drive.google.com), we use last modified time as the tie breaker.
53 return a_file_info.last_modified() > b_file_info.last_modified();
56 struct ResultCandidateComparator {
57 bool operator()(const ResultCandidate* a, const ResultCandidate* b) const {
58 return CompareByTimestamp(a->entry, b->entry);
62 // A wrapper of std::priority_queue which deals with pointers of values.
63 template<typename T, typename Compare>
64 class ScopedPriorityQueue {
65 public:
66 ScopedPriorityQueue() {}
68 ~ScopedPriorityQueue() {
69 while (!empty())
70 pop();
73 bool empty() const { return queue_.empty(); }
75 size_t size() const { return queue_.size(); }
77 const T* top() const { return queue_.top(); }
79 void push(T* x) { queue_.push(x); }
81 void pop() {
82 // Keep top alive for the pop() call so that debug checks can access
83 // underlying data (e.g. validating heap property of the priority queue
84 // will call the comparator).
85 T* saved_top = queue_.top();
86 queue_.pop();
87 delete saved_top;
90 private:
91 std::priority_queue<T*, std::vector<T*>, Compare> queue_;
93 DISALLOW_COPY_AND_ASSIGN(ScopedPriorityQueue);
96 // Classifies the given entry as hidden if it's not under specific directories.
97 class HiddenEntryClassifier {
98 public:
99 HiddenEntryClassifier(ResourceMetadata* metadata,
100 const std::string& mydrive_local_id)
101 : metadata_(metadata) {
102 // Only things under My Drive and drive/other are not hidden.
103 is_hiding_child_[mydrive_local_id] = false;
104 is_hiding_child_[util::kDriveOtherDirLocalId] = false;
106 // Everything else is hidden, including the directories mentioned above
107 // themselves.
108 is_hiding_child_[""] = true;
111 // |result| is set to true if |entry| is hidden.
112 FileError IsHidden(const ResourceEntry& entry, bool* result) {
113 // Look up for parents recursively.
114 std::vector<std::string> undetermined_ids;
115 undetermined_ids.push_back(entry.parent_local_id());
117 std::map<std::string, bool>::iterator it =
118 is_hiding_child_.find(undetermined_ids.back());
119 for (; it == is_hiding_child_.end();
120 it = is_hiding_child_.find(undetermined_ids.back())) {
121 ResourceEntry parent;
122 FileError error =
123 metadata_->GetResourceEntryById(undetermined_ids.back(), &parent);
124 if (error != FILE_ERROR_OK)
125 return error;
126 undetermined_ids.push_back(parent.parent_local_id());
129 // Cache the result.
130 undetermined_ids.pop_back(); // The last one is already in the map.
131 for (size_t i = 0; i < undetermined_ids.size(); ++i)
132 is_hiding_child_[undetermined_ids[i]] = it->second;
134 *result = it->second;
135 return FILE_ERROR_OK;
138 private:
139 ResourceMetadata* metadata_;
141 // local ID to is_hidden map.
142 std::map<std::string, bool> is_hiding_child_;
145 // Used to implement SearchMetadata.
146 // Adds entry to the result when appropriate.
147 // In particular, if |query| is non-null, only adds files with the name matching
148 // the query.
149 FileError MaybeAddEntryToResult(
150 ResourceMetadata* resource_metadata,
151 ResourceMetadata::Iterator* it,
152 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents* query,
153 const SearchMetadataPredicate& predicate,
154 size_t at_most_num_matches,
155 HiddenEntryClassifier* hidden_entry_classifier,
156 ScopedPriorityQueue<ResultCandidate, ResultCandidateComparator>*
157 result_candidates) {
158 DCHECK_GE(at_most_num_matches, result_candidates->size());
160 const ResourceEntry& entry = it->GetValue();
162 // If the candidate set is already full, and this |entry| is old, do nothing.
163 // We perform this check first in order to avoid the costly find-and-highlight
164 // or FilePath lookup as much as possible.
165 if (result_candidates->size() == at_most_num_matches &&
166 !CompareByTimestamp(entry, result_candidates->top()->entry))
167 return FILE_ERROR_OK;
169 // Add |entry| to the result if the entry is eligible for the given
170 // |options| and matches the query. The base name of the entry must
171 // contain |query| to match the query.
172 std::string highlighted;
173 if (!predicate.Run(entry) ||
174 (query && !FindAndHighlight(entry.base_name(), query, &highlighted)))
175 return FILE_ERROR_OK;
177 // Hidden entry should not be returned.
178 bool hidden = false;
179 FileError error = hidden_entry_classifier->IsHidden(entry, &hidden);
180 if (error != FILE_ERROR_OK || hidden)
181 return error;
183 // Make space for |entry| when appropriate.
184 if (result_candidates->size() == at_most_num_matches)
185 result_candidates->pop();
186 result_candidates->push(new ResultCandidate(it->GetID(), entry, highlighted));
187 return FILE_ERROR_OK;
190 // Implements SearchMetadata().
191 FileError SearchMetadataOnBlockingPool(ResourceMetadata* resource_metadata,
192 const std::string& query_text,
193 const SearchMetadataPredicate& predicate,
194 int at_most_num_matches,
195 MetadataSearchResultVector* results) {
196 ScopedPriorityQueue<ResultCandidate,
197 ResultCandidateComparator> result_candidates;
199 // Prepare data structure for searching.
200 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents query(
201 base::UTF8ToUTF16(query_text));
203 // Prepare an object to filter out hidden entries.
204 ResourceEntry mydrive;
205 FileError error = resource_metadata->GetResourceEntryByPath(
206 util::GetDriveMyDriveRootPath(), &mydrive);
207 if (error != FILE_ERROR_OK)
208 return error;
209 HiddenEntryClassifier hidden_entry_classifier(resource_metadata,
210 mydrive.local_id());
212 // Iterate over entries.
213 scoped_ptr<ResourceMetadata::Iterator> it = resource_metadata->GetIterator();
214 for (; !it->IsAtEnd(); it->Advance()) {
215 FileError error = MaybeAddEntryToResult(
216 resource_metadata, it.get(), query_text.empty() ? NULL : &query,
217 predicate, at_most_num_matches, &hidden_entry_classifier,
218 &result_candidates);
219 if (error != FILE_ERROR_OK)
220 return error;
223 // Prepare the result.
224 for (; !result_candidates.empty(); result_candidates.pop()) {
225 const ResultCandidate& candidate = *result_candidates.top();
226 // The path field of entries in result_candidates are empty at this point,
227 // because we don't want to run the expensive metadata DB look up except for
228 // the final results. Hence, here we fill the part.
229 base::FilePath path;
230 error = resource_metadata->GetFilePath(candidate.local_id, &path);
231 if (error != FILE_ERROR_OK)
232 return error;
233 bool is_directory = candidate.entry.file_info().is_directory();
234 results->push_back(MetadataSearchResult(
235 path, is_directory, candidate.highlighted_base_name,
236 candidate.entry.file_specific_info().md5()));
239 // Reverse the order here because |result_candidates| puts the most
240 // uninteresting candidate at the top.
241 std::reverse(results->begin(), results->end());
243 return FILE_ERROR_OK;
246 // Runs the SearchMetadataCallback and updates the histogram.
247 void RunSearchMetadataCallback(const SearchMetadataCallback& callback,
248 const base::TimeTicks& start_time,
249 scoped_ptr<MetadataSearchResultVector> results,
250 FileError error) {
251 if (error != FILE_ERROR_OK)
252 results.reset();
253 callback.Run(error, results.Pass());
255 UMA_HISTOGRAM_TIMES("Drive.SearchMetadataTime",
256 base::TimeTicks::Now() - start_time);
259 } // namespace
261 void SearchMetadata(
262 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
263 ResourceMetadata* resource_metadata,
264 const std::string& query,
265 const SearchMetadataPredicate& predicate,
266 size_t at_most_num_matches,
267 const SearchMetadataCallback& callback) {
268 DCHECK_CURRENTLY_ON(BrowserThread::UI);
269 DCHECK(!callback.is_null());
271 const base::TimeTicks start_time = base::TimeTicks::Now();
273 scoped_ptr<MetadataSearchResultVector> results(
274 new MetadataSearchResultVector);
275 MetadataSearchResultVector* results_ptr = results.get();
276 base::PostTaskAndReplyWithResult(
277 blocking_task_runner.get(), FROM_HERE,
278 base::Bind(&SearchMetadataOnBlockingPool, resource_metadata, query,
279 predicate, at_most_num_matches, results_ptr),
280 base::Bind(&RunSearchMetadataCallback, callback, start_time,
281 base::Passed(&results)));
284 bool MatchesType(int options, const ResourceEntry& entry) {
285 if ((options & SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS) &&
286 entry.file_specific_info().is_hosted_document())
287 return false;
289 if ((options & SEARCH_METADATA_EXCLUDE_DIRECTORIES) &&
290 entry.file_info().is_directory())
291 return false;
293 if (options & SEARCH_METADATA_SHARED_WITH_ME)
294 return entry.shared_with_me();
296 if (options & SEARCH_METADATA_OFFLINE) {
297 if (entry.file_specific_info().is_hosted_document()) {
298 // Not all hosted documents are cached by Drive offline app.
299 // http://support.google.com/drive/bin/answer.py?hl=en&answer=1628467
300 std::string mime_type = entry.file_specific_info().content_mime_type();
301 return mime_type == drive::util::kGoogleDocumentMimeType ||
302 mime_type == drive::util::kGoogleSpreadsheetMimeType ||
303 mime_type == drive::util::kGooglePresentationMimeType ||
304 mime_type == drive::util::kGoogleDrawingMimeType;
305 } else {
306 return entry.file_specific_info().cache_state().is_present();
310 return true;
313 bool FindAndHighlight(
314 const std::string& text,
315 base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents* query,
316 std::string* highlighted_text) {
317 DCHECK(query);
318 DCHECK(highlighted_text);
319 highlighted_text->clear();
321 base::string16 text16 = base::UTF8ToUTF16(text);
322 size_t match_start = 0;
323 size_t match_length = 0;
324 if (!query->Search(text16, &match_start, &match_length))
325 return false;
327 base::string16 pre = text16.substr(0, match_start);
328 base::string16 match = text16.substr(match_start, match_length);
329 base::string16 post = text16.substr(match_start + match_length);
330 highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(pre)));
331 highlighted_text->append("<b>");
332 highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(match)));
333 highlighted_text->append("</b>");
334 highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(post)));
335 return true;
338 } // namespace internal
339 } // namespace drive