1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/feed/core/v2/stream_model.h"
6 
7 #include <algorithm>
8 #include <sstream>
9 #include <utility>
10 
11 #include "base/check.h"
12 #include "base/json/string_escape.h"
13 #include "base/strings/strcat.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "components/feed/core/proto/v2/store.pb.h"
16 #include "components/feed/core/proto/v2/wire/content_id.pb.h"
17 #include "components/feed/core/v2/protocol_translator.h"
18 
19 namespace feed {
20 namespace {
21 using UiUpdate = StreamModel::UiUpdate;
22 using StoreUpdate = StreamModel::StoreUpdate;
23 
HasClearAll(const std::vector<feedstore::StreamStructure> & structures)24 bool HasClearAll(const std::vector<feedstore::StreamStructure>& structures) {
25   for (const feedstore::StreamStructure& data : structures) {
26     if (data.operation() == feedstore::StreamStructure::CLEAR_ALL)
27       return true;
28   }
29   return false;
30 }
31 }  // namespace
32 
33 UiUpdate::UiUpdate() = default;
34 UiUpdate::~UiUpdate() = default;
35 UiUpdate::UiUpdate(const UiUpdate&) = default;
36 UiUpdate& UiUpdate::operator=(const UiUpdate&) = default;
37 StoreUpdate::StoreUpdate() = default;
38 StoreUpdate::~StoreUpdate() = default;
39 StoreUpdate::StoreUpdate(StoreUpdate&&) = default;
40 StoreUpdate& StoreUpdate::operator=(StoreUpdate&&) = default;
41 
42 StreamModel::StreamModel() = default;
43 StreamModel::~StreamModel() = default;
44 
SetStoreObserver(StoreObserver * store_observer)45 void StreamModel::SetStoreObserver(StoreObserver* store_observer) {
46   DCHECK(!store_observer || !store_observer_)
47       << "Attempting to set store_observer multiple times";
48   store_observer_ = store_observer;
49 }
50 
AddObserver(Observer * observer)51 void StreamModel::AddObserver(Observer* observer) {
52   observers_.AddObserver(observer);
53 }
54 
RemoveObserver(Observer * observer)55 void StreamModel::RemoveObserver(Observer* observer) {
56   observers_.RemoveObserver(observer);
57 }
58 
FindContent(ContentRevision revision) const59 const feedstore::Content* StreamModel::FindContent(
60     ContentRevision revision) const {
61   return GetFinalFeatureTree()->FindContent(revision);
62 }
FindSharedStateData(const std::string & id) const63 const std::string* StreamModel::FindSharedStateData(
64     const std::string& id) const {
65   auto iter = shared_states_.find(id);
66   if (iter != shared_states_.end()) {
67     return &iter->second.data;
68   }
69   return nullptr;
70 }
71 
GetSharedStateIds() const72 std::vector<std::string> StreamModel::GetSharedStateIds() const {
73   std::vector<std::string> ids;
74   for (auto& entry : shared_states_) {
75     ids.push_back(entry.first);
76   }
77   return ids;
78 }
79 
Update(std::unique_ptr<StreamModelUpdateRequest> update_request)80 void StreamModel::Update(
81     std::unique_ptr<StreamModelUpdateRequest> update_request) {
82   std::vector<feedstore::StreamStructure>& stream_structures =
83       update_request->stream_structures;
84   const bool has_clear_all = HasClearAll(stream_structures);
85 
86   switch (update_request->source) {
87     case StreamModelUpdateRequest::Source::kNetworkUpdate:
88       // In this case, the stream state has been saved to persistent
89       // storage by the caller. Next sequence number is always 1.
90       next_structure_sequence_number_ = 1;
91       break;
92     case StreamModelUpdateRequest::Source::kInitialLoadFromStore:
93       // In this case, use max_structure_sequence_number to derive the next
94       // sequence number.
95       next_structure_sequence_number_ =
96           update_request->max_structure_sequence_number + 1;
97       break;
98     case StreamModelUpdateRequest::Source::kNetworkLoadMore: {
99       // In this case, |StreamModel| is responsible for triggering the update
100       // to the store. There are two main cases:
101       // 1. The update request has a CLEAR_ALL (this is unexpected).
102       //    In this case, we want to overwrite all stored stream data, since
103       //    the old data is no longer useful. Start using sequence number 0.
104       // 2. The update request does not have a CLEAR_ALL.
105       //    Save the new stream data with the next sequence number.
106       if (has_clear_all) {
107         next_structure_sequence_number_ = 0;
108       }
109       // Note: We might be overwriting some shared-states unnecessarily.
110       StoreUpdate store_update;
111       store_update.overwrite_stream_data = has_clear_all;
112       store_update.update_request =
113           std::make_unique<StreamModelUpdateRequest>(*update_request);
114       store_update.sequence_number = next_structure_sequence_number_++;
115       store_observer_->OnStoreChange(std::move(store_update));
116       break;
117     }
118   }
119 
120   // Update non-tree data.
121   stream_data_ = update_request->stream_data;
122 
123   if (has_clear_all) {
124     shared_states_.clear();
125   }
126 
127   // Update the feature tree.
128   for (const feedstore::StreamStructure& structure : stream_structures) {
129     base_feature_tree_.ApplyStreamStructure(structure);
130   }
131   for (feedstore::Content& content : update_request->content) {
132     base_feature_tree_.AddContent(std::move(content));
133   }
134 
135   for (feedstore::StreamSharedState& shared_state :
136        update_request->shared_states) {
137     std::string id = ContentIdString(shared_state.content_id());
138     if (!shared_states_.contains(id)) {
139       shared_states_[id].data =
140           std::move(*shared_state.mutable_shared_state_data());
141     }
142   }
143 
144   // TODO(harringtond): We're not using StreamData's content_id for anything.
145 
146   UpdateFlattenedTree();
147 }
148 
CreateEphemeralChange(std::vector<feedstore::DataOperation> operations)149 EphemeralChangeId StreamModel::CreateEphemeralChange(
150     std::vector<feedstore::DataOperation> operations) {
151   const EphemeralChangeId id =
152       ephemeral_changes_.AddEphemeralChange(std::move(operations))->id();
153 
154   UpdateFlattenedTree();
155 
156   return id;
157 }
158 
ExecuteOperations(std::vector<feedstore::DataOperation> operations)159 void StreamModel::ExecuteOperations(
160     std::vector<feedstore::DataOperation> operations) {
161   for (const feedstore::DataOperation& operation : operations) {
162     if (operation.has_structure()) {
163       base_feature_tree_.ApplyStreamStructure(operation.structure());
164     }
165     if (operation.has_content()) {
166       base_feature_tree_.AddContent(operation.content());
167     }
168   }
169 
170   if (store_observer_) {
171     StoreUpdate store_update;
172     store_update.operations = std::move(operations);
173     store_update.sequence_number = next_structure_sequence_number_++;
174     store_observer_->OnStoreChange(std::move(store_update));
175   }
176 
177   UpdateFlattenedTree();
178 }
179 
CommitEphemeralChange(EphemeralChangeId id)180 bool StreamModel::CommitEphemeralChange(EphemeralChangeId id) {
181   std::unique_ptr<stream_model::EphemeralChange> change =
182       ephemeral_changes_.Remove(id);
183   if (!change)
184     return false;
185 
186   // Note: it's possible that the does change even upon commit because it
187   // may change the order that operations are applied. ExecuteOperations
188   // will ensure observers are updated.
189   ExecuteOperations(change->GetOperations());
190   return true;
191 }
192 
RejectEphemeralChange(EphemeralChangeId id)193 bool StreamModel::RejectEphemeralChange(EphemeralChangeId id) {
194   if (ephemeral_changes_.Remove(id)) {
195     UpdateFlattenedTree();
196     return true;
197   }
198   return false;
199 }
200 
UpdateFlattenedTree()201 void StreamModel::UpdateFlattenedTree() {
202   if (ephemeral_changes_.GetChangeList().empty()) {
203     feature_tree_after_changes_.reset();
204   } else {
205     feature_tree_after_changes_ =
206         ApplyEphemeralChanges(base_feature_tree_, ephemeral_changes_);
207   }
208   // Update list of visible content.
209   std::vector<ContentRevision> new_state =
210       GetFinalFeatureTree()->GetVisibleContent();
211   const bool content_list_changed = content_list_ != new_state;
212   content_list_ = std::move(new_state);
213 
214   // Pack and send UiUpdate.
215   UiUpdate update;
216   update.content_list_changed = content_list_changed;
217   for (auto& entry : shared_states_) {
218     SharedState& shared_state = entry.second;
219     UiUpdate::SharedStateInfo info;
220     info.shared_state_id = entry.first;
221     info.updated = shared_state.updated;
222     update.shared_states.push_back(std::move(info));
223 
224     shared_state.updated = false;
225   }
226 
227   for (Observer& observer : observers_)
228     observer.OnUiUpdate(update);
229 }
230 
GetFinalFeatureTree()231 stream_model::FeatureTree* StreamModel::GetFinalFeatureTree() {
232   return feature_tree_after_changes_ ? feature_tree_after_changes_.get()
233                                      : &base_feature_tree_;
234 }
GetFinalFeatureTree() const235 const stream_model::FeatureTree* StreamModel::GetFinalFeatureTree() const {
236   return const_cast<StreamModel*>(this)->GetFinalFeatureTree();
237 }
238 
GetNextPageToken() const239 const std::string& StreamModel::GetNextPageToken() const {
240   return stream_data_.next_page_token();
241 }
242 
DumpStateForTesting()243 std::string StreamModel::DumpStateForTesting() {
244   std::stringstream ss;
245   ss << "StreamModel{\n";
246   ss << "next_page_token='" << GetNextPageToken() << "'\n";
247   for (auto& entry : shared_states_) {
248     ss << "shared_state[" << entry.first
249        << "]=" << base::GetQuotedJSONString(entry.second.data.substr(0, 100))
250        << "\n";
251   }
252   ss << GetFinalFeatureTree()->DumpStateForTesting();
253   ss << "}StreamModel\n";
254   return ss.str();
255 }
256 
257 }  // namespace feed
258