blob: 80223ea9b2ef8ff9c97c9dbd31391a52dbb49e0b [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/feed/core/v2/feedstore_util.h"
#include "base/base64url.h"
#include "base/hash/hash.h"
#include "base/strings/strcat.h"
#include "components/feed/core/proto/v2/store.pb.h"
#include "components/feed/core/proto/v2/wire/consistency_token.pb.h"
#include "components/feed/core/v2/config.h"
#include "components/feed/core/v2/feed_store.h"
#include "components/feed/core/v2/public/stream_type.h"
namespace feedstore {
using feed::LocalActionId;
using feed::StreamType;
// This returns a string version of StreamType which can be used in datastore
// keys.
std::string StreamKey(const StreamType& stream_type) {
if (stream_type.IsForYou())
return kForYouStreamKey;
if (stream_type.IsWebFeed())
return kFollowStreamKey;
DCHECK(stream_type.IsSingleWebFeed());
std::string encoding;
base::Base64UrlEncode(stream_type.GetWebFeedId(),
base::Base64UrlEncodePolicy::INCLUDE_PADDING,
&encoding);
if (stream_type.IsSingleWebFeedEntryMenu()) {
return base::StrCat({std::string(kSingleWebFeedStreamKeyPrefix), "/",
std::string(kSingleWebFeedMenuStreamKeyPrefix),
encoding});
} else {
return base::StrCat({std::string(kSingleWebFeedStreamKeyPrefix), "/",
std::string(kSingleWebFeedOtherStreamKeyPrefix),
encoding});
}
}
base::StringPiece StreamPrefix(feed::StreamKind stream_kind) {
if (stream_kind == feed::StreamKind::kForYou)
return kForYouStreamKey;
if (stream_kind == feed::StreamKind::kFollowing)
return kFollowStreamKey;
DCHECK(stream_kind == feed::StreamKind::kSingleWebFeed);
return kSingleWebFeedStreamKeyPrefix;
}
StreamType DecodeSingleWebFeedKeySuffix(
base::StringPiece suffix,
feed::SingleWebFeedEntryPoint entry_point,
base::StringPiece prefix) {
if (base::StartsWith(suffix, prefix, base::CompareCase::SENSITIVE)) {
suffix.remove_prefix(prefix.size());
std::string single_web_feed_key;
if (base::Base64UrlDecode(suffix,
base::Base64UrlDecodePolicy::IGNORE_PADDING,
&single_web_feed_key)) {
return StreamType(feed::StreamKind::kSingleWebFeed, single_web_feed_key,
entry_point);
}
}
return {};
}
StreamType StreamTypeFromKey(base::StringPiece id) {
if (id == kForYouStreamKey)
return StreamType(feed::StreamKind::kForYou);
if (id == kFollowStreamKey)
return StreamType(feed::StreamKind::kFollowing);
if (base::StartsWith(id, kSingleWebFeedStreamKeyPrefix,
base::CompareCase::SENSITIVE)) {
if ((id.size() < (kSingleWebFeedStreamKeyPrefix.size() +
kSingleWebFeedMenuStreamKeyPrefix.size() + 1))) {
return {};
}
// add +1 to account for the '/' separating the c/[mo]/webid
base::StringPiece substr =
id.substr(kSingleWebFeedStreamKeyPrefix.size() + 1);
StreamType result = DecodeSingleWebFeedKeySuffix(
substr, feed::SingleWebFeedEntryPoint::kMenu,
kSingleWebFeedMenuStreamKeyPrefix);
if (!result.IsValid()) {
result = DecodeSingleWebFeedKeySuffix(
substr, feed::SingleWebFeedEntryPoint::kOther,
kSingleWebFeedOtherStreamKeyPrefix);
}
return result;
}
return {};
}
int64_t ToTimestampMillis(base::Time t) {
return t.is_null() ? 0L : (t - base::Time::UnixEpoch()).InMilliseconds();
}
base::Time FromTimestampMillis(int64_t millis) {
return base::Time::UnixEpoch() + base::Milliseconds(millis);
}
int64_t ToTimestampNanos(base::Time t) {
return t.is_null() ? 0L : (t - base::Time::UnixEpoch()).InNanoseconds();
}
base::Time FromTimestampMicros(int64_t micros) {
return base::Time::UnixEpoch() + base::Microseconds(micros);
}
void SetLastAddedTime(base::Time t, feedstore::StreamData& data) {
data.set_last_added_time_millis(ToTimestampMillis(t));
}
base::Time GetLastAddedTime(const feedstore::StreamData& data) {
return FromTimestampMillis(data.last_added_time_millis());
}
base::Time GetSessionIdExpiryTime(const Metadata& metadata) {
return base::Time::FromDeltaSinceWindowsEpoch(
base::Milliseconds(metadata.session_id().expiry_time_ms()));
}
void SetSessionId(Metadata& metadata,
std::string token,
base::Time expiry_time) {
Metadata::SessionID* session_id = metadata.mutable_session_id();
session_id->set_token(std::move(token));
session_id->set_expiry_time_ms(
expiry_time.ToDeltaSinceWindowsEpoch().InMilliseconds());
}
void SetContentLifetime(
feedstore::Metadata& metadata,
const StreamType& stream_type,
feedstore::Metadata::StreamMetadata::ContentLifetime content_lifetime) {
feedstore::Metadata::StreamMetadata& stream_metadata =
feedstore::MetadataForStream(metadata, stream_type);
*stream_metadata.mutable_content_lifetime() = std::move(content_lifetime);
}
void MaybeUpdateSessionId(Metadata& metadata,
absl::optional<std::string> token) {
if (token && metadata.session_id().token() != *token) {
base::Time expiry_time =
token->empty()
? base::Time()
: base::Time::Now() + feed::GetFeedConfig().session_id_max_age;
SetSessionId(metadata, *token, expiry_time);
}
}
absl::optional<Metadata> MaybeUpdateConsistencyToken(
const feedstore::Metadata& metadata,
const feedwire::ConsistencyToken& token) {
if (token.has_token() && metadata.consistency_token() != token.token()) {
auto metadata_copy = metadata;
metadata_copy.set_consistency_token(token.token());
return metadata_copy;
}
return absl::nullopt;
}
LocalActionId GetNextActionId(Metadata& metadata) {
uint32_t id = metadata.next_action_id();
// Never use 0, as that's an invalid LocalActionId.
if (id == 0)
++id;
metadata.set_next_action_id(id + 1);
return LocalActionId(id);
}
const Metadata::StreamMetadata* FindMetadataForStream(
const Metadata& metadata,
const StreamType& stream_type) {
std::string key = StreamKey(stream_type);
for (const auto& sm : metadata.stream_metadata()) {
if (sm.stream_key() == key)
return &sm;
}
return nullptr;
}
Metadata::StreamMetadata& MetadataForStream(Metadata& metadata,
const StreamType& stream_type) {
const Metadata::StreamMetadata* existing =
FindMetadataForStream(metadata, stream_type);
if (existing)
return *const_cast<Metadata::StreamMetadata*>(existing);
Metadata::StreamMetadata* sm = metadata.add_stream_metadata();
sm->set_stream_key(std::string(StreamKey(stream_type)));
return *sm;
}
void SetStreamViewContentHashes(Metadata& metadata,
const StreamType& stream_type,
const feed::ContentHashSet& content_hashes) {
Metadata::StreamMetadata& stream_metadata =
MetadataForStream(metadata, stream_type);
stream_metadata.clear_view_content_hashes();
stream_metadata.mutable_view_content_hashes()->Add(
content_hashes.original_hashes().begin(),
content_hashes.original_hashes().end());
}
bool IsKnownStale(const Metadata& metadata, const StreamType& stream_type) {
const Metadata::StreamMetadata* sm =
FindMetadataForStream(metadata, stream_type);
return sm ? sm->is_known_stale() : false;
}
base::Time GetLastFetchTime(const Metadata& metadata,
const feed::StreamType& stream_type) {
const Metadata::StreamMetadata* sm =
FindMetadataForStream(metadata, stream_type);
return sm ? FromTimestampMillis(sm->last_fetch_time_millis()) : base::Time();
}
void SetLastFetchTime(Metadata& metadata,
const StreamType& stream_type,
const base::Time& fetch_time) {
Metadata::StreamMetadata& stream_metadata =
MetadataForStream(metadata, stream_type);
stream_metadata.set_last_fetch_time_millis(ToTimestampMillis(fetch_time));
}
feedstore::Metadata MakeMetadata(const std::string& gaia) {
feedstore::Metadata md;
md.set_stream_schema_version(feed::FeedStore::kCurrentStreamSchemaVersion);
md.set_gaia(gaia);
return md;
}
feedstore::DocView CreateDocView(uint64_t docid, base::Time timestamp) {
feedstore::DocView doc_view;
doc_view.set_docid(docid);
doc_view.set_view_time_millis(feedstore::ToTimestampMillis(timestamp));
return doc_view;
}
absl::optional<Metadata> SetStreamViewContentHashes(
const Metadata& metadata,
const StreamType& stream_type,
const feed::ContentHashSet& content_hashes) {
absl::optional<Metadata> result;
if (!(GetViewContentIds(metadata, stream_type) == content_hashes)) {
result = metadata;
SetStreamViewContentHashes(*result, stream_type, content_hashes);
}
return result;
}
feed::ContentHashSet GetContentIds(const StreamData& stream_data) {
return feed::ContentHashSet{{stream_data.content_hashes().begin(),
stream_data.content_hashes().end()}};
}
feed::ContentHashSet GetViewContentIds(const Metadata& metadata,
const StreamType& stream_type) {
const Metadata::StreamMetadata* stream_metadata =
FindMetadataForStream(metadata, stream_type);
if (stream_metadata) {
return feed::ContentHashSet({stream_metadata->view_content_hashes().begin(),
stream_metadata->view_content_hashes().end()});
}
return {};
}
void SetLastServerResponseTime(Metadata& metadata,
const feed::StreamType& stream_type,
const base::Time& server_time) {
Metadata::StreamMetadata& stream_metadata =
MetadataForStream(metadata, stream_type);
stream_metadata.set_last_server_response_time_millis(
ToTimestampMillis(server_time));
}
int32_t ContentHashFromPrefetchMetadata(
const feedwire::PrefetchMetadata& prefetch_metadata) {
return base::PersistentHash(prefetch_metadata.uri());
}
base::flat_set<uint32_t> GetViewedContentHashes(const Metadata& metadata,
const StreamType& stream_type) {
const Metadata::StreamMetadata* stream_metadata =
FindMetadataForStream(metadata, stream_type);
if (stream_metadata) {
return base::flat_set<uint32_t>(
stream_metadata->viewed_content_hashes().begin(),
stream_metadata->viewed_content_hashes().end());
}
return {};
}
} // namespace feedstore