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