Home | History | Annotate | Download | only in drive
      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