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
10using base::Time;
11using base::TimeDelta;
12
13const TimeDelta ThumbnailScore::kUpdateThumbnailTime = TimeDelta::FromDays(1);
14const double ThumbnailScore::kThumbnailMaximumBoringness = 0.94;
15// Per crbug.com/65936#c4, 91.83% of thumbnail scores are less than 0.70.
16const double ThumbnailScore::kThumbnailInterestingEnoughBoringness = 0.70;
17const 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.
22static 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
37ThumbnailScore::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
45ThumbnailScore::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
53ThumbnailScore::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
62ThumbnailScore::~ThumbnailScore() {
63}
64
65bool 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
76std::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
86bool 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
133bool 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