[go: nahoru, domu]

blob: 1a2f0e3a82a4e5463280f3c9db03f675fb2315d1 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include "chrome/browser/web_applications/generated_icon_fix_util.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/common/chrome_features.h"
#include "components/sync/base/time.h"
namespace web_app {
namespace generated_icon_fix_util {
namespace {
constexpr base::TimeDelta kFixWindowDuration = base::Days(7);
constexpr base::TimeDelta kFixAttemptThrottleDuration = base::Days(1);
// Capping the number of attempts should be redundant with the throttle + window
// but, because retries on failure are self propagating, have an explicit
// attempt count to be extra safe. Ordinarily a constraint like this would be
// CHECK'd but because these attempts run at start up it wouldn't be good for
// stability.
constexpr uint32_t kMaxAttemptCount =
kFixWindowDuration.IntDiv(kFixAttemptThrottleDuration);
static_assert(kMaxAttemptCount == 7u);
std::optional<base::Time> g_now_override_for_testing_;
base::Time Now() {
return g_now_override_for_testing_.value_or(base::Time::Now());
}
} // namespace
bool IsValid(const GeneratedIconFix& generated_icon_fix) {
return generated_icon_fix.has_source() &&
generated_icon_fix.source() != GeneratedIconFixSource_UNKNOWN &&
generated_icon_fix.has_window_start_time() &&
generated_icon_fix.has_attempt_count();
}
base::Value ToDebugValue(const GeneratedIconFix* generated_icon_fix) {
if (!generated_icon_fix) {
return base::Value();
}
base::Value::Dict debug_value;
debug_value.Set("source", base::ToString(generated_icon_fix->source()));
debug_value.Set("window_start_time",
base::ToString(syncer::ProtoTimeToTime(
generated_icon_fix->window_start_time())));
debug_value.Set("last_attempt_time",
generated_icon_fix->has_last_attempt_time()
? base::Value(base::ToString(syncer::ProtoTimeToTime(
generated_icon_fix->last_attempt_time())))
: base::Value());
debug_value.Set("attempt_count", base::saturated_cast<int>(
generated_icon_fix->attempt_count()));
return base::Value(std::move(debug_value));
}
void SetNowForTesting(base::Time now) {
g_now_override_for_testing_ = now;
}
bool HasRemainingAttempts(const WebApp& app) {
const std::optional<GeneratedIconFix>& generated_icon_fix =
app.generated_icon_fix();
if (!generated_icon_fix.has_value()) {
return true;
}
return generated_icon_fix->attempt_count() < kMaxAttemptCount;
}
bool IsWithinFixTimeWindow(const WebApp& app) {
const std::optional<GeneratedIconFix>& generated_icon_fix =
app.generated_icon_fix();
if (!generated_icon_fix.has_value()) {
return base::FeatureList::IsEnabled(
features::kWebAppSyncGeneratedIconRetroactiveFix);
}
base::TimeDelta duration_since_window_started =
Now() - syncer::ProtoTimeToTime(generated_icon_fix->window_start_time());
return duration_since_window_started <= kFixWindowDuration;
}
void EnsureFixTimeWindowStarted(WithAppResources& resources,
ScopedRegistryUpdate& update,
const webapps::AppId& app_id,
GeneratedIconFixSource source) {
if (resources.registrar()
.GetAppById(app_id)
->generated_icon_fix()
.has_value()) {
return;
}
update->UpdateApp(app_id)->SetGeneratedIconFix(
CreateInitialTimeWindow(source));
}
GeneratedIconFix CreateInitialTimeWindow(GeneratedIconFixSource source) {
GeneratedIconFix generated_icon_fix;
generated_icon_fix.set_source(source);
generated_icon_fix.set_window_start_time(syncer::TimeToProtoTime(Now()));
generated_icon_fix.set_attempt_count(0);
return generated_icon_fix;
}
base::TimeDelta GetThrottleDuration(const WebApp& app) {
const std::optional<GeneratedIconFix> generated_icon_fix =
app.generated_icon_fix();
if (!generated_icon_fix.has_value() ||
!generated_icon_fix->has_last_attempt_time()) {
return base::TimeDelta();
}
base::TimeDelta throttle_duration =
syncer::ProtoTimeToTime(generated_icon_fix->last_attempt_time()) +
kFixAttemptThrottleDuration - Now();
// Negative durations could cause us to skip ahead of other tasks already in
// the task queue when used in PostDelayedTask() so clamp to 0.
return throttle_duration.is_negative() ? base::TimeDelta()
: throttle_duration;
}
void RecordFixAttempt(WithAppResources& resources,
ScopedRegistryUpdate& update,
const webapps::AppId& app_id,
GeneratedIconFixSource source) {
EnsureFixTimeWindowStarted(resources, update, app_id, source);
WebApp* app = update->UpdateApp(app_id);
GeneratedIconFix generated_icon_fix = app->generated_icon_fix().value();
generated_icon_fix.set_attempt_count(generated_icon_fix.attempt_count() + 1);
generated_icon_fix.set_last_attempt_time(syncer::TimeToProtoTime(Now()));
app->SetGeneratedIconFix(std::move(generated_icon_fix));
}
} // namespace generated_icon_fix_util
bool operator==(const GeneratedIconFix& a, const GeneratedIconFix& b) {
return a.SerializeAsString() == b.SerializeAsString();
}
std::ostream& operator<<(std::ostream& out,
const GeneratedIconFixSource& source) {
switch (source) {
case GeneratedIconFixSource_UNKNOWN:
NOTREACHED();
return out << "Unknown";
case GeneratedIconFixSource_SYNC_INSTALL:
return out << "SyncInstall";
case GeneratedIconFixSource_RETROACTIVE:
return out << "Retroactive";
case GeneratedIconFixSource_MANIFEST_UPDATE:
return out << "ManifestUpdate";
}
}
} // namespace web_app