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, ®ex_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