// Copyright (c) 2009 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/stringprintf.h"
using base::Time;
using base::TimeDelta;
const TimeDelta ThumbnailScore::kUpdateThumbnailTime = TimeDelta::FromDays(1);
const double ThumbnailScore::kThumbnailMaximumBoringness = 0.94;
// Per crbug.com/65936#c4, 91.83% of thumbnail scores are less than 0.70.
const double ThumbnailScore::kThumbnailInterestingEnoughBoringness = 0.70;
const double ThumbnailScore::kThumbnailDegradePerHour = 0.01;
// Calculates a numeric score from traits about where a snapshot was
// taken. 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(bool good_clipping, bool at_top) {
if (good_clipping && at_top) {
return 0;
} else if (good_clipping && !at_top) {
return 1;
} else if (!good_clipping && at_top) {
return 2;
} else if (!good_clipping && !at_top) {
return 3;
} else {
NOTREACHED();
return -1;
}
}
ThumbnailScore::ThumbnailScore()
: boring_score(1.0),
good_clipping(false),
at_top(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),
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),
time_at_snapshot(time),
redirect_hops_from_dest(0) {
}
ThumbnailScore::~ThumbnailScore() {
}
bool ThumbnailScore::Equals(const ThumbnailScore& rhs) const {
// When testing equality we use ToTimeT() because that's the value
// stuck in the SQL database, so we need to test equivalence with
// that lower resolution.
return boring_score == rhs.boring_score &&
good_clipping == rhs.good_clipping &&
at_top == rhs.at_top &&
time_at_snapshot.ToTimeT() == rhs.time_at_snapshot.ToTimeT() &&
redirect_hops_from_dest == rhs.redirect_hops_from_dest;
}
std::string ThumbnailScore::ToString() const {
return StringPrintf("boring_score: %f, at_top %d, good_clipping %d, "
"time_at_snapshot: %f, redirect_hops_from_dest: %d",
boring_score,
at_top,
good_clipping,
time_at_snapshot.ToDoubleT(),
redirect_hops_from_dest);
}
bool ShouldReplaceThumbnailWith(const ThumbnailScore& current,
const ThumbnailScore& replacement) {
int current_type = GetThumbnailType(current.good_clipping, current.at_top);
int replacement_type = GetThumbnailType(replacement.good_clipping,
replacement.at_top);
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;
// Consider the current thumbnail to be new and interesting enough if
// the following critera are met.
const bool new_and_interesting_enough =
(time_elapsed < kUpdateThumbnailTime &&
good_clipping && at_top &&
boring_score < kThumbnailInterestingEnoughBoringness);
// We want to generate a new thumbnail when the current thumbnail is
// sufficiently old or uninteresting.
return !new_and_interesting_enough;
}