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/common/thumbnail_score.h" 6 7#include "base/logging.h" 8#include "base/strings/stringprintf.h" 9 10using base::Time; 11using base::TimeDelta; 12 13const int64 ThumbnailScore::kUpdateThumbnailTimeDays = 1; 14const double ThumbnailScore::kThumbnailMaximumBoringness = 0.94; 15const double ThumbnailScore::kThumbnailDegradePerHour = 0.01; 16const double ThumbnailScore::kTooWideAspectRatio = 2.0; 17 18// Calculates a numeric score from traits about where a snapshot was 19// taken. The lower the better. We store the raw components in the 20// database because I'm sure this will evolve and I don't want to break 21// databases. 22static int GetThumbnailType(const ThumbnailScore& score) { 23 int type = 0; 24 if (!score.at_top) 25 type += 1; 26 if (!score.good_clipping) 27 type += 2; 28 if (!score.load_completed) 29 type += 3; 30 return type; 31} 32 33ThumbnailScore::ThumbnailScore() 34 : boring_score(1.0), 35 good_clipping(false), 36 at_top(false), 37 load_completed(false), 38 time_at_snapshot(Time::Now()), 39 redirect_hops_from_dest(0) { 40} 41 42ThumbnailScore::ThumbnailScore(double score, bool clipping, bool top) 43 : boring_score(score), 44 good_clipping(clipping), 45 at_top(top), 46 load_completed(false), 47 time_at_snapshot(Time::Now()), 48 redirect_hops_from_dest(0) { 49} 50 51ThumbnailScore::ThumbnailScore(double score, bool clipping, bool top, 52 const Time& time) 53 : boring_score(score), 54 good_clipping(clipping), 55 at_top(top), 56 load_completed(false), 57 time_at_snapshot(time), 58 redirect_hops_from_dest(0) { 59} 60 61ThumbnailScore::~ThumbnailScore() { 62} 63 64bool ThumbnailScore::Equals(const ThumbnailScore& rhs) const { 65 return boring_score == rhs.boring_score && 66 good_clipping == rhs.good_clipping && 67 at_top == rhs.at_top && 68 time_at_snapshot == rhs.time_at_snapshot && 69 redirect_hops_from_dest == rhs.redirect_hops_from_dest; 70} 71 72std::string ThumbnailScore::ToString() const { 73 return base::StringPrintf( 74 "boring_score: %f, at_top %d, good_clipping %d, " 75 "load_completed: %d, " 76 "time_at_snapshot: %f, redirect_hops_from_dest: %d", 77 boring_score, 78 at_top, 79 good_clipping, 80 load_completed, 81 time_at_snapshot.ToDoubleT(), 82 redirect_hops_from_dest); 83} 84 85bool ShouldReplaceThumbnailWith(const ThumbnailScore& current, 86 const ThumbnailScore& replacement) { 87 int current_type = GetThumbnailType(current); 88 int replacement_type = GetThumbnailType(replacement); 89 if (replacement_type < current_type) { 90 // If we have a better class of thumbnail, add it if it meets 91 // certain minimum boringness. 92 return replacement.boring_score < 93 ThumbnailScore::kThumbnailMaximumBoringness; 94 } else if (replacement_type == current_type) { 95 // It's much easier to do the scaling below when we're dealing with "higher 96 // is better." Then we can decrease the score by dividing by a fraction. 97 const double kThumbnailMinimumInterestingness = 98 1.0 - ThumbnailScore::kThumbnailMaximumBoringness; 99 double current_interesting_score = 1.0 - current.boring_score; 100 double replacement_interesting_score = 1.0 - replacement.boring_score; 101 102 // Degrade the score of each thumbnail to account for how many redirects 103 // they are away from the destination. 1/(x+1) gives a scaling factor of 104 // one for x = 0, and asymptotically approaches 0 for larger values of x. 105 current_interesting_score *= 106 1.0 / (current.redirect_hops_from_dest + 1); 107 replacement_interesting_score *= 108 1.0 / (replacement.redirect_hops_from_dest + 1); 109 110 // Degrade the score and prefer the newer one based on how long apart the 111 // two thumbnails were taken. This means we'll eventually replace an old 112 // good one with a new worse one assuming enough time has passed. 113 TimeDelta time_between_thumbnails = 114 replacement.time_at_snapshot - current.time_at_snapshot; 115 current_interesting_score -= time_between_thumbnails.InHours() * 116 ThumbnailScore::kThumbnailDegradePerHour; 117 118 if (current_interesting_score < kThumbnailMinimumInterestingness) 119 current_interesting_score = kThumbnailMinimumInterestingness; 120 if (replacement_interesting_score > current_interesting_score) 121 return true; 122 } 123 124 // If the current thumbnail doesn't meet basic boringness 125 // requirements, but the replacement does, always replace the 126 // current one even if we're using a worse thumbnail type. 127 return current.boring_score >= ThumbnailScore::kThumbnailMaximumBoringness && 128 replacement.boring_score < ThumbnailScore::kThumbnailMaximumBoringness; 129} 130 131bool ThumbnailScore::ShouldConsiderUpdating() { 132 const TimeDelta time_elapsed = Time::Now() - time_at_snapshot; 133 if (time_elapsed < TimeDelta::FromDays(kUpdateThumbnailTimeDays) && 134 good_clipping && at_top && load_completed) { 135 // The current thumbnail is new and has good properties. 136 return false; 137 } 138 // The current thumbnail should be updated. 139 return true; 140} 141