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.
GetThumbnailType(bool good_clipping,bool at_top)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
ThumbnailScore()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
ThumbnailScore(double score,bool clipping,bool top)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
ThumbnailScore(double score,bool clipping,bool top,const Time & time)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
~ThumbnailScore()62 ThumbnailScore::~ThumbnailScore() {
63 }
64
Equals(const ThumbnailScore & rhs) const65 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
ToString() const76 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
ShouldReplaceThumbnailWith(const ThumbnailScore & current,const ThumbnailScore & replacement)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
ShouldConsiderUpdating()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