download_query.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
1// Copyright (c) 2012 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/download/download_query.h"
6
7#include <algorithm>
8#include <string>
9#include <vector>
10
11#include "base/bind.h"
12#include "base/callback.h"
13#include "base/files/file_path.h"
14#include "base/i18n/case_conversion.h"
15#include "base/i18n/string_search.h"
16#include "base/logging.h"
17#include "base/memory/scoped_ptr.h"
18#include "base/prefs/pref_service.h"
19#include "base/stl_util.h"
20#include "base/strings/string16.h"
21#include "base/strings/string_split.h"
22#include "base/strings/stringprintf.h"
23#include "base/strings/utf_string_conversions.h"
24#include "base/time/time.h"
25#include "base/values.h"
26#include "chrome/browser/profiles/profile.h"
27#include "chrome/common/pref_names.h"
28#include "content/public/browser/content_browser_client.h"
29#include "content/public/browser/download_item.h"
30#include "net/base/net_util.h"
31#include "third_party/re2/re2/re2.h"
32#include "url/gurl.h"
33
34using content::DownloadDangerType;
35using content::DownloadItem;
36
37namespace {
38
39// Templatized base::Value::GetAs*().
40template <typename T> bool GetAs(const base::Value& in, T* out);
41template<> bool GetAs(const base::Value& in, bool* out) {
42  return in.GetAsBoolean(out);
43}
44template<> bool GetAs(const base::Value& in, int* out) {
45  return in.GetAsInteger(out);
46}
47template<> bool GetAs(const base::Value& in, std::string* out) {
48  return in.GetAsString(out);
49}
50template<> bool GetAs(const base::Value& in, string16* out) {
51  return in.GetAsString(out);
52}
53
54// The next several functions are helpers for making Callbacks that access
55// DownloadItem fields.
56
57static bool MatchesQuery(const string16& query, const DownloadItem& item) {
58  if (query.empty())
59    return true;
60
61  DCHECK_EQ(query, base::i18n::ToLower(query));
62
63  string16 url_raw(UTF8ToUTF16(item.GetOriginalUrl().spec()));
64  if (base::i18n::StringSearchIgnoringCaseAndAccents(
65          query, url_raw, NULL, NULL)) {
66    return true;
67  }
68
69  string16 url_formatted = url_raw;
70  if (item.GetBrowserContext()) {
71    Profile* profile = Profile::FromBrowserContext(item.GetBrowserContext());
72    url_formatted = net::FormatUrl(
73        item.GetOriginalUrl(),
74        profile->GetPrefs()->GetString(prefs::kAcceptLanguages));
75  }
76  if (base::i18n::StringSearchIgnoringCaseAndAccents(
77        query, url_formatted, NULL, NULL)) {
78    return true;
79  }
80
81  string16 path(item.GetTargetFilePath().LossyDisplayName());
82  return base::i18n::StringSearchIgnoringCaseAndAccents(
83      query, path, NULL, NULL);
84}
85
86static int64 GetStartTimeMsEpoch(const DownloadItem& item) {
87  return (item.GetStartTime() - base::Time::UnixEpoch()).InMilliseconds();
88}
89
90static int64 GetEndTimeMsEpoch(const DownloadItem& item) {
91  return (item.GetEndTime() - base::Time::UnixEpoch()).InMilliseconds();
92}
93
94std::string TimeToISO8601(const base::Time& t) {
95  base::Time::Exploded exploded;
96  t.UTCExplode(&exploded);
97  return base::StringPrintf(
98      "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", exploded.year, exploded.month,
99      exploded.day_of_month, exploded.hour, exploded.minute, exploded.second,
100      exploded.millisecond);
101}
102
103static std::string GetStartTime(const DownloadItem& item) {
104  return TimeToISO8601(item.GetStartTime());
105}
106
107static std::string GetEndTime(const DownloadItem& item) {
108  return TimeToISO8601(item.GetEndTime());
109}
110
111static bool GetDangerAccepted(const DownloadItem& item) {
112  return (item.GetDangerType() ==
113          content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED);
114}
115
116static bool GetExists(const DownloadItem& item) {
117  return !item.GetFileExternallyRemoved();
118}
119
120static string16 GetFilename(const DownloadItem& item) {
121  // This filename will be compared with strings that could be passed in by the
122  // user, who only sees LossyDisplayNames.
123  return item.GetTargetFilePath().LossyDisplayName();
124}
125
126static std::string GetFilenameUTF8(const DownloadItem& item) {
127  return UTF16ToUTF8(GetFilename(item));
128}
129
130static std::string GetUrl(const DownloadItem& item) {
131  return item.GetOriginalUrl().spec();
132}
133
134static DownloadItem::DownloadState GetState(const DownloadItem& item) {
135  return item.GetState();
136}
137
138static DownloadDangerType GetDangerType(const DownloadItem& item) {
139  return item.GetDangerType();
140}
141
142static int GetReceivedBytes(const DownloadItem& item) {
143  return item.GetReceivedBytes();
144}
145
146static int GetTotalBytes(const DownloadItem& item) {
147  return item.GetTotalBytes();
148}
149
150static std::string GetMimeType(const DownloadItem& item) {
151  return item.GetMimeType();
152}
153
154static bool IsPaused(const DownloadItem& item) {
155  return item.IsPaused();
156}
157
158enum ComparisonType {LT, EQ, GT};
159
160// Returns true if |item| matches the filter specified by |value|, |cmptype|,
161// and |accessor|. |accessor| is conceptually a function that takes a
162// DownloadItem and returns one of its fields, which is then compared to
163// |value|.
164template<typename ValueType>
165static bool FieldMatches(
166    const ValueType& value,
167    ComparisonType cmptype,
168    const base::Callback<ValueType(const DownloadItem&)>& accessor,
169    const DownloadItem& item) {
170  switch (cmptype) {
171    case LT: return accessor.Run(item) < value;
172    case EQ: return accessor.Run(item) == value;
173    case GT: return accessor.Run(item) > value;
174  }
175  NOTREACHED();
176  return false;
177}
178
179// Helper for building a Callback to FieldMatches<>().
180template <typename ValueType> DownloadQuery::FilterCallback BuildFilter(
181    const base::Value& value, ComparisonType cmptype,
182    ValueType (*accessor)(const DownloadItem&)) {
183  ValueType cpp_value;
184  if (!GetAs(value, &cpp_value)) return DownloadQuery::FilterCallback();
185  return base::Bind(&FieldMatches<ValueType>, cpp_value, cmptype,
186                    base::Bind(accessor));
187}
188
189// Returns true if |accessor.Run(item)| matches |pattern|.
190static bool FindRegex(
191    RE2* pattern,
192    const base::Callback<std::string(const DownloadItem&)>& accessor,
193    const DownloadItem& item) {
194  return RE2::PartialMatch(accessor.Run(item), *pattern);
195}
196
197// Helper for building a Callback to FindRegex().
198DownloadQuery::FilterCallback BuildRegexFilter(
199    const base::Value& regex_value,
200    std::string (*accessor)(const DownloadItem&)) {
201  std::string regex_str;
202  if (!GetAs(regex_value, &regex_str)) return DownloadQuery::FilterCallback();
203  scoped_ptr<RE2> pattern(new RE2(regex_str));
204  if (!pattern->ok()) return DownloadQuery::FilterCallback();
205  return base::Bind(&FindRegex, base::Owned(pattern.release()),
206                    base::Bind(accessor));
207}
208
209// Returns a ComparisonType to indicate whether a field in |left| is less than,
210// greater than or equal to the same field in |right|.
211template<typename ValueType>
212static ComparisonType Compare(
213    const base::Callback<ValueType(const DownloadItem&)>& accessor,
214    const DownloadItem& left, const DownloadItem& right) {
215  ValueType left_value = accessor.Run(left);
216  ValueType right_value = accessor.Run(right);
217  if (left_value > right_value) return GT;
218  if (left_value < right_value) return LT;
219  DCHECK_EQ(left_value, right_value);
220  return EQ;
221}
222
223}  // anonymous namespace
224
225DownloadQuery::DownloadQuery()
226  : limit_(kuint32max) {
227}
228
229DownloadQuery::~DownloadQuery() {
230}
231
232// AddFilter() pushes a new FilterCallback to filters_. Most FilterCallbacks are
233// Callbacks to FieldMatches<>(). Search() iterates over given DownloadItems,
234// discarding items for which any filter returns false. A DownloadQuery may have
235// zero or more FilterCallbacks.
236
237bool DownloadQuery::AddFilter(const DownloadQuery::FilterCallback& value) {
238  if (value.is_null()) return false;
239  filters_.push_back(value);
240  return true;
241}
242
243void DownloadQuery::AddFilter(DownloadItem::DownloadState state) {
244  AddFilter(base::Bind(&FieldMatches<DownloadItem::DownloadState>, state, EQ,
245      base::Bind(&GetState)));
246}
247
248void DownloadQuery::AddFilter(DownloadDangerType danger) {
249  AddFilter(base::Bind(&FieldMatches<DownloadDangerType>, danger, EQ,
250      base::Bind(&GetDangerType)));
251}
252
253bool DownloadQuery::AddFilter(DownloadQuery::FilterType type,
254                              const base::Value& value) {
255  switch (type) {
256    case FILTER_BYTES_RECEIVED:
257      return AddFilter(BuildFilter<int>(value, EQ, &GetReceivedBytes));
258    case FILTER_DANGER_ACCEPTED:
259      return AddFilter(BuildFilter<bool>(value, EQ, &GetDangerAccepted));
260    case FILTER_EXISTS:
261      return AddFilter(BuildFilter<bool>(value, EQ, &GetExists));
262    case FILTER_FILENAME:
263      return AddFilter(BuildFilter<string16>(value, EQ, &GetFilename));
264    case FILTER_FILENAME_REGEX:
265      return AddFilter(BuildRegexFilter(value, &GetFilenameUTF8));
266    case FILTER_MIME:
267      return AddFilter(BuildFilter<std::string>(value, EQ, &GetMimeType));
268    case FILTER_PAUSED:
269      return AddFilter(BuildFilter<bool>(value, EQ, &IsPaused));
270    case FILTER_QUERY: {
271      string16 query;
272      return GetAs(value, &query) &&
273             AddFilter(base::Bind(&MatchesQuery, query));
274    }
275    case FILTER_ENDED_AFTER:
276      return AddFilter(BuildFilter<std::string>(value, GT, &GetEndTime));
277    case FILTER_ENDED_BEFORE:
278      return AddFilter(BuildFilter<std::string>(value, LT, &GetEndTime));
279    case FILTER_END_TIME:
280      return AddFilter(BuildFilter<std::string>(value, EQ, &GetEndTime));
281    case FILTER_STARTED_AFTER:
282      return AddFilter(BuildFilter<std::string>(value, GT, &GetStartTime));
283    case FILTER_STARTED_BEFORE:
284      return AddFilter(BuildFilter<std::string>(value, LT, &GetStartTime));
285    case FILTER_START_TIME:
286      return AddFilter(BuildFilter<std::string>(value, EQ, &GetStartTime));
287    case FILTER_TOTAL_BYTES:
288      return AddFilter(BuildFilter<int>(value, EQ, &GetTotalBytes));
289    case FILTER_TOTAL_BYTES_GREATER:
290      return AddFilter(BuildFilter<int>(value, GT, &GetTotalBytes));
291    case FILTER_TOTAL_BYTES_LESS:
292      return AddFilter(BuildFilter<int>(value, LT, &GetTotalBytes));
293    case FILTER_URL:
294      return AddFilter(BuildFilter<std::string>(value, EQ, &GetUrl));
295    case FILTER_URL_REGEX:
296      return AddFilter(BuildRegexFilter(value, &GetUrl));
297  }
298  return false;
299}
300
301bool DownloadQuery::Matches(const DownloadItem& item) const {
302  for (FilterCallbackVector::const_iterator filter = filters_.begin();
303        filter != filters_.end(); ++filter) {
304    if (!filter->Run(item))
305      return false;
306  }
307  return true;
308}
309
310// AddSorter() creates a Sorter and pushes it onto sorters_. A Sorter is a
311// direction and a Callback to Compare<>(). After filtering, Search() makes a
312// DownloadComparator functor from the sorters_ and passes the
313// DownloadComparator to std::partial_sort. std::partial_sort calls the
314// DownloadComparator with different pairs of DownloadItems.  DownloadComparator
315// iterates over the sorters until a callback returns ComparisonType LT or GT.
316// DownloadComparator returns true or false depending on that ComparisonType and
317// the sorter's direction in order to indicate to std::partial_sort whether the
318// left item is after or before the right item. If all sorters return EQ, then
319// DownloadComparator compares GetId. A DownloadQuery may have zero or more
320// Sorters, but there is one DownloadComparator per call to Search().
321
322struct DownloadQuery::Sorter {
323  typedef base::Callback<ComparisonType(
324      const DownloadItem&, const DownloadItem&)> SortType;
325
326  template<typename ValueType>
327  static Sorter Build(DownloadQuery::SortDirection adirection,
328                         ValueType (*accessor)(const DownloadItem&)) {
329    return Sorter(adirection, base::Bind(&Compare<ValueType>,
330        base::Bind(accessor)));
331  }
332
333  Sorter(DownloadQuery::SortDirection adirection,
334            const SortType& asorter)
335    : direction(adirection),
336      sorter(asorter) {
337  }
338  ~Sorter() {}
339
340  DownloadQuery::SortDirection direction;
341  SortType sorter;
342};
343
344class DownloadQuery::DownloadComparator {
345 public:
346  explicit DownloadComparator(const DownloadQuery::SorterVector& terms)
347    : terms_(terms) {
348  }
349
350  // Returns true if |left| sorts before |right|.
351  bool operator() (const DownloadItem* left, const DownloadItem* right);
352
353 private:
354  const DownloadQuery::SorterVector& terms_;
355
356  // std::sort requires this class to be copyable.
357};
358
359bool DownloadQuery::DownloadComparator::operator() (
360    const DownloadItem* left, const DownloadItem* right) {
361  for (DownloadQuery::SorterVector::const_iterator term = terms_.begin();
362       term != terms_.end(); ++term) {
363    switch (term->sorter.Run(*left, *right)) {
364      case LT: return term->direction == DownloadQuery::ASCENDING;
365      case GT: return term->direction == DownloadQuery::DESCENDING;
366      case EQ: break;  // break the switch but not the loop
367    }
368  }
369  CHECK_NE(left->GetId(), right->GetId());
370  return left->GetId() < right->GetId();
371}
372
373void DownloadQuery::AddSorter(DownloadQuery::SortType type,
374                              DownloadQuery::SortDirection direction) {
375  switch (type) {
376    case SORT_END_TIME:
377      sorters_.push_back(Sorter::Build<int64>(direction, &GetEndTimeMsEpoch));
378      break;
379    case SORT_START_TIME:
380      sorters_.push_back(Sorter::Build<int64>(direction, &GetStartTimeMsEpoch));
381      break;
382    case SORT_URL:
383      sorters_.push_back(Sorter::Build<std::string>(direction, &GetUrl));
384      break;
385    case SORT_FILENAME:
386      sorters_.push_back(Sorter::Build<string16>(direction, &GetFilename));
387      break;
388    case SORT_DANGER:
389      sorters_.push_back(Sorter::Build<DownloadDangerType>(
390          direction, &GetDangerType));
391      break;
392    case SORT_DANGER_ACCEPTED:
393      sorters_.push_back(Sorter::Build<bool>(direction, &GetDangerAccepted));
394      break;
395    case SORT_EXISTS:
396      sorters_.push_back(Sorter::Build<bool>(direction, &GetExists));
397      break;
398    case SORT_STATE:
399      sorters_.push_back(Sorter::Build<DownloadItem::DownloadState>(
400          direction, &GetState));
401      break;
402    case SORT_PAUSED:
403      sorters_.push_back(Sorter::Build<bool>(direction, &IsPaused));
404      break;
405    case SORT_MIME:
406      sorters_.push_back(Sorter::Build<std::string>(direction, &GetMimeType));
407      break;
408    case SORT_BYTES_RECEIVED:
409      sorters_.push_back(Sorter::Build<int>(direction, &GetReceivedBytes));
410      break;
411    case SORT_TOTAL_BYTES:
412      sorters_.push_back(Sorter::Build<int>(direction, &GetTotalBytes));
413      break;
414  }
415}
416
417void DownloadQuery::FinishSearch(DownloadQuery::DownloadVector* results) const {
418  if (!sorters_.empty())
419    std::partial_sort(results->begin(),
420                      results->begin() + std::min(limit_, results->size()),
421                      results->end(),
422                      DownloadComparator(sorters_));
423  if (results->size() > limit_)
424    results->resize(limit_);
425}
426