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

Side by Side Diff: chrome/browser/download/download_file_manager.cc

Issue 6969009: Reduced the lifetime of DownloadCreateInfo. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Merged with trunk Created 9 years, 7 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) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 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/download/download_file_manager.h" 5 #include "chrome/browser/download/download_file_manager.h"
6 6
7 #include "base/file_util.h" 7 #include "base/file_util.h"
8 #include "base/logging.h" 8 #include "base/logging.h"
9 #include "base/stl_util-inl.h" 9 #include "base/stl_util-inl.h"
10 #include "base/task.h" 10 #include "base/task.h"
11 #include "base/utf_string_conversions.h" 11 #include "base/utf_string_conversions.h"
12 #include "build/build_config.h" 12 #include "build/build_config.h"
13 #include "chrome/browser/download/download_create_info.h"
13 #include "chrome/browser/download/download_manager.h" 14 #include "chrome/browser/download/download_manager.h"
14 #include "chrome/browser/download/download_process_handle.h" 15 #include "chrome/browser/download/download_process_handle.h"
15 #include "chrome/browser/download/download_util.h" 16 #include "chrome/browser/download/download_util.h"
16 #include "chrome/browser/history/download_create_info.h"
17 #include "chrome/browser/net/chrome_url_request_context.h" 17 #include "chrome/browser/net/chrome_url_request_context.h"
18 #include "chrome/browser/platform_util.h" 18 #include "chrome/browser/platform_util.h"
19 #include "chrome/browser/profiles/profile.h" 19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/safe_browsing/safe_browsing_service.h" 20 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
21 #include "chrome/browser/tab_contents/tab_util.h" 21 #include "chrome/browser/tab_contents/tab_util.h"
22 #include "content/browser/browser_thread.h" 22 #include "content/browser/browser_thread.h"
23 #include "content/browser/renderer_host/resource_dispatcher_host.h" 23 #include "content/browser/renderer_host/resource_dispatcher_host.h"
24 #include "content/browser/tab_contents/tab_contents.h" 24 #include "content/browser/tab_contents/tab_contents.h"
25 #include "googleurl/src/gurl.h" 25 #include "googleurl/src/gurl.h"
26 #include "net/base/io_buffer.h" 26 #include "net/base/io_buffer.h"
(...skipping 24 matching lines...) Expand all
51 51
52 void DownloadFileManager::OnShutdown() { 52 void DownloadFileManager::OnShutdown() {
53 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 53 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
54 StopUpdateTimer(); 54 StopUpdateTimer();
55 STLDeleteValues(&downloads_); 55 STLDeleteValues(&downloads_);
56 } 56 }
57 57
58 void DownloadFileManager::CreateDownloadFile(DownloadCreateInfo* info, 58 void DownloadFileManager::CreateDownloadFile(DownloadCreateInfo* info,
59 DownloadManager* download_manager, 59 DownloadManager* download_manager,
60 bool get_hash) { 60 bool get_hash) {
61 DCHECK(info);
61 VLOG(20) << __FUNCTION__ << "()" << " info = " << info->DebugString(); 62 VLOG(20) << __FUNCTION__ << "()" << " info = " << info->DebugString();
62 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 63 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
63 64
65 // Life of |info| ends here. No more references to it after this method.
66 scoped_ptr<DownloadCreateInfo> infop(info);
67
64 scoped_ptr<DownloadFile> 68 scoped_ptr<DownloadFile>
65 download_file(new DownloadFile(info, download_manager)); 69 download_file(new DownloadFile(info, download_manager));
66 if (!download_file->Initialize(get_hash)) { 70 if (!download_file->Initialize(get_hash)) {
67 download_util::CancelDownloadRequest(resource_dispatcher_host_, 71 download_util::CancelDownloadRequest(resource_dispatcher_host_,
68 info->process_handle); 72 info->process_handle);
69 delete info;
70 return; 73 return;
71 } 74 }
72 75
73 DCHECK(GetDownloadFile(info->download_id) == NULL); 76 int32 id = info->download_id;
74 downloads_[info->download_id] = download_file.release(); 77 DCHECK(GetDownloadFile(id) == NULL);
75 // TODO(phajdan.jr): fix the duplication of path info below. 78 downloads_[id] = download_file.release();
76 info->path = info->save_info.file_path;
77 79
78 // The file is now ready, we can un-pause the request and start saving data. 80 // The file is now ready, we can un-pause the request and start saving data.
79 BrowserThread::PostTask( 81 BrowserThread::PostTask(
80 BrowserThread::IO, FROM_HERE, 82 BrowserThread::IO, FROM_HERE,
81 NewRunnableMethod(this, &DownloadFileManager::ResumeDownloadRequest, 83 NewRunnableMethod(this, &DownloadFileManager::ResumeDownloadRequest,
82 info->process_handle)); 84 info->process_handle));
83 85
84 StartUpdateTimer(); 86 StartUpdateTimer();
85 87
86 BrowserThread::PostTask( 88 BrowserThread::PostTask(
87 BrowserThread::UI, FROM_HERE, 89 BrowserThread::UI, FROM_HERE,
88 NewRunnableMethod(download_manager, 90 NewRunnableMethod(download_manager,
89 &DownloadManager::StartDownload, info)); 91 &DownloadManager::StartDownload, id));
90 } 92 }
91 93
92 void DownloadFileManager::ResumeDownloadRequest( 94 void DownloadFileManager::ResumeDownloadRequest(
93 DownloadProcessHandle process) { 95 DownloadProcessHandle process) {
94 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 96 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
95 97
96 // This balances the pause in DownloadResourceHandler::OnResponseStarted. 98 // This balances the pause in DownloadResourceHandler::OnResponseStarted.
97 resource_dispatcher_host_->PauseRequest(process.child_id(), 99 resource_dispatcher_host_->PauseRequest(process.child_id(),
98 process.request_id(), 100 process.request_id(),
99 false); 101 false);
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
144 DCHECK(info); 146 DCHECK(info);
145 147
146 DownloadManager* manager = info->process_handle.GetDownloadManager(); 148 DownloadManager* manager = info->process_handle.GetDownloadManager();
147 if (!manager) { 149 if (!manager) {
148 download_util::CancelDownloadRequest(resource_dispatcher_host_, 150 download_util::CancelDownloadRequest(resource_dispatcher_host_,
149 info->process_handle); 151 info->process_handle);
150 delete info; 152 delete info;
151 return; 153 return;
152 } 154 }
153 155
156 // TODO(phajdan.jr): fix the duplication of path info below.
157 info->path = info->save_info.file_path;
158
154 manager->CreateDownloadItem(info); 159 manager->CreateDownloadItem(info);
155 160
156 bool hash_needed = resource_dispatcher_host_->safe_browsing_service()-> 161 bool hash_needed = resource_dispatcher_host_->safe_browsing_service()->
157 DownloadBinHashNeeded(); 162 DownloadBinHashNeeded();
158 163
159 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 164 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
160 NewRunnableMethod(this, &DownloadFileManager::CreateDownloadFile, 165 NewRunnableMethod(this, &DownloadFileManager::CreateDownloadFile,
161 info, 166 info, make_scoped_refptr(manager), hash_needed));
162 make_scoped_refptr(manager), hash_needed));
163 } 167 }
164 168
165 // We don't forward an update to the UI thread here, since we want to throttle 169 // We don't forward an update to the UI thread here, since we want to throttle
166 // the UI update rate via a periodic timer. If the user has cancelled the 170 // the UI update rate via a periodic timer. If the user has cancelled the
167 // download (in the UI thread), we may receive a few more updates before the IO 171 // download (in the UI thread), we may receive a few more updates before the IO
168 // thread gets the cancel message: we just delete the data since the 172 // thread gets the cancel message: we just delete the data since the
169 // DownloadFile has been deleted. 173 // DownloadFile has been deleted.
170 void DownloadFileManager::UpdateDownload(int id, DownloadBuffer* buffer) { 174 void DownloadFileManager::UpdateDownload(int id, DownloadBuffer* buffer) {
171 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 175 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
172 std::vector<DownloadBuffer::Contents> contents; 176 std::vector<DownloadBuffer::Contents> contents;
173 { 177 {
174 base::AutoLock auto_lock(buffer->lock); 178 base::AutoLock auto_lock(buffer->lock);
175 contents.swap(buffer->contents); 179 contents.swap(buffer->contents);
176 } 180 }
177 181
178 DownloadFile* download = GetDownloadFile(id); 182 DownloadFile* download_file = GetDownloadFile(id);
179 for (size_t i = 0; i < contents.size(); ++i) { 183 for (size_t i = 0; i < contents.size(); ++i) {
180 net::IOBuffer* data = contents[i].first; 184 net::IOBuffer* data = contents[i].first;
181 const int data_len = contents[i].second; 185 const int data_len = contents[i].second;
182 if (download) 186 if (download_file)
183 download->AppendDataToFile(data->data(), data_len); 187 download_file->AppendDataToFile(data->data(), data_len);
184 data->Release(); 188 data->Release();
185 } 189 }
186 } 190 }
187 191
188 void DownloadFileManager::OnResponseCompleted( 192 void DownloadFileManager::OnResponseCompleted(
189 int id, 193 int id,
190 DownloadBuffer* buffer, 194 DownloadBuffer* buffer,
191 int os_error, 195 int os_error,
192 const std::string& security_info) { 196 const std::string& security_info) {
193 VLOG(20) << __FUNCTION__ << "()" << " id = " << id 197 VLOG(20) << __FUNCTION__ << "()" << " id = " << id
194 << " os_error = " << os_error 198 << " os_error = " << os_error
195 << " security_info = \"" << security_info << "\""; 199 << " security_info = \"" << security_info << "\"";
196 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 200 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
197 delete buffer; 201 delete buffer;
198 DownloadFile* download = GetDownloadFile(id); 202 DownloadFile* download_file = GetDownloadFile(id);
199 if (!download) 203 if (!download_file)
200 return; 204 return;
201 205
202 download->Finish(); 206 download_file->Finish();
203 207
204 DownloadManager* download_manager = download->GetDownloadManager(); 208 DownloadManager* download_manager = download_file->GetDownloadManager();
205 if (!download_manager) { 209 if (!download_manager) {
206 CancelDownload(id); 210 CancelDownload(id);
207 return; 211 return;
208 } 212 }
209 213
210 std::string hash; 214 std::string hash;
211 if (!download->GetSha256Hash(&hash)) 215 if (!download_file->GetSha256Hash(&hash))
212 hash.clear(); 216 hash.clear();
213 217
214 BrowserThread::PostTask( 218 BrowserThread::PostTask(
215 BrowserThread::UI, FROM_HERE, 219 BrowserThread::UI, FROM_HERE,
216 NewRunnableMethod( 220 NewRunnableMethod(
217 download_manager, &DownloadManager::OnResponseCompleted, 221 download_manager, &DownloadManager::OnResponseCompleted,
218 id, download->bytes_so_far(), os_error, hash)); 222 id, download_file->bytes_so_far(), os_error, hash));
219 // We need to keep the download around until the UI thread has finalized 223 // We need to keep the download around until the UI thread has finalized
220 // the name. 224 // the name.
221 } 225 }
222 226
223 // This method will be sent via a user action, or shutdown on the UI thread, and 227 // This method will be sent via a user action, or shutdown on the UI thread, and
224 // run on the download thread. Since this message has been sent from the UI 228 // run on the download thread. Since this message has been sent from the UI
225 // thread, the download may have already completed and won't exist in our map. 229 // thread, the download may have already completed and won't exist in our map.
226 void DownloadFileManager::CancelDownload(int id) { 230 void DownloadFileManager::CancelDownload(int id) {
227 VLOG(20) << __FUNCTION__ << "()" << " id = " << id; 231 VLOG(20) << __FUNCTION__ << "()" << " id = " << id;
228 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 232 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
229 DownloadFileMap::iterator it = downloads_.find(id); 233 DownloadFileMap::iterator it = downloads_.find(id);
230 if (it == downloads_.end()) 234 if (it == downloads_.end())
231 return; 235 return;
232 236
233 DownloadFile* download = it->second; 237 DownloadFile* download_file = it->second;
234 VLOG(20) << __FUNCTION__ << "()" 238 VLOG(20) << __FUNCTION__ << "()"
235 << " download = " << download->DebugString(); 239 << " download_file = " << download_file->DebugString();
236 download->Cancel(); 240 download_file->Cancel();
237 241
238 EraseDownload(id); 242 EraseDownload(id);
239 } 243 }
240 244
241 void DownloadFileManager::CompleteDownload(int id) { 245 void DownloadFileManager::CompleteDownload(int id) {
242 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 246 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
243 247
244 if (!ContainsKey(downloads_, id)) 248 if (!ContainsKey(downloads_, id))
245 return; 249 return;
246 250
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
284 // 288 //
285 // There are 2 possible rename cases where this method can be called: 289 // There are 2 possible rename cases where this method can be called:
286 // 1. tmp -> foo.crdownload (not final, safe) 290 // 1. tmp -> foo.crdownload (not final, safe)
287 // 2. tmp-> Unconfirmed.xxx.crdownload (not final, dangerous) 291 // 2. tmp-> Unconfirmed.xxx.crdownload (not final, dangerous)
288 void DownloadFileManager::RenameInProgressDownloadFile( 292 void DownloadFileManager::RenameInProgressDownloadFile(
289 int id, const FilePath& full_path) { 293 int id, const FilePath& full_path) {
290 VLOG(20) << __FUNCTION__ << "()" << " id = " << id 294 VLOG(20) << __FUNCTION__ << "()" << " id = " << id
291 << " full_path = \"" << full_path.value() << "\""; 295 << " full_path = \"" << full_path.value() << "\"";
292 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 296 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
293 297
294 DownloadFile* download = GetDownloadFile(id); 298 DownloadFile* download_file = GetDownloadFile(id);
295 if (!download) 299 if (!download_file)
296 return; 300 return;
297 301
298 VLOG(20) << __FUNCTION__ << "()" << " download = " << download->DebugString(); 302 VLOG(20) << __FUNCTION__ << "()"
303 << " download_file = " << download_file->DebugString();
299 304
300 if (!download->Rename(full_path)) { 305 if (!download_file->Rename(full_path)) {
301 // Error. Between the time the UI thread generated 'full_path' to the time 306 // Error. Between the time the UI thread generated 'full_path' to the time
302 // this code runs, something happened that prevents us from renaming. 307 // this code runs, something happened that prevents us from renaming.
303 CancelDownloadOnRename(id); 308 CancelDownloadOnRename(id);
304 } 309 }
305 } 310 }
306 311
307 // The DownloadManager in the UI thread has provided a final name for the 312 // The DownloadManager in the UI thread has provided a final name for the
308 // download specified by 'id'. Rename the download that's in the process 313 // download specified by 'id'. Rename the download that's in the process
309 // of completing. 314 // of completing.
310 // 315 //
311 // There are 2 possible rename cases where this method can be called: 316 // There are 2 possible rename cases where this method can be called:
312 // 1. foo.crdownload -> foo (final, safe) 317 // 1. foo.crdownload -> foo (final, safe)
313 // 2. Unconfirmed.xxx.crdownload -> xxx (final, validated) 318 // 2. Unconfirmed.xxx.crdownload -> xxx (final, validated)
314 void DownloadFileManager::RenameCompletingDownloadFile( 319 void DownloadFileManager::RenameCompletingDownloadFile(
315 int id, const FilePath& full_path, bool overwrite_existing_file) { 320 int id, const FilePath& full_path, bool overwrite_existing_file) {
316 VLOG(20) << __FUNCTION__ << "()" << " id = " << id 321 VLOG(20) << __FUNCTION__ << "()" << " id = " << id
317 << " overwrite_existing_file = " << overwrite_existing_file 322 << " overwrite_existing_file = " << overwrite_existing_file
318 << " full_path = \"" << full_path.value() << "\""; 323 << " full_path = \"" << full_path.value() << "\"";
319 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 324 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
320 325
321 DownloadFile* download = GetDownloadFile(id); 326 DownloadFile* download_file = GetDownloadFile(id);
322 if (!download) 327 if (!download_file)
323 return; 328 return;
324 329
325 DCHECK(download->GetDownloadManager()); 330 DCHECK(download_file->GetDownloadManager());
326 DownloadManager* download_manager = download->GetDownloadManager(); 331 DownloadManager* download_manager = download_file->GetDownloadManager();
327 332
328 VLOG(20) << __FUNCTION__ << "()" << " download = " << download->DebugString(); 333 VLOG(20) << __FUNCTION__ << "()"
334 << " download_file = " << download_file->DebugString();
329 335
330 int uniquifier = 0; 336 int uniquifier = 0;
331 FilePath new_path = full_path; 337 FilePath new_path = full_path;
332 if (!overwrite_existing_file) { 338 if (!overwrite_existing_file) {
333 // Make our name unique at this point, as if a dangerous file is 339 // Make our name unique at this point, as if a dangerous file is
334 // downloading and a 2nd download is started for a file with the same 340 // downloading and a 2nd download is started for a file with the same
335 // name, they would have the same path. This is because we uniquify 341 // name, they would have the same path. This is because we uniquify
336 // the name on download start, and at that time the first file does 342 // the name on download start, and at that time the first file does
337 // not exists yet, so the second file gets the same name. 343 // not exists yet, so the second file gets the same name.
338 // This should not happen in the SAFE case, and we check for that in the UI 344 // This should not happen in the SAFE case, and we check for that in the UI
339 // thread. 345 // thread.
340 uniquifier = download_util::GetUniquePathNumber(new_path); 346 uniquifier = download_util::GetUniquePathNumber(new_path);
341 if (uniquifier > 0) { 347 if (uniquifier > 0) {
342 download_util::AppendNumberToPath(&new_path, uniquifier); 348 download_util::AppendNumberToPath(&new_path, uniquifier);
343 } 349 }
344 } 350 }
345 351
346 // Rename the file, overwriting if necessary. 352 // Rename the file, overwriting if necessary.
347 if (!download->Rename(new_path)) { 353 if (!download_file->Rename(new_path)) {
348 // Error. Between the time the UI thread generated 'full_path' to the time 354 // Error. Between the time the UI thread generated 'full_path' to the time
349 // this code runs, something happened that prevents us from renaming. 355 // this code runs, something happened that prevents us from renaming.
350 CancelDownloadOnRename(id); 356 CancelDownloadOnRename(id);
351 return; 357 return;
352 } 358 }
353 359
354 #if defined(OS_MACOSX) 360 #if defined(OS_MACOSX)
355 // Done here because we only want to do this once; see 361 // Done here because we only want to do this once; see
356 // http://crbug.com/13120 for details. 362 // http://crbug.com/13120 for details.
357 download->AnnotateWithSourceInformation(); 363 download_file->AnnotateWithSourceInformation();
358 #endif 364 #endif
359 365
360 BrowserThread::PostTask( 366 BrowserThread::PostTask(
361 BrowserThread::UI, FROM_HERE, 367 BrowserThread::UI, FROM_HERE,
362 NewRunnableMethod( 368 NewRunnableMethod(
363 download_manager, &DownloadManager::OnDownloadRenamedToFinalName, id, 369 download_manager, &DownloadManager::OnDownloadRenamedToFinalName, id,
364 new_path, uniquifier)); 370 new_path, uniquifier));
365 } 371 }
366 372
367 // Called only from RenameInProgressDownloadFile and 373 // Called only from RenameInProgressDownloadFile and
368 // RenameCompletingDownloadFile on the FILE thread. 374 // RenameCompletingDownloadFile on the FILE thread.
369 void DownloadFileManager::CancelDownloadOnRename(int id) { 375 void DownloadFileManager::CancelDownloadOnRename(int id) {
370 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 376 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
371 377
372 DownloadFile* download = GetDownloadFile(id); 378 DownloadFile* download_file = GetDownloadFile(id);
373 if (!download) 379 if (!download_file)
374 return; 380 return;
375 381
376 DownloadManager* download_manager = download->GetDownloadManager(); 382 DownloadManager* download_manager = download_file->GetDownloadManager();
377 if (!download_manager) { 383 if (!download_manager) {
378 // Without a download manager, we can't cancel the request normally, so we 384 // Without a download manager, we can't cancel the request normally, so we
379 // need to do it here. The normal path will also update the download 385 // need to do it here. The normal path will also update the download
380 // history before cancelling the request. 386 // history before cancelling the request.
381 download->CancelDownloadRequest(resource_dispatcher_host_); 387 download_file->CancelDownloadRequest(resource_dispatcher_host_);
382 return; 388 return;
383 } 389 }
384 390
385 BrowserThread::PostTask( 391 BrowserThread::PostTask(
386 BrowserThread::UI, FROM_HERE, 392 BrowserThread::UI, FROM_HERE,
387 NewRunnableMethod(download_manager, 393 NewRunnableMethod(download_manager,
388 &DownloadManager::DownloadCancelled, id)); 394 &DownloadManager::DownloadCancelled, id));
389 } 395 }
390 396
391 void DownloadFileManager::EraseDownload(int id) { 397 void DownloadFileManager::EraseDownload(int id) {
392 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 398 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
393 399
394 if (!ContainsKey(downloads_, id)) 400 if (!ContainsKey(downloads_, id))
395 return; 401 return;
396 402
397 DownloadFile* download_file = downloads_[id]; 403 DownloadFile* download_file = downloads_[id];
398 404
399 VLOG(20) << " " << __FUNCTION__ << "()" 405 VLOG(20) << " " << __FUNCTION__ << "()"
400 << " id = " << id 406 << " id = " << id
401 << " download_file = " << download_file->DebugString(); 407 << " download_file = " << download_file->DebugString();
402 408
403 downloads_.erase(id); 409 downloads_.erase(id);
404 410
405 delete download_file; 411 delete download_file;
406 412
407 if (downloads_.empty()) 413 if (downloads_.empty())
408 StopUpdateTimer(); 414 StopUpdateTimer();
409 } 415 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698