blob: a01d16b4faea12688e1db4bd3c86cc6ebd9bd866 [file] [log] [blame]
// Copyright 2020 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 "components/feed/core/v2/protocol_translator.h"
#include <string>
#include <utility>
#include "base/logging.h"
#include "base/time/time.h"
#include "components/feed/core/proto/v2/packing.pb.h"
#include "components/feed/core/proto/v2/wire/chrome_feed_response_metadata.pb.h"
#include "components/feed/core/proto/v2/wire/data_operation.pb.h"
#include "components/feed/core/proto/v2/wire/feature.pb.h"
#include "components/feed/core/proto/v2/wire/feed_response.pb.h"
#include "components/feed/core/proto/v2/wire/payload_metadata.pb.h"
#include "components/feed/core/proto/v2/wire/request_schedule.pb.h"
#include "components/feed/core/proto/v2/wire/stream_structure.pb.h"
#include "components/feed/core/proto/v2/wire/token.pb.h"
#include "components/feed/core/v2/feedstore_util.h"
#include "components/feed/core/v2/metrics_reporter.h"
#include "components/feed/core/v2/proto_util.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace feed {
namespace {
feedstore::StreamStructure::Operation TranslateOperationType(
feedwire::DataOperation::Operation operation) {
switch (operation) {
case feedwire::DataOperation::UNKNOWN_OPERATION:
return feedstore::StreamStructure::UNKNOWN;
case feedwire::DataOperation::CLEAR_ALL:
return feedstore::StreamStructure::CLEAR_ALL;
case feedwire::DataOperation::UPDATE_OR_APPEND:
return feedstore::StreamStructure::UPDATE_OR_APPEND;
case feedwire::DataOperation::REMOVE:
return feedstore::StreamStructure::REMOVE;
default:
return feedstore::StreamStructure::UNKNOWN;
}
}
feedstore::StreamStructure::Type TranslateNodeType(
feedwire::Feature::RenderableUnit renderable_unit) {
switch (renderable_unit) {
case feedwire::Feature::UNKNOWN_RENDERABLE_UNIT:
return feedstore::StreamStructure::UNKNOWN_TYPE;
case feedwire::Feature::STREAM:
return feedstore::StreamStructure::STREAM;
case feedwire::Feature::CONTENT:
return feedstore::StreamStructure::CONTENT;
case feedwire::Feature::CLUSTER:
return feedstore::StreamStructure::CLUSTER;
default:
return feedstore::StreamStructure::UNKNOWN_TYPE;
}
}
base::TimeDelta TranslateDuration(const feedwire::Duration& v) {
return base::TimeDelta::FromSeconds(v.seconds()) +
base::TimeDelta::FromNanoseconds(v.nanos());
}
absl::optional<RequestSchedule> TranslateRequestSchedule(
base::Time now,
const feedwire::RequestSchedule& v) {
RequestSchedule schedule;
const feedwire::RequestSchedule_TimeBasedSchedule& time_schedule =
v.time_based_schedule();
for (const feedwire::Duration& duration :
time_schedule.refresh_time_from_response_time()) {
schedule.refresh_offsets.push_back(TranslateDuration(duration));
}
schedule.anchor_time = now;
return schedule;
}
// Fields that should be present at most once in the response.
struct ConvertedGlobalData {
absl::optional<RequestSchedule> request_schedule;
};
struct ConvertedDataOperation {
feedstore::StreamStructure stream_structure;
absl::optional<feedstore::Content> content;
absl::optional<feedstore::StreamSharedState> shared_state;
absl::optional<std::string> next_page_token;
};
bool TranslateFeature(feedwire::Feature* feature,
ConvertedDataOperation& result) {
feedstore::StreamStructure::Type type =
TranslateNodeType(feature->renderable_unit());
result.stream_structure.set_type(type);
if (type == feedstore::StreamStructure::CONTENT) {
feedwire::Content* wire_content = feature->mutable_content();
if (!wire_content->has_xsurface_content())
return false;
// TODO(iwells): We still need score, availability_time_seconds,
// offline_metadata, and representation_data to populate content_info.
result.content.emplace();
*(result.content->mutable_content_id()) =
result.stream_structure.content_id();
result.content->set_allocated_frame(
wire_content->mutable_xsurface_content()->release_xsurface_output());
if (wire_content->prefetch_metadata_size() > 0) {
result.content->mutable_prefetch_metadata()->Swap(
wire_content->mutable_prefetch_metadata());
}
}
return true;
}
absl::optional<feedstore::StreamSharedState> TranslateSharedState(
feedwire::ContentId content_id,
feedwire::RenderData& wire_shared_state) {
if (wire_shared_state.render_data_type() != feedwire::RenderData::XSURFACE) {
return absl::nullopt;
}
feedstore::StreamSharedState shared_state;
*shared_state.mutable_content_id() = std::move(content_id);
shared_state.set_allocated_shared_state_data(
wire_shared_state.mutable_xsurface_container()->release_render_data());
return shared_state;
}
bool TranslatePayload(base::Time now,
feedwire::DataOperation operation,
ConvertedGlobalData* global_data,
ConvertedDataOperation& result) {
switch (operation.payload_case()) {
case feedwire::DataOperation::kFeature: {
feedwire::Feature* feature = operation.mutable_feature();
DCHECK(!result.stream_structure.has_parent_id());
if (feature->has_parent_id()) {
result.stream_structure.set_allocated_parent_id(
feature->release_parent_id());
} else if (feature->is_root()) {
result.stream_structure.set_is_root(true);
}
if (!TranslateFeature(feature, result))
return false;
} break;
case feedwire::DataOperation::kNextPageToken: {
feedwire::Token* token = operation.mutable_next_page_token();
result.stream_structure.set_allocated_parent_id(
token->release_parent_id());
result.next_page_token = std::move(
*token->mutable_next_page_token()->mutable_next_page_token());
} break;
case feedwire::DataOperation::kRenderData: {
result.shared_state =
TranslateSharedState(result.stream_structure.content_id(),
*operation.mutable_render_data());
if (!result.shared_state)
return false;
} break;
case feedwire::DataOperation::kRequestSchedule: {
if (global_data) {
global_data->request_schedule =
TranslateRequestSchedule(now, operation.request_schedule());
}
} break;
default:
return false;
}
return true;
}
absl::optional<ConvertedDataOperation> TranslateDataOperationInternal(
base::Time now,
feedwire::DataOperation operation,
ConvertedGlobalData* global_data) {
feedstore::StreamStructure::Operation operation_type =
TranslateOperationType(operation.operation());
ConvertedDataOperation result;
result.stream_structure.set_operation(operation_type);
switch (operation_type) {
case feedstore::StreamStructure::CLEAR_ALL:
return result;
case feedstore::StreamStructure::UPDATE_OR_APPEND:
if (!operation.has_metadata() || !operation.metadata().has_content_id())
return absl::nullopt;
result.stream_structure.set_allocated_content_id(
operation.mutable_metadata()->release_content_id());
if (!TranslatePayload(now, std::move(operation), global_data, result))
return absl::nullopt;
break;
case feedstore::StreamStructure::REMOVE:
if (!operation.has_metadata() || !operation.metadata().has_content_id())
return absl::nullopt;
result.stream_structure.set_allocated_content_id(
operation.mutable_metadata()->release_content_id());
break;
case feedstore::StreamStructure::UNKNOWN: // Fall through
default:
return absl::nullopt;
}
return result;
}
} // namespace
StreamModelUpdateRequest::StreamModelUpdateRequest() = default;
StreamModelUpdateRequest::~StreamModelUpdateRequest() = default;
StreamModelUpdateRequest::StreamModelUpdateRequest(
const StreamModelUpdateRequest&) = default;
StreamModelUpdateRequest& StreamModelUpdateRequest::operator=(
const StreamModelUpdateRequest&) = default;
RefreshResponseData::RefreshResponseData() = default;
RefreshResponseData::~RefreshResponseData() = default;
RefreshResponseData::RefreshResponseData(RefreshResponseData&&) = default;
RefreshResponseData& RefreshResponseData::operator=(RefreshResponseData&&) =
default;
absl::optional<feedstore::DataOperation> TranslateDataOperation(
base::Time now,
feedwire::DataOperation wire_operation) {
feedstore::DataOperation store_operation;
// Note: This function is used when executing operations in response to
// actions embedded in the server protobuf. Some data in data operations
// aren't supported by this function, which is why we're passing in
// global_data=nullptr.
absl::optional<ConvertedDataOperation> converted =
TranslateDataOperationInternal(now, std::move(wire_operation), nullptr);
if (!converted)
return absl::nullopt;
// We only support translating StreamSharedStates when they will be attached
// to StreamModelUpdateRequests.
if (converted->shared_state)
return absl::nullopt;
*store_operation.mutable_structure() = std::move(converted->stream_structure);
if (converted->content)
*store_operation.mutable_content() = std::move(*converted->content);
return store_operation;
}
RefreshResponseData TranslateWireResponse(
feedwire::Response response,
StreamModelUpdateRequest::Source source,
bool was_signed_in_request,
base::Time current_time) {
if (response.response_version() != feedwire::Response::FEED_RESPONSE)
return {};
auto result = std::make_unique<StreamModelUpdateRequest>();
result->source = source;
ConvertedGlobalData global_data;
feedwire::FeedResponse* feed_response = response.mutable_feed_response();
for (auto& wire_data_operation : *feed_response->mutable_data_operation()) {
if (!wire_data_operation.has_operation())
continue;
absl::optional<ConvertedDataOperation> operation =
TranslateDataOperationInternal(
current_time, std::move(wire_data_operation), &global_data);
if (!operation)
continue;
result->stream_structures.push_back(std::move(operation->stream_structure));
if (operation->content)
result->content.push_back(std::move(*operation->content));
if (operation->shared_state)
result->shared_states.push_back(std::move(*operation->shared_state));
if (operation->next_page_token) {
result->stream_data.set_next_page_token(
std::move(*operation->next_page_token));
}
}
if (!result->shared_states.empty()) {
for (const auto& shared_state : result->shared_states) {
*result->stream_data.add_shared_state_ids() = shared_state.content_id();
}
}
feedstore::SetLastAddedTime(current_time, result->stream_data);
const auto& response_metadata =
feed_response->feed_response_metadata().chrome_feed_response_metadata();
result->stream_data.set_signed_in(was_signed_in_request);
result->stream_data.set_logging_enabled(response_metadata.logging_enabled());
result->stream_data.set_privacy_notice_fulfilled(
response_metadata.privacy_notice_fulfilled());
for (const feedstore::Content& content : result->content) {
result->stream_data.add_content_ids(content.content_id().id());
}
absl::optional<std::string> session_id = absl::nullopt;
if (was_signed_in_request) {
// Signed-in requests don't use session_id tokens; set an empty value to
// ensure that there are no old session_id tokens left hanging around.
session_id = std::string();
} else if (response_metadata.has_session_id()) {
// Signed-out requests can set a new session token; otherwise, we leave
// the default absl::nullopt value to keep whatever token is already in
// play.
session_id = response_metadata.session_id();
}
absl::optional<Experiments> experiments = absl::nullopt;
if (response_metadata.experiments_size() > 0) {
Experiments e;
for (feedwire::Experiment exp : response_metadata.experiments()) {
e[exp.trial_name()] = exp.group_name();
}
experiments = std::move(e);
}
MetricsReporter::ActivityLoggingEnabled(response_metadata.logging_enabled());
MetricsReporter::NoticeCardFulfilledObsolete(
response_metadata.privacy_notice_fulfilled());
RefreshResponseData response_data;
response_data.model_update_request = std::move(result);
response_data.request_schedule = std::move(global_data.request_schedule);
response_data.session_id = std::move(session_id);
response_data.experiments = std::move(experiments);
response_data.server_request_received_timestamp_ns =
feed_response->feed_response_metadata().event_id().time_usec() * 1'000;
response_data.server_response_sent_timestamp_ns =
feed_response->feed_response_metadata().response_time_ms() * 1'000'000;
return response_data;
}
std::vector<feedstore::DataOperation> TranslateDismissData(
base::Time current_time,
feedpacking::DismissData data) {
std::vector<feedstore::DataOperation> result;
for (auto& operation : data.data_operations()) {
absl::optional<feedstore::DataOperation> translated_operation =
TranslateDataOperation(current_time, operation);
if (translated_operation) {
result.push_back(std::move(translated_operation.value()));
}
}
return result;
}
} // namespace feed