Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(72)

Side by Side Diff: components/ntp_snippets/offline_pages/offline_page_suggestions_provider.cc

Issue 2251743002: Refactor OfflinePageSuggestionsProvider dismissed ID handling (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@offlinedelete
Patch Set: Clean up dismissed IDs when loading new offline page suggestions Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « components/ntp_snippets/offline_pages/offline_page_suggestions_provider.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/ntp_snippets/offline_pages/offline_page_suggestions_provide r.h" 5 #include "components/ntp_snippets/offline_pages/offline_page_suggestions_provide r.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 8
9 #include "base/bind.h" 9 #include "base/bind.h"
10 #include "base/guid.h" 10 #include "base/guid.h"
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
53 ? CategoryStatus::AVAILABLE_LOADING 53 ? CategoryStatus::AVAILABLE_LOADING
54 : CategoryStatus::NOT_PROVIDED), 54 : CategoryStatus::NOT_PROVIDED),
55 downloads_status_(downloads_enabled ? CategoryStatus::AVAILABLE_LOADING 55 downloads_status_(downloads_enabled ? CategoryStatus::AVAILABLE_LOADING
56 : CategoryStatus::NOT_PROVIDED), 56 : CategoryStatus::NOT_PROVIDED),
57 offline_page_model_(offline_page_model), 57 offline_page_model_(offline_page_model),
58 recent_tabs_category_( 58 recent_tabs_category_(
59 category_factory->FromKnownCategory(KnownCategories::RECENT_TABS)), 59 category_factory->FromKnownCategory(KnownCategories::RECENT_TABS)),
60 downloads_category_( 60 downloads_category_(
61 category_factory->FromKnownCategory(KnownCategories::DOWNLOADS)), 61 category_factory->FromKnownCategory(KnownCategories::DOWNLOADS)),
62 pref_service_(pref_service), 62 pref_service_(pref_service),
63 dismissed_recent_tab_ids_(ReadDismissedIDsFromPrefs(
64 prefs::kDismissedRecentOfflineTabSuggestions)),
65 dismissed_download_ids_(
66 ReadDismissedIDsFromPrefs(prefs::kDismissedDownloadSuggestions)),
67 download_manager_ui_enabled_(download_manager_ui_enabled) { 63 download_manager_ui_enabled_(download_manager_ui_enabled) {
68 DCHECK(recent_tabs_enabled || downloads_enabled); 64 DCHECK(recent_tabs_enabled || downloads_enabled);
69 offline_page_model_->AddObserver(this); 65 offline_page_model_->AddObserver(this);
70 FetchOfflinePages(); 66 FetchOfflinePages();
71 } 67 }
72 68
73 OfflinePageSuggestionsProvider::~OfflinePageSuggestionsProvider() { 69 OfflinePageSuggestionsProvider::~OfflinePageSuggestionsProvider() {
74 offline_page_model_->RemoveObserver(this); 70 offline_page_model_->RemoveObserver(this);
75 } 71 }
76 72
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
120 NOTREACHED() << "Unknown category " << category.id(); 116 NOTREACHED() << "Unknown category " << category.id();
121 return CategoryInfo(base::string16(), 117 return CategoryInfo(base::string16(),
122 ContentSuggestionsCardLayout::MINIMAL_CARD, 118 ContentSuggestionsCardLayout::MINIMAL_CARD,
123 /* has_more_button */ false); 119 /* has_more_button */ false);
124 } 120 }
125 121
126 void OfflinePageSuggestionsProvider::DismissSuggestion( 122 void OfflinePageSuggestionsProvider::DismissSuggestion(
127 const std::string& suggestion_id) { 123 const std::string& suggestion_id) {
128 Category category = GetCategoryFromUniqueID(suggestion_id); 124 Category category = GetCategoryFromUniqueID(suggestion_id);
129 std::string offline_page_id = GetWithinCategoryIDFromUniqueID(suggestion_id); 125 std::string offline_page_id = GetWithinCategoryIDFromUniqueID(suggestion_id);
130 if (category == recent_tabs_category_) { 126 std::set<std::string> dismissed_ids = ReadDismissedIDsFromPrefs(category);
131 DCHECK_NE(CategoryStatus::NOT_PROVIDED, recent_tabs_status_); 127 dismissed_ids.insert(offline_page_id);
132 dismissed_recent_tab_ids_.insert(offline_page_id); 128 StoreDismissedIDsToPrefs(category, dismissed_ids);
133 StoreDismissedIDsToPrefs(prefs::kDismissedRecentOfflineTabSuggestions,
134 dismissed_recent_tab_ids_);
135 } else if (category == downloads_category_) {
136 DCHECK_NE(CategoryStatus::NOT_PROVIDED, downloads_status_);
137 dismissed_download_ids_.insert(offline_page_id);
138 StoreDismissedIDsToPrefs(prefs::kDismissedDownloadSuggestions,
139 dismissed_download_ids_);
140 } else {
141 NOTREACHED() << "Unknown category " << category.id();
142 }
143 } 129 }
144 130
145 void OfflinePageSuggestionsProvider::FetchSuggestionImage( 131 void OfflinePageSuggestionsProvider::FetchSuggestionImage(
146 const std::string& suggestion_id, 132 const std::string& suggestion_id,
147 const ImageFetchedCallback& callback) { 133 const ImageFetchedCallback& callback) {
148 // TODO(pke): Fetch proper thumbnail from OfflinePageModel once it's available 134 // TODO(pke): Fetch proper thumbnail from OfflinePageModel once it's available
149 // there. 135 // there.
150 base::ThreadTaskRunnerHandle::Get()->PostTask( 136 base::ThreadTaskRunnerHandle::Get()->PostTask(
151 FROM_HERE, base::Bind(callback, suggestion_id, gfx::Image())); 137 FROM_HERE, base::Bind(callback, suggestion_id, gfx::Image()));
152 } 138 }
153 139
154 void OfflinePageSuggestionsProvider::ClearCachedSuggestionsForDebugging( 140 void OfflinePageSuggestionsProvider::ClearCachedSuggestionsForDebugging(
155 Category category) { 141 Category category) {
156 // Ignored. 142 // Ignored.
157 } 143 }
158 144
159 std::vector<ContentSuggestion> 145 std::vector<ContentSuggestion>
160 OfflinePageSuggestionsProvider::GetDismissedSuggestionsForDebugging( 146 OfflinePageSuggestionsProvider::GetDismissedSuggestionsForDebugging(
161 Category category) { 147 Category category) {
162 // TODO(pke): Make GetDismissedSuggestionsForDebugging asynchronous so this 148 // TODO(pke): Make GetDismissedSuggestionsForDebugging asynchronous so this
163 // can return proper values. 149 // can return proper values.
150 std::set<std::string> dismissed_ids = ReadDismissedIDsFromPrefs(category);
164 std::vector<ContentSuggestion> suggestions; 151 std::vector<ContentSuggestion> suggestions;
165 const std::set<std::string>* dismissed_ids = nullptr; 152 for (const std::string& dismissed_id : dismissed_ids) {
166 if (category == recent_tabs_category_) {
167 DCHECK_NE(CategoryStatus::NOT_PROVIDED, recent_tabs_status_);
168 dismissed_ids = &dismissed_recent_tab_ids_;
169 } else if (category == downloads_category_) {
170 DCHECK_NE(CategoryStatus::NOT_PROVIDED, downloads_status_);
171 dismissed_ids = &dismissed_download_ids_;
172 } else {
173 NOTREACHED() << "Unknown category " << category.id();
174 return suggestions;
175 }
176
177 for (const std::string& dismissed_id : *dismissed_ids) {
178 ContentSuggestion suggestion( 153 ContentSuggestion suggestion(
179 MakeUniqueID(category, dismissed_id), 154 MakeUniqueID(category, dismissed_id),
180 GURL("http://dismissed-offline-page-" + dismissed_id)); 155 GURL("http://dismissed-offline-page-" + dismissed_id));
181 suggestion.set_title(base::UTF8ToUTF16("Title not available")); 156 suggestion.set_title(base::UTF8ToUTF16("Title not available"));
182 suggestions.push_back(std::move(suggestion)); 157 suggestions.push_back(std::move(suggestion));
183 } 158 }
184 return suggestions; 159 return suggestions;
185 } 160 }
186 161
187 void OfflinePageSuggestionsProvider::ClearDismissedSuggestionsForDebugging( 162 void OfflinePageSuggestionsProvider::ClearDismissedSuggestionsForDebugging(
188 Category category) { 163 Category category) {
189 if (category == recent_tabs_category_) { 164 StoreDismissedIDsToPrefs(category, std::set<std::string>());
190 DCHECK_NE(CategoryStatus::NOT_PROVIDED, recent_tabs_status_);
191 dismissed_recent_tab_ids_.clear();
192 StoreDismissedIDsToPrefs(prefs::kDismissedRecentOfflineTabSuggestions,
193 dismissed_recent_tab_ids_);
194 } else if (category == downloads_category_) {
195 DCHECK_NE(CategoryStatus::NOT_PROVIDED, downloads_status_);
196 dismissed_download_ids_.clear();
197 StoreDismissedIDsToPrefs(prefs::kDismissedDownloadSuggestions,
198 dismissed_download_ids_);
199 } else {
200 NOTREACHED() << "Unknown category " << category.id();
201 }
202 FetchOfflinePages(); 165 FetchOfflinePages();
203 } 166 }
204 167
205 void OfflinePageSuggestionsProvider::OfflinePageModelLoaded( 168 void OfflinePageSuggestionsProvider::OfflinePageModelLoaded(
206 OfflinePageModel* model) { 169 OfflinePageModel* model) {
207 DCHECK_EQ(offline_page_model_, model); 170 DCHECK_EQ(offline_page_model_, model);
208 } 171 }
209 172
210 void OfflinePageSuggestionsProvider::OfflinePageModelChanged( 173 void OfflinePageSuggestionsProvider::OfflinePageModelChanged(
211 OfflinePageModel* model) { 174 OfflinePageModel* model) {
212 DCHECK_EQ(offline_page_model_, model); 175 DCHECK_EQ(offline_page_model_, model);
213 FetchOfflinePages(); 176 FetchOfflinePages();
214 } 177 }
215 178
216 void OfflinePageSuggestionsProvider::OfflinePageDeleted( 179 void OfflinePageSuggestionsProvider::OfflinePageDeleted(
217 int64_t offline_id, 180 int64_t offline_id,
218 const offline_pages::ClientId& client_id) { 181 const offline_pages::ClientId& client_id) {
219 std::string offline_page_id = base::IntToString(offline_id);
220 if (recent_tabs_status_ != CategoryStatus::NOT_PROVIDED && 182 if (recent_tabs_status_ != CategoryStatus::NOT_PROVIDED &&
221 client_id.name_space == offline_pages::kLastNNamespace) { 183 client_id.name_space == offline_pages::kLastNNamespace) {
222 auto it = std::find(dismissed_recent_tab_ids_.begin(), 184 InvalidateSuggestion(recent_tabs_category_, offline_id);
223 dismissed_recent_tab_ids_.end(), offline_page_id);
224 if (it == dismissed_recent_tab_ids_.end()) {
225 observer()->OnSuggestionInvalidated(
226 this, recent_tabs_category_,
227 MakeUniqueID(recent_tabs_category_, offline_page_id));
228 } else {
229 dismissed_recent_tab_ids_.erase(it);
230 StoreDismissedIDsToPrefs(prefs::kDismissedRecentOfflineTabSuggestions,
231 dismissed_recent_tab_ids_);
232 }
233 } else if (downloads_status_ != CategoryStatus::NOT_PROVIDED && 185 } else if (downloads_status_ != CategoryStatus::NOT_PROVIDED &&
234 client_id.name_space == offline_pages::kAsyncNamespace && 186 client_id.name_space == offline_pages::kAsyncNamespace &&
235 base::IsValidGUID(client_id.id)) { 187 base::IsValidGUID(client_id.id)) {
236 auto it = std::find(dismissed_download_ids_.begin(), 188 InvalidateSuggestion(downloads_category_, offline_id);
237 dismissed_download_ids_.end(), offline_page_id);
238 if (it == dismissed_download_ids_.end()) {
239 observer()->OnSuggestionInvalidated(
240 this, downloads_category_,
241 MakeUniqueID(downloads_category_, offline_page_id));
242 } else {
243 dismissed_download_ids_.erase(it);
244 StoreDismissedIDsToPrefs(prefs::kDismissedDownloadSuggestions,
245 dismissed_download_ids_);
246 }
247 } 189 }
248 } 190 }
249 191
250 void OfflinePageSuggestionsProvider::FetchOfflinePages() { 192 void OfflinePageSuggestionsProvider::FetchOfflinePages() {
251 offline_page_model_->GetAllPages( 193 offline_page_model_->GetAllPages(
252 base::Bind(&OfflinePageSuggestionsProvider::OnOfflinePagesLoaded, 194 base::Bind(&OfflinePageSuggestionsProvider::OnOfflinePagesLoaded,
253 base::Unretained(this))); 195 base::Unretained(this)));
254 } 196 }
255 197
256 void OfflinePageSuggestionsProvider::OnOfflinePagesLoaded( 198 void OfflinePageSuggestionsProvider::OnOfflinePagesLoaded(
257 const MultipleOfflinePageItemResult& result) { 199 const MultipleOfflinePageItemResult& result) {
258 bool need_recent_tabs = recent_tabs_status_ != CategoryStatus::NOT_PROVIDED; 200 bool need_recent_tabs = recent_tabs_status_ != CategoryStatus::NOT_PROVIDED;
259 bool need_downloads = downloads_status_ != CategoryStatus::NOT_PROVIDED; 201 bool need_downloads = downloads_status_ != CategoryStatus::NOT_PROVIDED;
260 if (need_recent_tabs) 202 if (need_recent_tabs)
261 NotifyStatusChanged(recent_tabs_category_, CategoryStatus::AVAILABLE); 203 NotifyStatusChanged(recent_tabs_category_, CategoryStatus::AVAILABLE);
262 if (need_downloads) 204 if (need_downloads)
263 NotifyStatusChanged(downloads_category_, CategoryStatus::AVAILABLE); 205 NotifyStatusChanged(downloads_category_, CategoryStatus::AVAILABLE);
264 206
207 std::set<std::string> dismissed_recent_tab_ids =
208 ReadDismissedIDsFromPrefs(recent_tabs_category_);
209 std::set<std::string> dismissed_download_ids =
210 ReadDismissedIDsFromPrefs(downloads_category_);
211 std::set<std::string> cleaned_recent_tab_ids;
212 std::set<std::string> cleaned_download_ids;
265 std::vector<const OfflinePageItem*> recent_tab_items; 213 std::vector<const OfflinePageItem*> recent_tab_items;
266 std::vector<const OfflinePageItem*> download_items; 214 std::vector<const OfflinePageItem*> download_items;
267 for (const OfflinePageItem& item : result) { 215 for (const OfflinePageItem& item : result) {
216 std::string offline_page_id = base::IntToString(item.offline_id);
268 if (need_recent_tabs && 217 if (need_recent_tabs &&
269 item.client_id.name_space == offline_pages::kLastNNamespace && 218 item.client_id.name_space == offline_pages::kLastNNamespace) {
270 !dismissed_recent_tab_ids_.count(base::IntToString(item.offline_id))) { 219 if (dismissed_recent_tab_ids.count(offline_page_id)) {
Marc Treib 2016/08/16 11:48:37 nit: no braces required
Philipp Keck 2016/08/16 12:04:02 Done.
271 recent_tab_items.push_back(&item); 220 cleaned_recent_tab_ids.insert(offline_page_id);
Marc Treib 2016/08/16 11:48:37 Hm. This depends on us getting *all* offline pages
Philipp Keck 2016/08/16 12:04:02 Added comment in FetchOfflinePages, so that Justin
221 } else {
222 recent_tab_items.push_back(&item);
223 }
272 } 224 }
273 225
274 // TODO(pke): Use kDownloadNamespace once the OfflinePageModel uses that. 226 // TODO(pke): Use kDownloadNamespace once the OfflinePageModel uses that.
275 // The current logic is taken from DownloadUIAdapter::IsVisibleInUI. 227 // The current logic is taken from DownloadUIAdapter::IsVisibleInUI.
276 // Note: This is also copied in OfflinePageDeleted above. 228 // Note: This is also copied in |OfflinePageDeleted| above.
277 if (need_downloads && 229 if (need_downloads &&
278 item.client_id.name_space == offline_pages::kAsyncNamespace && 230 item.client_id.name_space == offline_pages::kAsyncNamespace &&
279 base::IsValidGUID(item.client_id.id) && 231 base::IsValidGUID(item.client_id.id)) {
280 !dismissed_download_ids_.count(base::IntToString(item.offline_id))) { 232 if (dismissed_download_ids.count(offline_page_id)) {
Marc Treib 2016/08/16 11:48:37 also here, no braces required
Philipp Keck 2016/08/16 12:04:02 Done.
281 download_items.push_back(&item); 233 cleaned_download_ids.insert(offline_page_id);
234 } else {
235 download_items.push_back(&item);
236 }
282 } 237 }
283 } 238 }
284 239
285 // TODO(pke): Once we have our OfflinePageModel getter and that doesn't do it 240 // TODO(pke): Once we have our OfflinePageModel getter and that doesn't do it
286 // already, filter out duplicate URLs for recent tabs here. Duplicates for 241 // already, filter out duplicate URLs for recent tabs here. Duplicates for
287 // downloads are fine. 242 // downloads are fine.
288 243
289 if (need_recent_tabs) { 244 if (need_recent_tabs) {
290 observer()->OnNewSuggestions( 245 observer()->OnNewSuggestions(
291 this, recent_tabs_category_, 246 this, recent_tabs_category_,
292 GetMostRecentlyVisited(recent_tabs_category_, 247 GetMostRecentlyVisited(recent_tabs_category_,
293 std::move(recent_tab_items))); 248 std::move(recent_tab_items)));
249 if (cleaned_recent_tab_ids.size() != dismissed_recent_tab_ids.size()) {
Marc Treib 2016/08/16 11:48:37 and here
Philipp Keck 2016/08/16 12:04:01 Done.
250 StoreDismissedIDsToPrefs(recent_tabs_category_, cleaned_recent_tab_ids);
251 }
294 } 252 }
295 if (need_downloads) { 253 if (need_downloads) {
296 observer()->OnNewSuggestions( 254 observer()->OnNewSuggestions(
297 this, downloads_category_, 255 this, downloads_category_,
298 GetMostRecentlyVisited(downloads_category_, std::move(download_items))); 256 GetMostRecentlyVisited(downloads_category_, std::move(download_items)));
257 if (cleaned_download_ids.size() != dismissed_download_ids.size()) {
Marc Treib 2016/08/16 11:48:37 and here :)
Philipp Keck 2016/08/16 12:04:02 Done.
258 StoreDismissedIDsToPrefs(downloads_category_, cleaned_download_ids);
259 }
299 } 260 }
300 } 261 }
301 262
302 void OfflinePageSuggestionsProvider::NotifyStatusChanged( 263 void OfflinePageSuggestionsProvider::NotifyStatusChanged(
303 Category category, 264 Category category,
304 CategoryStatus new_status) { 265 CategoryStatus new_status) {
305 if (category == recent_tabs_category_) { 266 if (category == recent_tabs_category_) {
306 DCHECK_NE(CategoryStatus::NOT_PROVIDED, recent_tabs_status_); 267 DCHECK_NE(CategoryStatus::NOT_PROVIDED, recent_tabs_status_);
307 if (recent_tabs_status_ == new_status) 268 if (recent_tabs_status_ == new_status)
308 return; 269 return;
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
349 OrderByMostRecentlyVisited()); 310 OrderByMostRecentlyVisited());
350 std::vector<ContentSuggestion> suggestions; 311 std::vector<ContentSuggestion> suggestions;
351 for (const OfflinePageItem* offline_page_item : offline_page_items) { 312 for (const OfflinePageItem* offline_page_item : offline_page_items) {
352 suggestions.push_back(ConvertOfflinePage(category, *offline_page_item)); 313 suggestions.push_back(ConvertOfflinePage(category, *offline_page_item));
353 if (suggestions.size() == kMaxSuggestionsCount) 314 if (suggestions.size() == kMaxSuggestionsCount)
354 break; 315 break;
355 } 316 }
356 return suggestions; 317 return suggestions;
357 } 318 }
358 319
320 void OfflinePageSuggestionsProvider::InvalidateSuggestion(Category category,
321 int64_t offline_id) {
322 std::string offline_page_id = base::IntToString(offline_id);
323 observer()->OnSuggestionInvalidated(this, category,
324 MakeUniqueID(category, offline_page_id));
325
326 std::set<std::string> dismissed_ids = ReadDismissedIDsFromPrefs(category);
327 auto it =
328 std::find(dismissed_ids.begin(), dismissed_ids.end(), offline_page_id);
329 if (it != dismissed_ids.end()) {
330 dismissed_ids.erase(it);
331 StoreDismissedIDsToPrefs(category, dismissed_ids);
332 }
333 }
334
335 std::string OfflinePageSuggestionsProvider::GetDismissedPref(
336 Category category) const {
337 if (category == recent_tabs_category_) {
338 return prefs::kDismissedRecentOfflineTabSuggestions;
339 } else if (category == downloads_category_) {
340 return prefs::kDismissedDownloadSuggestions;
341 } else {
342 NOTREACHED() << "Unknown category " << category.id();
343 return std::string();
344 }
345 }
346
359 std::set<std::string> OfflinePageSuggestionsProvider::ReadDismissedIDsFromPrefs( 347 std::set<std::string> OfflinePageSuggestionsProvider::ReadDismissedIDsFromPrefs(
360 const std::string& pref_name) const { 348 Category category) const {
361 std::set<std::string> dismissed_ids; 349 std::set<std::string> dismissed_ids;
362 const base::ListValue* list = pref_service_->GetList(pref_name); 350 const base::ListValue* list =
351 pref_service_->GetList(GetDismissedPref(category));
363 for (const std::unique_ptr<base::Value>& value : *list) { 352 for (const std::unique_ptr<base::Value>& value : *list) {
364 std::string dismissed_id; 353 std::string dismissed_id;
365 bool success = value->GetAsString(&dismissed_id); 354 bool success = value->GetAsString(&dismissed_id);
366 DCHECK(success) << "Failed to parse dismissed offline page ID from prefs"; 355 DCHECK(success) << "Failed to parse dismissed offline page ID from prefs";
367 dismissed_ids.insert(dismissed_id); 356 dismissed_ids.insert(dismissed_id);
368 } 357 }
369 return dismissed_ids; 358 return dismissed_ids;
370 } 359 }
371 360
372 void OfflinePageSuggestionsProvider::StoreDismissedIDsToPrefs( 361 void OfflinePageSuggestionsProvider::StoreDismissedIDsToPrefs(
373 const std::string& pref_name, 362 Category category,
374 const std::set<std::string>& dismissed_ids) { 363 const std::set<std::string>& dismissed_ids) {
375 base::ListValue list; 364 base::ListValue list;
376 for (const std::string& dismissed_id : dismissed_ids) 365 for (const std::string& dismissed_id : dismissed_ids)
377 list.AppendString(dismissed_id); 366 list.AppendString(dismissed_id);
378 pref_service_->Set(pref_name, list); 367 pref_service_->Set(GetDismissedPref(category), list);
379 } 368 }
380 369
381 } // namespace ntp_snippets 370 } // namespace ntp_snippets
OLDNEW
« no previous file with comments | « components/ntp_snippets/offline_pages/offline_page_suggestions_provider.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698