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

Side by Side Diff: chrome/browser/services/gcm/gcm_profile_service.cc

Issue 165993005: [GCM] Make sure GCM checkout logic is invoked when the profile is signed out (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address more feedback Created 6 years, 10 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2013 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 "chrome/browser/services/gcm/gcm_profile_service.h" 5 #include "chrome/browser/services/gcm/gcm_profile_service.h"
6 6
7 #include "base/base64.h" 7 #include "base/base64.h"
8 #include "base/files/file_path.h" 8 #include "base/files/file_path.h"
9 #include "base/logging.h" 9 #include "base/logging.h"
10 #include "base/path_service.h" 10 #include "base/path_service.h"
(...skipping 237 matching lines...) Expand 10 before | Expand all | Expand 10 after
248 const std::string& app_id, 248 const std::string& app_id,
249 const GCMClient::IncomingMessage& message) OVERRIDE; 249 const GCMClient::IncomingMessage& message) OVERRIDE;
250 virtual void OnMessagesDeleted(const std::string& app_id) OVERRIDE; 250 virtual void OnMessagesDeleted(const std::string& app_id) OVERRIDE;
251 virtual void OnMessageSendError(const std::string& app_id, 251 virtual void OnMessageSendError(const std::string& app_id,
252 const std::string& message_id, 252 const std::string& message_id,
253 GCMClient::Result result) OVERRIDE; 253 GCMClient::Result result) OVERRIDE;
254 virtual void OnGCMReady() OVERRIDE; 254 virtual void OnGCMReady() OVERRIDE;
255 255
256 // Called on IO thread. 256 // Called on IO thread.
257 void Initialize( 257 void Initialize(
258 GCMClientFactory* gcm_client_factory, 258 scoped_ptr<GCMClientFactory> gcm_client_factory,
259 const base::FilePath& store_path, 259 const base::FilePath& store_path,
260 const scoped_refptr<net::URLRequestContextGetter>& 260 const scoped_refptr<net::URLRequestContextGetter>&
261 url_request_context_getter); 261 url_request_context_getter);
262 void Reset(); 262 void Reset();
263 void CheckIn();
Nicolas Zea 2014/02/21 20:58:58 this should be named Load as well
jianli 2014/02/22 00:03:06 Done.
263 void CheckOut(); 264 void CheckOut();
264 void Register(const std::string& app_id, 265 void Register(const std::string& app_id,
265 const std::vector<std::string>& sender_ids, 266 const std::vector<std::string>& sender_ids,
266 const std::string& cert); 267 const std::string& cert);
267 void Unregister(const std::string& app_id); 268 void Unregister(const std::string& app_id);
268 void Send(const std::string& app_id, 269 void Send(const std::string& app_id,
269 const std::string& receiver_id, 270 const std::string& receiver_id,
270 const GCMClient::OutgoingMessage& message); 271 const GCMClient::OutgoingMessage& message);
271 272
273 // For testing purpose. Can be called from UI thread. Use with care.
274 GCMClient* gcm_client_for_testing() const { return gcm_client_.get(); }
275
272 private: 276 private:
273 friend class base::RefCountedThreadSafe<IOWorker>; 277 friend class base::RefCountedThreadSafe<IOWorker>;
274 virtual ~IOWorker(); 278 virtual ~IOWorker();
275 279
276 const base::WeakPtr<GCMProfileService> service_; 280 const base::WeakPtr<GCMProfileService> service_;
277 281
278 scoped_ptr<GCMClient> gcm_client_; 282 scoped_ptr<GCMClient> gcm_client_;
279 }; 283 };
280 284
281 GCMProfileService::IOWorker::IOWorker( 285 GCMProfileService::IOWorker::IOWorker(
282 const base::WeakPtr<GCMProfileService>& service) 286 const base::WeakPtr<GCMProfileService>& service)
283 : service_(service) { 287 : service_(service) {
284 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 288 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
285 } 289 }
286 290
287 GCMProfileService::IOWorker::~IOWorker() { 291 GCMProfileService::IOWorker::~IOWorker() {
288 } 292 }
289 293
290 void GCMProfileService::IOWorker::Initialize( 294 void GCMProfileService::IOWorker::Initialize(
291 GCMClientFactory* gcm_client_factory, 295 scoped_ptr<GCMClientFactory> gcm_client_factory,
292 const base::FilePath& store_path, 296 const base::FilePath& store_path,
293 const scoped_refptr<net::URLRequestContextGetter>& 297 const scoped_refptr<net::URLRequestContextGetter>&
294 url_request_context_getter) { 298 url_request_context_getter) {
295 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 299 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
296 300
297 gcm_client_ = gcm_client_factory->BuildInstance().Pass(); 301 gcm_client_ = gcm_client_factory->BuildInstance().Pass();
298 302
299 checkin_proto::ChromeBuildProto chrome_build_proto; 303 checkin_proto::ChromeBuildProto chrome_build_proto;
300 chrome_build_proto.set_platform(GetPlatform()); 304 chrome_build_proto.set_platform(GetPlatform());
301 chrome_build_proto.set_chrome_version(GetVersion()); 305 chrome_build_proto.set_chrome_version(GetVersion());
302 chrome_build_proto.set_channel(GetChannel()); 306 chrome_build_proto.set_channel(GetChannel());
303 307
304 scoped_refptr<base::SequencedWorkerPool> worker_pool( 308 scoped_refptr<base::SequencedWorkerPool> worker_pool(
305 content::BrowserThread::GetBlockingPool()); 309 content::BrowserThread::GetBlockingPool());
306 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner( 310 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner(
307 worker_pool->GetSequencedTaskRunnerWithShutdownBehavior( 311 worker_pool->GetSequencedTaskRunnerWithShutdownBehavior(
308 worker_pool->GetSequenceToken(), 312 worker_pool->GetSequenceToken(),
309 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)); 313 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN));
310 314
311 gcm_client_->Initialize(chrome_build_proto, 315 gcm_client_->Initialize(chrome_build_proto,
312 store_path, 316 store_path,
313 blocking_task_runner, 317 blocking_task_runner,
314 url_request_context_getter, 318 url_request_context_getter,
315 this); 319 this);
316 320
317 content::BrowserThread::PostTask( 321 content::BrowserThread::PostTask(
318 content::BrowserThread::UI, 322 content::BrowserThread::UI,
319 FROM_HERE, 323 FROM_HERE,
320 base::Bind(&GCMProfileService::FinishInitializationOnUI, 324 base::Bind(&GCMProfileService::FinishInitializationOnUI, service_));
321 service_,
322 gcm_client_->IsReady()));
323 } 325 }
324 326
325 void GCMProfileService::IOWorker::Reset() { 327 void GCMProfileService::IOWorker::Reset() {
326 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 328 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
327 329
328 // GCMClient instance must be destroyed from the same thread where it was 330 // GCMClient instance must be destroyed from the same thread where it was
329 // created. 331 // created.
330 gcm_client_.reset(); 332 gcm_client_.reset();
331 } 333 }
332 334
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
410 } 412 }
411 413
412 void GCMProfileService::IOWorker::OnGCMReady() { 414 void GCMProfileService::IOWorker::OnGCMReady() {
413 content::BrowserThread::PostTask( 415 content::BrowserThread::PostTask(
414 content::BrowserThread::UI, 416 content::BrowserThread::UI,
415 FROM_HERE, 417 FROM_HERE,
416 base::Bind(&GCMProfileService::GCMClientReady, 418 base::Bind(&GCMProfileService::GCMClientReady,
417 service_)); 419 service_));
418 } 420 }
419 421
422 void GCMProfileService::IOWorker::CheckIn() {
423 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
424
425 // This will load the data from the gcm store and trigger the check-in if
426 // the persisted check-in info is not found.
427 gcm_client_->Load();
428 }
429
420 void GCMProfileService::IOWorker::CheckOut() { 430 void GCMProfileService::IOWorker::CheckOut() {
421 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 431 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
422 432
423 gcm_client_->CheckOut(); 433 gcm_client_->CheckOut();
424 gcm_client_.reset();
Nicolas Zea 2014/02/21 20:58:58 why was this removed?
jianli 2014/02/22 00:03:06 We need to keep GCMClient instance alive in case t
425 } 434 }
426 435
427 void GCMProfileService::IOWorker::Register( 436 void GCMProfileService::IOWorker::Register(
428 const std::string& app_id, 437 const std::string& app_id,
429 const std::vector<std::string>& sender_ids, 438 const std::vector<std::string>& sender_ids,
430 const std::string& cert) { 439 const std::string& cert) {
431 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 440 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
432 441
433 gcm_client_->Register(app_id, cert, sender_ids); 442 gcm_client_->Register(app_id, cert, sender_ids);
434 } 443 }
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
498 testing_delegate_(NULL), 507 testing_delegate_(NULL),
499 weak_ptr_factory_(this) { 508 weak_ptr_factory_(this) {
500 DCHECK(!profile->IsOffTheRecord()); 509 DCHECK(!profile->IsOffTheRecord());
501 } 510 }
502 511
503 GCMProfileService::~GCMProfileService() { 512 GCMProfileService::~GCMProfileService() {
504 } 513 }
505 514
506 void GCMProfileService::Initialize( 515 void GCMProfileService::Initialize(
507 scoped_ptr<GCMClientFactory> gcm_client_factory) { 516 scoped_ptr<GCMClientFactory> gcm_client_factory) {
508 gcm_client_factory_ = gcm_client_factory.Pass();
509
510 // This has to be done first since CheckIn depends on it.
511 io_worker_ = new IOWorker(weak_ptr_factory_.GetWeakPtr());
512
513 #if !defined(OS_ANDROID)
514 js_event_router_.reset(new extensions::GcmJsEventRouter(profile_));
515 #endif
516
517 registrar_.Add(this, 517 registrar_.Add(this,
518 chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL, 518 chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL,
519 content::Source<Profile>(profile_)); 519 content::Source<Profile>(profile_));
520 registrar_.Add(this, 520 registrar_.Add(this,
521 chrome::NOTIFICATION_GOOGLE_SIGNED_OUT, 521 chrome::NOTIFICATION_GOOGLE_SIGNED_OUT,
522 content::Source<Profile>(profile_)); 522 content::Source<Profile>(profile_));
523 registrar_.Add(this, 523 registrar_.Add(this,
524 chrome::NOTIFICATION_PROFILE_DESTROYED, 524 chrome::NOTIFICATION_PROFILE_DESTROYED,
525 content::Source<Profile>(profile_)); 525 content::Source<Profile>(profile_));
526 // TODO(jianli): move extension specific logic out of GCMProfileService. 526 // TODO(jianli): move extension specific logic out of GCMProfileService.
527 registrar_.Add(this, 527 registrar_.Add(this,
528 chrome:: NOTIFICATION_EXTENSION_UNINSTALLED, 528 chrome:: NOTIFICATION_EXTENSION_UNINSTALLED,
529 content::Source<Profile>(profile_)); 529 content::Source<Profile>(profile_));
530 530
531 // In case that the profile has been signed in before GCMProfileService is 531 // Create and initialize the GCMClient. Note that this does not initiate the
532 // created. 532 // GCM check-in.
533 SigninManagerBase* manager = SigninManagerFactory::GetForProfile(profile_); 533 io_worker_ = new IOWorker(weak_ptr_factory_.GetWeakPtr());
534 if (manager) { 534 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter =
535 std::string username = manager->GetAuthenticatedUsername(); 535 profile_->GetRequestContext();
536 if (!username.empty()) 536 content::BrowserThread::PostTask(
537 CheckIn(username); 537 content::BrowserThread::IO,
538 } 538 FROM_HERE,
539 base::Bind(&GCMProfileService::IOWorker::Initialize,
540 io_worker_,
541 base::Passed(&gcm_client_factory),
542 profile_->GetPath().Append(chrome::kGCMStoreDirname),
543 url_request_context_getter));
539 } 544 }
540 545
541 void GCMProfileService::Register(const std::string& app_id, 546 void GCMProfileService::Register(const std::string& app_id,
542 const std::vector<std::string>& sender_ids, 547 const std::vector<std::string>& sender_ids,
543 const std::string& cert, 548 const std::string& cert,
544 RegisterCallback callback) { 549 RegisterCallback callback) {
545 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 550 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
546 DCHECK(!app_id.empty() && !sender_ids.empty() && !callback.is_null()); 551 DCHECK(!app_id.empty() && !sender_ids.empty() && !callback.is_null());
547 552
548 // If the profile was not signed in, bail out. 553 // If the profile was not signed in, bail out.
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after
667 content::BrowserThread::PostTask( 672 content::BrowserThread::PostTask(
668 content::BrowserThread::IO, 673 content::BrowserThread::IO,
669 FROM_HERE, 674 FROM_HERE,
670 base::Bind(&GCMProfileService::IOWorker::Send, 675 base::Bind(&GCMProfileService::IOWorker::Send,
671 io_worker_, 676 io_worker_,
672 app_id, 677 app_id,
673 receiver_id, 678 receiver_id,
674 message)); 679 message));
675 } 680 }
676 681
682 GCMClient* GCMProfileService::GetGCMClientForTesting() const {
683 return io_worker_ ? io_worker_->gcm_client_for_testing() : NULL;
684 }
685
677 void GCMProfileService::Observe(int type, 686 void GCMProfileService::Observe(int type,
678 const content::NotificationSource& source, 687 const content::NotificationSource& source,
679 const content::NotificationDetails& details) { 688 const content::NotificationDetails& details) {
680 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 689 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
681 690
682 switch (type) { 691 switch (type) {
683 case chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL: { 692 case chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL: {
684 const GoogleServiceSigninSuccessDetails* signin_details = 693 if (IsGCMChannelEnabled())
685 content::Details<GoogleServiceSigninSuccessDetails>(details).ptr(); 694 EnsureCheckedIn();
686 // This could be called multiple times when the password changed.
687 if (username_ != signin_details->username)
688 CheckIn(signin_details->username);
689 break; 695 break;
690 } 696 }
691 case chrome::NOTIFICATION_GOOGLE_SIGNED_OUT: 697 case chrome::NOTIFICATION_GOOGLE_SIGNED_OUT:
692 CheckOut(); 698 CheckOut();
693 break; 699 break;
694 case chrome::NOTIFICATION_PROFILE_DESTROYED: 700 case chrome::NOTIFICATION_PROFILE_DESTROYED:
695 ResetGCMClient(); 701 ResetGCMClient();
696 break; 702 break;
697 case chrome:: NOTIFICATION_EXTENSION_UNINSTALLED: { 703 case chrome:: NOTIFICATION_EXTENSION_UNINSTALLED:
698 extensions::Extension* extension = 704 if (!username_.empty()) {
699 content::Details<extensions::Extension>(details).ptr(); 705 extensions::Extension* extension =
700 Unregister(extension->id()); 706 content::Details<extensions::Extension>(details).ptr();
707 Unregister(extension->id());
708 }
701 break; 709 break;
702 }
703 default: 710 default:
704 NOTREACHED(); 711 NOTREACHED();
705 } 712 }
706 } 713 }
707 714
708 void GCMProfileService::CheckIn(const std::string& username) { 715 bool GCMProfileService::IsGCMChannelEnabled() const {
709 DCHECK(!username.empty() && username_.empty()); 716 const base::Value* gcm_enabled_value =
717 profile_->GetPrefs()->GetUserPrefValue(prefs::kGCMChannelEnabled);
718 bool gcm_enabled = false;
719 return gcm_enabled_value &&
720 gcm_enabled_value->GetAsBoolean(&gcm_enabled) &&
721 gcm_enabled;
722 }
723
724 void GCMProfileService::EnsureCheckedIn() {
725 SigninManagerBase* manager = SigninManagerFactory::GetForProfile(profile_);
726 if (!manager)
727 return;
728 std::string username = manager->GetAuthenticatedUsername();
729 if (username.empty())
730 return;
731
732 // CheckIn could be called more than once when:
733 // 1) The password changes.
734 // 2) Register/send function calls it to ensure CheckIn is done.
735 if (username_ == username)
736 return;
710 username_ = username; 737 username_ = username;
711 738
739 #if !defined(OS_ANDROID)
740 if (!js_event_router_)
741 js_event_router_.reset(new extensions::GcmJsEventRouter(profile_));
742 #endif
743
712 DCHECK(!delayed_task_controller_); 744 DCHECK(!delayed_task_controller_);
713 delayed_task_controller_.reset(new DelayedTaskController); 745 delayed_task_controller_.reset(new DelayedTaskController);
714 746
715 // Load all register apps. 747 // Load all the registered apps.
716 ReadRegisteredAppIDs(); 748 ReadRegisteredAppIDs();
717 749
718 // Let the IO thread create and initialize GCMClient. 750 // Initiate the GCM check-in.
Nicolas Zea 2014/02/21 20:58:58 nit: the GCM load
jianli 2014/02/22 00:03:06 Done.
719 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter =
720 profile_->GetRequestContext();
721 content::BrowserThread::PostTask( 751 content::BrowserThread::PostTask(
722 content::BrowserThread::IO, 752 content::BrowserThread::IO,
723 FROM_HERE, 753 FROM_HERE,
724 base::Bind(&GCMProfileService::IOWorker::Initialize, 754 base::Bind(&GCMProfileService::IOWorker::CheckIn, io_worker_));
725 io_worker_,
726 gcm_client_factory_.get(),
727 profile_->GetPath().Append(chrome::kGCMStoreDirname),
728 url_request_context_getter));
729 } 755 }
730 756
731 void GCMProfileService::CheckOut() { 757 void GCMProfileService::CheckOut() {
732 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 758 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
733 759
734 DCHECK(!username_.empty()); 760 // We still proceed with the check-out logic even if the check-in is not
761 // initiated in the current session. This will make sure that all the
762 // persisted data written previously will get purged.
735 username_.clear(); 763 username_.clear();
736 764
737 // Remove persisted data from app's state store. 765 // Remove persisted data from app's state store.
738 for (RegistrationInfoMap::const_iterator iter = 766 for (RegistrationInfoMap::const_iterator iter =
739 registration_info_map_.begin(); 767 registration_info_map_.begin();
740 iter != registration_info_map_.end(); ++iter) { 768 iter != registration_info_map_.end(); ++iter) {
741 DeleteRegistrationInfo(iter->first); 769 DeleteRegistrationInfo(iter->first);
742 } 770 }
743 771
744 // Remove persisted data from prefs store. 772 // Remove persisted data from prefs store.
745 profile_->GetPrefs()->ClearPref(prefs::kGCMChannelEnabled);
746 profile_->GetPrefs()->ClearPref(prefs::kGCMRegisteredAppIDs); 773 profile_->GetPrefs()->ClearPref(prefs::kGCMRegisteredAppIDs);
747 774
748 gcm_client_ready_ = false; 775 gcm_client_ready_ = false;
749 delayed_task_controller_.reset(); 776 delayed_task_controller_.reset();
750 register_callbacks_.clear(); 777 register_callbacks_.clear();
751 send_callbacks_.clear(); 778 send_callbacks_.clear();
752 registration_info_map_.clear(); 779 registration_info_map_.clear();
753 780
754 content::BrowserThread::PostTask( 781 content::BrowserThread::PostTask(
755 content::BrowserThread::IO, 782 content::BrowserThread::IO,
(...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after
881 GCMClient::Result result) { 908 GCMClient::Result result) {
882 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 909 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
883 910
884 // Drop the event if signed out. 911 // Drop the event if signed out.
885 if (username_.empty()) 912 if (username_.empty())
886 return; 913 return;
887 914
888 GetEventRouter(app_id)->OnSendError(app_id, message_id, result); 915 GetEventRouter(app_id)->OnSendError(app_id, message_id, result);
889 } 916 }
890 917
891 void GCMProfileService::FinishInitializationOnUI(bool ready) { 918 void GCMProfileService::FinishInitializationOnUI() {
892 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 919 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
893 920
894 gcm_client_ready_ = ready; 921 // Initiates the check-in if the rollout signal indicates yes.
895 if (gcm_client_ready_) 922 if (IsGCMChannelEnabled())
896 delayed_task_controller_->SetGCMReady(); 923 EnsureCheckedIn();
897 } 924 }
898 925
899 void GCMProfileService::GCMClientReady() { 926 void GCMProfileService::GCMClientReady() {
900 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 927 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
901 928
902 if (gcm_client_ready_) 929 if (gcm_client_ready_)
903 return; 930 return;
904 gcm_client_ready_ = true; 931 gcm_client_ready_ = true;
905 932
906 delayed_task_controller_->SetGCMReady(); 933 delayed_task_controller_->SetGCMReady();
(...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after
1028 1055
1029 return true; 1056 return true;
1030 } 1057 }
1031 1058
1032 // static 1059 // static
1033 const char* GCMProfileService::GetPersistentRegisterKeyForTesting() { 1060 const char* GCMProfileService::GetPersistentRegisterKeyForTesting() {
1034 return kRegistrationKey; 1061 return kRegistrationKey;
1035 } 1062 }
1036 1063
1037 } // namespace gcm 1064 } // namespace gcm
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698