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