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