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)24bool 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)45void 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)51void StreamModel::AddObserver(Observer* observer) { 52 observers_.AddObserver(observer); 53 } 54 RemoveObserver(Observer * observer)55void StreamModel::RemoveObserver(Observer* observer) { 56 observers_.RemoveObserver(observer); 57 } 58 FindContent(ContentRevision revision) const59const feedstore::Content* StreamModel::FindContent( 60 ContentRevision revision) const { 61 return GetFinalFeatureTree()->FindContent(revision); 62 } FindSharedStateData(const std::string & id) const63const 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() const72std::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)80void 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)149EphemeralChangeId 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)159void 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)180bool 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)193bool StreamModel::RejectEphemeralChange(EphemeralChangeId id) { 194 if (ephemeral_changes_.Remove(id)) { 195 UpdateFlattenedTree(); 196 return true; 197 } 198 return false; 199 } 200 UpdateFlattenedTree()201void 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()231stream_model::FeatureTree* StreamModel::GetFinalFeatureTree() { 232 return feature_tree_after_changes_ ? feature_tree_after_changes_.get() 233 : &base_feature_tree_; 234 } GetFinalFeatureTree() const235const stream_model::FeatureTree* StreamModel::GetFinalFeatureTree() const { 236 return const_cast<StreamModel*>(this)->GetFinalFeatureTree(); 237 } 238 GetNextPageToken() const239const std::string& StreamModel::GetNextPageToken() const { 240 return stream_data_.next_page_token(); 241 } 242 DumpStateForTesting()243std::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