1 // Copyright 2014 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 "components/history/core/common/thumbnail_score.h" 6 7 #include "base/logging.h" 8 #include "base/strings/stringprintf.h" 9 10 using base::Time; 11 using base::TimeDelta; 12 13 const int64 ThumbnailScore::kUpdateThumbnailTimeDays = 1; 14 const double ThumbnailScore::kThumbnailMaximumBoringness = 0.94; 15 const double ThumbnailScore::kThumbnailDegradePerHour = 0.01; 16 const 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. 22 static 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 33 ThumbnailScore::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 42 ThumbnailScore::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 51 ThumbnailScore::ThumbnailScore(double score, 52 bool clipping, 53 bool top, 54 const Time& time) 55 : boring_score(score), 56 good_clipping(clipping), 57 at_top(top), 58 load_completed(false), 59 time_at_snapshot(time), 60 redirect_hops_from_dest(0) { 61 } 62 63 ThumbnailScore::~ThumbnailScore() { 64 } 65 66 bool ThumbnailScore::Equals(const ThumbnailScore& rhs) const { 67 return boring_score == rhs.boring_score && 68 good_clipping == rhs.good_clipping && at_top == rhs.at_top && 69 time_at_snapshot == rhs.time_at_snapshot && 70 redirect_hops_from_dest == rhs.redirect_hops_from_dest; 71 } 72 73 std::string ThumbnailScore::ToString() const { 74 return base::StringPrintf( 75 "boring_score: %f, at_top %d, good_clipping %d, " 76 "load_completed: %d, " 77 "time_at_snapshot: %f, redirect_hops_from_dest: %d", 78 boring_score, 79 at_top, 80 good_clipping, 81 load_completed, 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); 89 int replacement_type = GetThumbnailType(replacement); 90 if (replacement_type < current_type) { 91 // If we have a better class of thumbnail, add it if it meets 92 // certain minimum boringness. 93 return replacement.boring_score < 94 ThumbnailScore::kThumbnailMaximumBoringness; 95 } else if (replacement_type == current_type) { 96 // It's much easier to do the scaling below when we're dealing with "higher 97 // is better." Then we can decrease the score by dividing by a fraction. 98 const double kThumbnailMinimumInterestingness = 99 1.0 - ThumbnailScore::kThumbnailMaximumBoringness; 100 double current_interesting_score = 1.0 - current.boring_score; 101 double replacement_interesting_score = 1.0 - replacement.boring_score; 102 103 // Degrade the score of each thumbnail to account for how many redirects 104 // they are away from the destination. 1/(x+1) gives a scaling factor of 105 // one for x = 0, and asymptotically approaches 0 for larger values of x. 106 current_interesting_score *= 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 131 bool 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