summaryrefslogtreecommitdiffstats
path: root/chrome/common/thumbnail_score.cc
blob: 5d3d698f1a8c23e2ea412eeac2aa0b70e5712a73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/common/thumbnail_score.h"

#include "base/logging.h"
#include "base/strings/stringprintf.h"

using base::Time;
using base::TimeDelta;

const int64 ThumbnailScore::kUpdateThumbnailTimeDays = 1;
const double ThumbnailScore::kThumbnailMaximumBoringness = 0.94;
const double ThumbnailScore::kThumbnailDegradePerHour = 0.01;
const double ThumbnailScore::kTooWideAspectRatio = 2.0;

// Calculates a numeric score from traits about where a snapshot was
// taken. The lower the better. We store the raw components in the
// database because I'm sure this will evolve and I don't want to break
// databases.
static int GetThumbnailType(const ThumbnailScore& score) {
  int type = 0;
  if (!score.at_top)
    type += 1;
  if (!score.good_clipping)
    type += 2;
  if (!score.load_completed)
    type += 3;
  return type;
}

ThumbnailScore::ThumbnailScore()
    : boring_score(1.0),
      good_clipping(false),
      at_top(false),
      load_completed(false),
      time_at_snapshot(Time::Now()),
      redirect_hops_from_dest(0) {
}

ThumbnailScore::ThumbnailScore(double score, bool clipping, bool top)
    : boring_score(score),
      good_clipping(clipping),
      at_top(top),
      load_completed(false),
      time_at_snapshot(Time::Now()),
      redirect_hops_from_dest(0) {
}

ThumbnailScore::ThumbnailScore(double score, bool clipping, bool top,
                               const Time& time)
    : boring_score(score),
      good_clipping(clipping),
      at_top(top),
      load_completed(false),
      time_at_snapshot(time),
      redirect_hops_from_dest(0) {
}

ThumbnailScore::~ThumbnailScore() {
}

bool ThumbnailScore::Equals(const ThumbnailScore& rhs) const {
  return boring_score == rhs.boring_score &&
      good_clipping == rhs.good_clipping &&
      at_top == rhs.at_top &&
      time_at_snapshot == rhs.time_at_snapshot &&
      redirect_hops_from_dest == rhs.redirect_hops_from_dest;
}

std::string ThumbnailScore::ToString() const {
  return base::StringPrintf(
      "boring_score: %f, at_top %d, good_clipping %d, "
      "load_completed: %d, "
      "time_at_snapshot: %f, redirect_hops_from_dest: %d",
      boring_score,
      at_top,
      good_clipping,
      load_completed,
      time_at_snapshot.ToDoubleT(),
      redirect_hops_from_dest);
}

bool ShouldReplaceThumbnailWith(const ThumbnailScore& current,
                                const ThumbnailScore& replacement) {
  int current_type = GetThumbnailType(current);
  int replacement_type = GetThumbnailType(replacement);
  if (replacement_type < current_type) {
    // If we have a better class of thumbnail, add it if it meets
    // certain minimum boringness.
    return replacement.boring_score <
        ThumbnailScore::kThumbnailMaximumBoringness;
  } else if (replacement_type == current_type) {
    // It's much easier to do the scaling below when we're dealing with "higher
    // is better." Then we can decrease the score by dividing by a fraction.
    const double kThumbnailMinimumInterestingness =
        1.0 - ThumbnailScore::kThumbnailMaximumBoringness;
    double current_interesting_score = 1.0 - current.boring_score;
    double replacement_interesting_score = 1.0 - replacement.boring_score;

    // Degrade the score of each thumbnail to account for how many redirects
    // they are away from the destination. 1/(x+1) gives a scaling factor of
    // one for x = 0, and asymptotically approaches 0 for larger values of x.
    current_interesting_score *=
        1.0 / (current.redirect_hops_from_dest + 1);
    replacement_interesting_score *=
        1.0 / (replacement.redirect_hops_from_dest + 1);

    // Degrade the score and prefer the newer one based on how long apart the
    // two thumbnails were taken. This means we'll eventually replace an old
    // good one with a new worse one assuming enough time has passed.
    TimeDelta time_between_thumbnails =
        replacement.time_at_snapshot - current.time_at_snapshot;
    current_interesting_score -= time_between_thumbnails.InHours() *
         ThumbnailScore::kThumbnailDegradePerHour;

    if (current_interesting_score < kThumbnailMinimumInterestingness)
      current_interesting_score = kThumbnailMinimumInterestingness;
    if (replacement_interesting_score > current_interesting_score)
      return true;
  }

  // If the current thumbnail doesn't meet basic boringness
  // requirements, but the replacement does, always replace the
  // current one even if we're using a worse thumbnail type.
  return current.boring_score >= ThumbnailScore::kThumbnailMaximumBoringness &&
      replacement.boring_score < ThumbnailScore::kThumbnailMaximumBoringness;
}

bool ThumbnailScore::ShouldConsiderUpdating() {
  const TimeDelta time_elapsed = Time::Now() - time_at_snapshot;
  if (time_elapsed < TimeDelta::FromDays(kUpdateThumbnailTimeDays) &&
      good_clipping && at_top && load_completed) {
    // The current thumbnail is new and has good properties.
    return false;
  }
  // The current thumbnail should be updated.
  return true;
}