Chromium Code Reviews| Index: content/browser/download/download_query.cc |
| diff --git a/content/browser/download/download_query.cc b/content/browser/download/download_query.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..8952af46ea4ea555a7dc80ad2678d23a15217ede |
| --- /dev/null |
| +++ b/content/browser/download/download_query.cc |
| @@ -0,0 +1,377 @@ |
| +// Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "content/browser/download/download_query.h" |
| + |
| +#include <algorithm> |
| +#include <string> |
| +#include <vector> |
| + |
| +#include "base/bind.h" |
| +#include "base/callback.h" |
| +#include "base/logging.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "base/stl_util.h" |
| +#include "base/string16.h" |
| +#include "base/string_split.h" |
| +#include "base/time.h" |
| +#include "base/utf_string_conversions.h" |
| +#include "base/values.h" |
| +#include "content/browser/download/download_item.h" |
| +#include "googleurl/src/gurl.h" |
| +#include "unicode/regex.h" |
| + |
| +namespace { |
| + |
| +// Templatized base::Value::GetAs*(). |
| +template <typename T> bool GetAs(const base::Value& in, T* out); |
| +template<> bool GetAs(const base::Value& in, bool* out) { |
| + return in.GetAsBoolean(out); |
| +} |
| +template<> bool GetAs(const base::Value& in, int* out) { |
| + return in.GetAsInteger(out); |
| +} |
| +template<> bool GetAs(const base::Value& in, std::string* out) { |
| + return in.GetAsString(out); |
| +} |
| +template<> bool GetAs(const base::Value& in, string16* out) { |
| + return in.GetAsString(out); |
| +} |
| + |
| +// The next several functions are helpers for making Callbacks that access |
| +// DownloadItem fields. |
| + |
| +static bool MatchesQuery(const string16& value, const DownloadItem& item) { |
| + return item.MatchesQuery(value); |
| +} |
| + |
| +static int GetStartTime(const DownloadItem& item) { |
| + return (item.GetStartTime() - base::Time::UnixEpoch()).InMilliseconds(); |
| +} |
| + |
| +static bool GetDangerAccepted(const DownloadItem& item) { |
| + return (item.GetSafetyState() == DownloadItem::DANGEROUS_BUT_VALIDATED); |
| +} |
| + |
| +static string16 GetFilename(const DownloadItem& item) { |
| + // This filename will be compared with strings that could be passed in by the |
| + // user, who only sees LossyDisplayNames. |
| + return item.GetFullPath().LossyDisplayName(); |
| +} |
| + |
| +static std::string GetFilenameUTF8(const DownloadItem& item) { |
| + return UTF16ToUTF8(GetFilename(item)); |
| +} |
| + |
| +static std::string GetUrl(const DownloadItem& item) { |
| + return item.GetOriginalUrl().spec(); |
| +} |
| + |
| +static DownloadItem::DownloadState GetState(const DownloadItem& item) { |
| + return item.GetState(); |
| +} |
| + |
| +static DownloadStateInfo::DangerType GetDangerType(const DownloadItem& item) { |
| + return item.GetDangerType(); |
| +} |
| + |
| +static int GetReceivedBytes(const DownloadItem& item) { |
| + return item.GetReceivedBytes(); |
| +} |
| + |
| +static int GetTotalBytes(const DownloadItem& item) { |
| + return item.GetTotalBytes(); |
| +} |
| + |
| +static std::string GetMimeType(const DownloadItem& item) { |
| + return item.GetMimeType(); |
| +} |
| + |
| +static bool IsPaused(const DownloadItem& item) { |
| + return item.IsPaused(); |
| +} |
| + |
| +// Wrap Callback to work around a bug in base::Bind/Callback where the inner |
| +// callback is nullified when the outer callback is Run. |
| +template<typename ValueType> |
| +class InnerCallback { |
| + public: |
| + typedef base::Callback<ValueType(const DownloadItem&)> CallbackType; |
| + |
| + explicit InnerCallback(const CallbackType& inner) : inner_(inner) {} |
| + ~InnerCallback() {} |
| + |
| + // Mimic Callback's interface to facilitate removing InnerCallback when the |
| + // bug is fixed. |
| + ValueType Run(const DownloadItem& item) const { return inner_.Run(item); } |
| + |
| + private: |
| + CallbackType inner_; |
| +}; |
| + |
| +enum ComparisonType {LT, EQ, GT}; |
| + |
| +// Returns true if |item| matches the filter specified by |value|, |cmptype|, |
| +// and |accessor|. |accessor| is conceptually a function that takes a |
| +// DownloadItem and returns one of its fields, which is then compared to |
| +// |value|. |
| +template<typename ValueType> |
| +static bool FieldMatches( |
| + const ValueType& value, |
| + ComparisonType cmptype, |
| + const InnerCallback<ValueType>& accessor, |
| + const DownloadItem& item) { |
| + switch (cmptype) { |
| + case LT: return accessor.Run(item) < value; |
| + case EQ: return accessor.Run(item) == value; |
| + case GT: return accessor.Run(item) > value; |
| + } |
| + NOTREACHED(); |
| + return false; |
| +} |
| + |
| +// Helper for building a Callback to FieldMatches<>(). |
| +template <typename ValueType> DownloadQuery::FilterCallback BuildFilter( |
| + const base::Value& value, ComparisonType cmptype, |
| + ValueType (*accessor)(const DownloadItem&)) { |
| + ValueType cpp_value; |
| + if (!GetAs(value, &cpp_value)) return DownloadQuery::FilterCallback(); |
| + return base::Bind(&FieldMatches<ValueType>, cpp_value, cmptype, |
| + InnerCallback<ValueType>(base::Bind(accessor))); |
| +} |
| + |
| +// Returns true if |accessor.Run(item)| matches |pattern|. |
| +static bool FindRegex( |
| + icu::RegexPattern* pattern, |
| + const InnerCallback<std::string>& accessor, |
| + const DownloadItem& item) { |
| + icu::UnicodeString input(accessor.Run(item).c_str()); |
| + UErrorCode status = U_ZERO_ERROR; |
| + scoped_ptr<icu::RegexMatcher> matcher(pattern->matcher(input, status)); |
| + return matcher->find(); |
| +} |
| + |
| +// Helper for building a Callback to FindRegex(). |
| +DownloadQuery::FilterCallback BuildRegexFilter( |
| + const base::Value& regex_value, |
| + std::string (*accessor)(const DownloadItem&)) { |
| + std::string regex_str; |
| + if (!GetAs(regex_value, ®ex_str)) return DownloadQuery::FilterCallback(); |
| + UParseError re_err; |
| + UErrorCode re_status = U_ZERO_ERROR; |
| + scoped_ptr<icu::RegexPattern> pattern(icu::RegexPattern::compile( |
| + icu::UnicodeString::fromUTF8(regex_str), re_err, re_status)); |
| + if (!U_SUCCESS(re_status)) return DownloadQuery::FilterCallback(); |
| + return base::Bind(&FindRegex, base::Owned(pattern.release()), |
| + InnerCallback<std::string>(base::Bind(accessor))); |
| +} |
| + |
| +// Returns a ComparisonType to indicate whether a field in |left| is less than, |
| +// greater than or equal to the same field in |right|. |
| +template<typename ValueType> |
| +static ComparisonType Compare( |
| + const InnerCallback<ValueType>& accessor, |
| + const DownloadItem& left, const DownloadItem& right) { |
| + ValueType left_value = accessor.Run(left); |
| + ValueType right_value = accessor.Run(right); |
| + if (left_value > right_value) return GT; |
| + if (left_value < right_value) return LT; |
| + DCHECK_EQ(left_value, right_value); |
| + return EQ; |
| +} |
| + |
| +} // anonymous namespace |
| + |
| +DownloadQuery::DownloadQuery() |
| + : limit_(kuint32max) { |
| +} |
| + |
| +DownloadQuery::~DownloadQuery() { |
| +} |
| + |
| +// AddFilter() pushes a new FilterCallback to filters_. Most FilterCallbacks are |
| +// Callbacks to FieldMatches<>(). Search() iterates over given DownloadItems, |
| +// discarding items for which any filter returns false. A DownloadQuery may have |
| +// zero or more FilterCallbacks. |
| + |
| +bool DownloadQuery::AddFilter(const DownloadQuery::FilterCallback& value) { |
| + if (value.is_null()) return false; |
| + filters_.push_back(value); |
| + return true; |
| +} |
| + |
| +void DownloadQuery::AddFilter(DownloadItem::DownloadState state) { |
| + AddFilter(base::Bind(&FieldMatches<DownloadItem::DownloadState>, state, EQ, |
| + InnerCallback<DownloadItem::DownloadState>(base::Bind(&GetState)))); |
| +} |
| + |
| +void DownloadQuery::AddFilter(DownloadStateInfo::DangerType danger) { |
| + AddFilter(base::Bind(&FieldMatches<DownloadStateInfo::DangerType>, danger, EQ, |
| + InnerCallback<DownloadStateInfo::DangerType>(base::Bind( |
| + &GetDangerType)))); |
| +} |
| + |
| +bool DownloadQuery::AddFilter(DownloadQuery::FilterType type, |
| + const base::Value& value) { |
| + switch (type) { |
| + case FILTER_BYTES_RECEIVED: |
| + return AddFilter(BuildFilter<int>(value, EQ, &GetReceivedBytes)); |
| + case FILTER_DANGER_ACCEPTED: |
| + return AddFilter(BuildFilter<bool>(value, EQ, &GetDangerAccepted)); |
| + case FILTER_FILENAME: |
| + return AddFilter(BuildFilter<string16>(value, EQ, &GetFilename)); |
| + case FILTER_FILENAME_REGEX: |
| + return AddFilter(BuildRegexFilter(value, &GetFilenameUTF8)); |
| + case FILTER_MIME: |
| + return AddFilter(BuildFilter<std::string>(value, EQ, &GetMimeType)); |
| + case FILTER_PAUSED: |
| + return AddFilter(BuildFilter<bool>(value, EQ, &IsPaused)); |
| + case FILTER_QUERY: { |
| + string16 query; |
| + return GetAs(value, &query) && |
| + AddFilter(base::Bind(&MatchesQuery, query)); |
| + } |
| + case FILTER_STARTED_AFTER: |
| + return AddFilter(BuildFilter<int>(value, GT, &GetStartTime)); |
| + case FILTER_STARTED_BEFORE: |
| + return AddFilter(BuildFilter<int>(value, LT, &GetStartTime)); |
| + case FILTER_START_TIME: |
| + return AddFilter(BuildFilter<int>(value, EQ, &GetStartTime)); |
| + case FILTER_TOTAL_BYTES: |
| + return AddFilter(BuildFilter<int>(value, EQ, &GetTotalBytes)); |
| + case FILTER_TOTAL_BYTES_GREATER: |
| + return AddFilter(BuildFilter<int>(value, GT, &GetTotalBytes)); |
| + case FILTER_TOTAL_BYTES_LESS: |
| + return AddFilter(BuildFilter<int>(value, LT, &GetTotalBytes)); |
| + case FILTER_URL: |
| + return AddFilter(BuildFilter<std::string>(value, EQ, &GetUrl)); |
| + case FILTER_URL_REGEX: |
| + return AddFilter(BuildRegexFilter(value, &GetUrl)); |
| + } |
| + return false; |
| +} |
| + |
| +bool DownloadQuery::Matches(const DownloadItem& item) const { |
| + for (FilterCallbackVector::const_iterator filter = filters_.begin(); |
| + filter != filters_.end(); ++filter) { |
| + if (!filter->Run(item)) |
| + return false; |
| + } |
| + return true; |
| +} |
| + |
| +// AddSorter() creates a Sorter and pushes it onto sorters_. A Sorter is a |
| +// direction and a Callback to Compare<>(). After filtering, Search() makes a |
| +// DownloadComparator functor from the sorters_ and passes the |
| +// DownloadComparator to std::partial_sort. std::partial_sort calls the |
| +// DownloadComparator with different pairs of DownloadItems. DownloadComparator |
| +// iterates over the sorters until a callback returns ComparisonType LT or GT. |
| +// DownloadComparator returns true or false depending on that ComparisonType and |
| +// the sorter's direction in order to indicate to std::partial_sort whether the |
| +// left item is after or before the right item. If all sorters return EQ, then |
| +// DownloadComparator compares GetId. A DownloadQuery may have zero or more |
| +// Sorters, but there is one DownloadComparator per call to Search(). |
| + |
| +struct DownloadQuery::Sorter { |
| + typedef base::Callback<ComparisonType( |
| + const DownloadItem&, const DownloadItem&)> SortType; |
| + |
| + template<typename ValueType> |
| + static Sorter Build(DownloadQuery::SortDirection adirection, |
| + ValueType (*accessor)(const DownloadItem&)) { |
| + return Sorter(adirection, base::Bind(&Compare<ValueType>, |
| + InnerCallback<ValueType>(base::Bind(accessor)))); |
| + } |
| + |
| + Sorter(DownloadQuery::SortDirection adirection, |
| + const SortType& asorter) |
| + : direction(adirection), |
| + sorter(asorter) { |
| + } |
| + ~Sorter() {} |
| + |
| + DownloadQuery::SortDirection direction; |
| + SortType sorter; |
| +}; |
| + |
| +class DownloadQuery::DownloadComparator { |
| + public: |
| + DownloadComparator(const DownloadQuery::SorterVector& terms) |
| + : terms_(terms) { |
| + } |
| + |
| + // Returns true if |left| sorts before |right|. |
| + bool operator() (const DownloadItem* left, const DownloadItem* right); |
| + |
| + private: |
| + const DownloadQuery::SorterVector& terms_; |
| + |
| + // std::sort requires this class to be copyable. |
| +}; |
| + |
| +bool DownloadQuery::DownloadComparator::operator() ( |
| + const DownloadItem* left, const DownloadItem* right) { |
| + for (DownloadQuery::SorterVector::const_iterator term = terms_.begin(); |
| + term != terms_.end(); ++term) { |
| + switch (term->sorter.Run(*left, *right)) { |
| + case LT: return term->direction == DownloadQuery::ASCENDING; |
| + case GT: return term->direction == DownloadQuery::DESCENDING; |
| + case EQ: break; // break the switch but not the loop |
| + } |
| + } |
| + CHECK_NE(left->GetId(), right->GetId()); |
| + return left->GetId() < right->GetId(); |
| +} |
| + |
| +void DownloadQuery::AddSorter(DownloadQuery::SortType type, |
| + DownloadQuery::SortDirection direction) { |
| + switch (type) { |
| + case SORT_START_TIME: |
| + sorters_.push_back(Sorter::Build<int>(direction, &GetStartTime)); |
| + break; |
| + case SORT_URL: |
| + sorters_.push_back(Sorter::Build<std::string>(direction, &GetUrl)); |
| + break; |
| + case SORT_FILENAME: |
| + sorters_.push_back(Sorter::Build<string16>( |
| + direction, &GetFilename)); |
|
asanka
2011/12/13 16:17:12
Nit: indentation. Here and below.
benjhayden
2011/12/13 18:50:03
Done.
|
| + break; |
| + case SORT_DANGER: |
| + sorters_.push_back(Sorter::Build<DownloadStateInfo::DangerType>( |
| + direction, &GetDangerType)); |
| + break; |
| + case SORT_DANGER_ACCEPTED: |
| + sorters_.push_back(Sorter::Build<bool>( |
| + direction, &GetDangerAccepted)); |
| + break; |
| + case SORT_STATE: |
| + sorters_.push_back(Sorter::Build<DownloadItem::DownloadState>( |
| + direction, &GetState)); |
| + break; |
| + case SORT_PAUSED: |
| + sorters_.push_back(Sorter::Build<bool>(direction, &IsPaused)); |
| + break; |
| + case SORT_MIME: |
| + sorters_.push_back(Sorter::Build<std::string>( |
| + direction, &GetMimeType)); |
| + break; |
| + case SORT_BYTES_RECEIVED: |
| + sorters_.push_back(Sorter::Build<int>( |
| + direction, &GetReceivedBytes)); |
| + break; |
| + case SORT_TOTAL_BYTES: |
| + sorters_.push_back(Sorter::Build<int>(direction, &GetTotalBytes)); |
| + break; |
| + } |
| +} |
| + |
| +void DownloadQuery::FinishSearch(DownloadQuery::DownloadVector* results) const { |
| + if (!sorters_.empty()) |
| + std::partial_sort(results->begin(), |
| + results->begin() + std::min(limit_, results->size()), |
| + results->end(), |
| + DownloadComparator(sorters_)); |
| + if (results->size() > limit_) |
| + results->resize(limit_); |
| +} |