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 3 // found in the LICENSE file. 4 5 #include "build/build_config.h" 6 7 #include "chrome/browser/download/save_file_manager.h" 8 9 #include "base/file_util.h" 10 #include "base/logging.h" 11 #include "base/stl_util-inl.h" 12 #include "base/string_util.h" 13 #include "base/task.h" 14 #include "base/threading/thread.h" 15 #include "chrome/browser/download/save_file.h" 16 #include "chrome/browser/download/save_package.h" 17 #include "chrome/browser/platform_util.h" 18 #include "chrome/browser/tab_contents/tab_util.h" 19 #include "chrome/browser/ui/download/download_tab_helper.h" 20 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" 21 #include "chrome/common/chrome_paths.h" 22 #include "content/browser/browser_thread.h" 23 #include "content/browser/renderer_host/resource_dispatcher_host.h" 24 #include "content/browser/tab_contents/tab_contents.h" 25 #include "googleurl/src/gurl.h" 26 #include "net/base/net_util.h" 27 #include "net/base/io_buffer.h" 28 #include "net/url_request/url_request_context.h" 29 #include "net/url_request/url_request_context_getter.h" 30 31 SaveFileManager::SaveFileManager(ResourceDispatcherHost* rdh) 32 : next_id_(0), 33 resource_dispatcher_host_(rdh) { 34 DCHECK(resource_dispatcher_host_); 35 } 36 37 SaveFileManager::~SaveFileManager() { 38 // Check for clean shutdown. 39 DCHECK(save_file_map_.empty()); 40 } 41 42 // Called during the browser shutdown process to clean up any state (open files, 43 // timers) that live on the saving thread (file thread). 44 void SaveFileManager::Shutdown() { 45 BrowserThread::PostTask( 46 BrowserThread::FILE, FROM_HERE, 47 NewRunnableMethod(this, &SaveFileManager::OnShutdown)); 48 } 49 50 // Stop file thread operations. 51 void SaveFileManager::OnShutdown() { 52 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 53 STLDeleteValues(&save_file_map_); 54 } 55 56 SaveFile* SaveFileManager::LookupSaveFile(int save_id) { 57 SaveFileMap::iterator it = save_file_map_.find(save_id); 58 return it == save_file_map_.end() ? NULL : it->second; 59 } 60 61 // Called on the IO thread when 62 // a) The ResourceDispatcherHost has decided that a request is savable. 63 // b) The resource does not come from the network, but we still need a 64 // save ID for for managing the status of the saving operation. So we 65 // file a request from the file thread to the IO thread to generate a 66 // unique save ID. 67 int SaveFileManager::GetNextId() { 68 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 69 return next_id_++; 70 } 71 72 void SaveFileManager::RegisterStartingRequest(const GURL& save_url, 73 SavePackage* save_package) { 74 // Make sure it runs in the UI thread. 75 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 76 int tab_id = save_package->tab_id(); 77 78 // Register this starting request. 79 StartingRequestsMap& starting_requests = tab_starting_requests_[tab_id]; 80 bool never_present = starting_requests.insert( 81 StartingRequestsMap::value_type(save_url.spec(), save_package)).second; 82 DCHECK(never_present); 83 } 84 85 SavePackage* SaveFileManager::UnregisterStartingRequest( 86 const GURL& save_url, int tab_id) { 87 // Make sure it runs in UI thread. 88 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 89 90 TabToStartingRequestsMap::iterator it = tab_starting_requests_.find(tab_id); 91 if (it != tab_starting_requests_.end()) { 92 StartingRequestsMap& requests = it->second; 93 StartingRequestsMap::iterator sit = requests.find(save_url.spec()); 94 if (sit == requests.end()) 95 return NULL; 96 97 // Found, erase it from starting list and return SavePackage. 98 SavePackage* save_package = sit->second; 99 requests.erase(sit); 100 // If there is no element in requests, remove it 101 if (requests.empty()) 102 tab_starting_requests_.erase(it); 103 return save_package; 104 } 105 106 return NULL; 107 } 108 109 // Look up a SavePackage according to a save id. 110 SavePackage* SaveFileManager::LookupPackage(int save_id) { 111 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 112 SavePackageMap::iterator it = packages_.find(save_id); 113 if (it != packages_.end()) 114 return it->second; 115 return NULL; 116 } 117 118 // Call from SavePackage for starting a saving job 119 void SaveFileManager::SaveURL( 120 const GURL& url, 121 const GURL& referrer, 122 int render_process_host_id, 123 int render_view_id, 124 SaveFileCreateInfo::SaveFileSource save_source, 125 const FilePath& file_full_path, 126 net::URLRequestContextGetter* request_context_getter, 127 SavePackage* save_package) { 128 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 129 130 // Register a saving job. 131 RegisterStartingRequest(url, save_package); 132 if (save_source == SaveFileCreateInfo::SAVE_FILE_FROM_NET) { 133 DCHECK(url.is_valid()); 134 135 BrowserThread::PostTask( 136 BrowserThread::IO, FROM_HERE, 137 NewRunnableMethod(this, 138 &SaveFileManager::OnSaveURL, 139 url, 140 referrer, 141 render_process_host_id, 142 render_view_id, 143 make_scoped_refptr(request_context_getter))); 144 } else { 145 // We manually start the save job. 146 SaveFileCreateInfo* info = new SaveFileCreateInfo(file_full_path, 147 url, 148 save_source, 149 -1); 150 info->render_process_id = render_process_host_id; 151 info->render_view_id = render_view_id; 152 153 // Since the data will come from render process, so we need to start 154 // this kind of save job by ourself. 155 BrowserThread::PostTask( 156 BrowserThread::IO, FROM_HERE, 157 NewRunnableMethod( 158 this, &SaveFileManager::OnRequireSaveJobFromOtherSource, info)); 159 } 160 } 161 162 // Utility function for look up table maintenance, called on the UI thread. 163 // A manager may have multiple save page job (SavePackage) in progress, 164 // so we just look up the save id and remove it from the tracking table. 165 // If the save id is -1, it means we just send a request to save, but the 166 // saving action has still not happened, need to call UnregisterStartingRequest 167 // to remove it from the tracking map. 168 void SaveFileManager::RemoveSaveFile(int save_id, const GURL& save_url, 169 SavePackage* package) { 170 DCHECK(package); 171 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 172 // A save page job (SavePackage) can only have one manager, 173 // so remove it if it exists. 174 if (save_id == -1) { 175 SavePackage* old_package = UnregisterStartingRequest(save_url, 176 package->tab_id()); 177 DCHECK_EQ(old_package, package); 178 } else { 179 SavePackageMap::iterator it = packages_.find(save_id); 180 if (it != packages_.end()) 181 packages_.erase(it); 182 } 183 } 184 185 // Static 186 // Utility function for converting request IDs to a TabContents. Must be called 187 // only on the UI thread. 188 SavePackage* SaveFileManager::GetSavePackageFromRenderIds( 189 int render_process_id, int render_view_id) { 190 TabContents* contents = tab_util::GetTabContentsByID(render_process_id, 191 render_view_id); 192 if (contents) { 193 TabContentsWrapper* wrapper = 194 TabContentsWrapper::GetCurrentWrapperForContents(contents); 195 return wrapper->download_tab_helper()->save_package(); 196 } 197 198 return NULL; 199 } 200 201 // Utility function for deleting specified file. 202 void SaveFileManager::DeleteDirectoryOrFile(const FilePath& full_path, 203 bool is_dir) { 204 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 205 BrowserThread::PostTask( 206 BrowserThread::FILE, FROM_HERE, 207 NewRunnableMethod( 208 this, &SaveFileManager::OnDeleteDirectoryOrFile, full_path, is_dir)); 209 } 210 211 void SaveFileManager::SendCancelRequest(int save_id) { 212 // Cancel the request which has specific save id. 213 DCHECK_GT(save_id, -1); 214 BrowserThread::PostTask( 215 BrowserThread::FILE, FROM_HERE, 216 NewRunnableMethod(this, &SaveFileManager::CancelSave, save_id)); 217 } 218 219 // Notifications sent from the IO thread and run on the file thread: 220 221 // The IO thread created |info|, but the file thread (this method) uses it 222 // to create a SaveFile which will hold and finally destroy |info|. It will 223 // then passes |info| to the UI thread for reporting saving status. 224 void SaveFileManager::StartSave(SaveFileCreateInfo* info) { 225 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 226 DCHECK(info); 227 SaveFile* save_file = new SaveFile(info); 228 229 // TODO(phajdan.jr): We should check the return value and handle errors here. 230 save_file->Initialize(false); // No need to calculate hash. 231 232 DCHECK(!LookupSaveFile(info->save_id)); 233 save_file_map_[info->save_id] = save_file; 234 info->path = save_file->full_path(); 235 236 BrowserThread::PostTask( 237 BrowserThread::UI, FROM_HERE, 238 NewRunnableMethod(this, &SaveFileManager::OnStartSave, info)); 239 } 240 241 // We do forward an update to the UI thread here, since we do not use timer to 242 // update the UI. If the user has canceled the saving action (in the UI 243 // thread). We may receive a few more updates before the IO thread gets the 244 // cancel message. We just delete the data since the SaveFile has been deleted. 245 void SaveFileManager::UpdateSaveProgress(int save_id, 246 net::IOBuffer* data, 247 int data_len) { 248 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 249 SaveFile* save_file = LookupSaveFile(save_id); 250 if (save_file) { 251 bool write_success = save_file->AppendDataToFile(data->data(), data_len); 252 BrowserThread::PostTask( 253 BrowserThread::UI, FROM_HERE, 254 NewRunnableMethod( 255 this, &SaveFileManager::OnUpdateSaveProgress, save_file->save_id(), 256 save_file->bytes_so_far(), write_success)); 257 } 258 } 259 260 // The IO thread will call this when saving is completed or it got error when 261 // fetching data. In the former case, we forward the message to OnSaveFinished 262 // in UI thread. In the latter case, the save ID will be -1, which means the 263 // saving action did not even start, so we need to call OnErrorFinished in UI 264 // thread, which will use the save URL to find corresponding request record and 265 // delete it. 266 void SaveFileManager::SaveFinished(int save_id, 267 const GURL& save_url, 268 int render_process_id, 269 bool is_success) { 270 VLOG(20) << " " << __FUNCTION__ << "()" 271 << " save_id = " << save_id 272 << " save_url = \"" << save_url.spec() << "\"" 273 << " is_success = " << is_success; 274 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 275 SaveFileMap::iterator it = save_file_map_.find(save_id); 276 if (it != save_file_map_.end()) { 277 SaveFile* save_file = it->second; 278 VLOG(20) << " " << __FUNCTION__ << "()" 279 << " save_file = " << save_file->DebugString(); 280 BrowserThread::PostTask( 281 BrowserThread::UI, FROM_HERE, 282 NewRunnableMethod( 283 this, &SaveFileManager::OnSaveFinished, save_id, 284 save_file->bytes_so_far(), is_success)); 285 286 save_file->Finish(); 287 save_file->Detach(); 288 } else if (save_id == -1) { 289 // Before saving started, we got error. We still call finish process. 290 DCHECK(!save_url.is_empty()); 291 BrowserThread::PostTask( 292 BrowserThread::UI, FROM_HERE, 293 NewRunnableMethod( 294 this, &SaveFileManager::OnErrorFinished, save_url, 295 render_process_id)); 296 } 297 } 298 299 // Notifications sent from the file thread and run on the UI thread. 300 301 void SaveFileManager::OnStartSave(const SaveFileCreateInfo* info) { 302 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 303 SavePackage* save_package = 304 GetSavePackageFromRenderIds(info->render_process_id, 305 info->render_view_id); 306 if (!save_package) { 307 // Cancel this request. 308 SendCancelRequest(info->save_id); 309 return; 310 } 311 312 // Insert started saving job to tracking list. 313 SavePackageMap::iterator sit = packages_.find(info->save_id); 314 if (sit == packages_.end()) { 315 // Find the registered request. If we can not find, it means we have 316 // canceled the job before. 317 SavePackage* old_save_package = UnregisterStartingRequest(info->url, 318 info->render_process_id); 319 if (!old_save_package) { 320 // Cancel this request. 321 SendCancelRequest(info->save_id); 322 return; 323 } 324 DCHECK_EQ(old_save_package, save_package); 325 packages_[info->save_id] = save_package; 326 } else { 327 NOTREACHED(); 328 } 329 330 // Forward this message to SavePackage. 331 save_package->StartSave(info); 332 } 333 334 void SaveFileManager::OnUpdateSaveProgress(int save_id, int64 bytes_so_far, 335 bool write_success) { 336 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 337 SavePackage* package = LookupPackage(save_id); 338 if (package) 339 package->UpdateSaveProgress(save_id, bytes_so_far, write_success); 340 else 341 SendCancelRequest(save_id); 342 } 343 344 void SaveFileManager::OnSaveFinished(int save_id, 345 int64 bytes_so_far, 346 bool is_success) { 347 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 348 SavePackage* package = LookupPackage(save_id); 349 if (package) 350 package->SaveFinished(save_id, bytes_so_far, is_success); 351 } 352 353 void SaveFileManager::OnErrorFinished(const GURL& save_url, int tab_id) { 354 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 355 SavePackage* save_package = UnregisterStartingRequest(save_url, tab_id); 356 if (save_package) 357 save_package->SaveFailed(save_url); 358 } 359 360 // Notifications sent from the UI thread and run on the IO thread. 361 362 void SaveFileManager::OnSaveURL( 363 const GURL& url, 364 const GURL& referrer, 365 int render_process_host_id, 366 int render_view_id, 367 net::URLRequestContextGetter* request_context_getter) { 368 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 369 net::URLRequestContext* context = 370 request_context_getter->GetURLRequestContext(); 371 resource_dispatcher_host_->BeginSaveFile(url, 372 referrer, 373 render_process_host_id, 374 render_view_id, 375 context); 376 } 377 378 void SaveFileManager::OnRequireSaveJobFromOtherSource( 379 SaveFileCreateInfo* info) { 380 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 381 DCHECK_EQ(info->save_id, -1); 382 // Generate a unique save id. 383 info->save_id = GetNextId(); 384 // Start real saving action. 385 BrowserThread::PostTask( 386 BrowserThread::FILE, FROM_HERE, 387 NewRunnableMethod(this, &SaveFileManager::StartSave, info)); 388 } 389 390 void SaveFileManager::ExecuteCancelSaveRequest(int render_process_id, 391 int request_id) { 392 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 393 resource_dispatcher_host_->CancelRequest(render_process_id, 394 request_id, 395 false); 396 } 397 398 // Notifications sent from the UI thread and run on the file thread. 399 400 // This method will be sent via a user action, or shutdown on the UI thread, 401 // and run on the file thread. We don't post a message back for cancels, 402 // but we do forward the cancel to the IO thread. Since this message has been 403 // sent from the UI thread, the saving job may have already completed and 404 // won't exist in our map. 405 void SaveFileManager::CancelSave(int save_id) { 406 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 407 SaveFileMap::iterator it = save_file_map_.find(save_id); 408 if (it != save_file_map_.end()) { 409 SaveFile* save_file = it->second; 410 411 // If the data comes from the net IO thread, then forward the cancel 412 // message to IO thread. If the data comes from other sources, just 413 // ignore the cancel message. 414 if (save_file->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_NET) { 415 BrowserThread::PostTask( 416 BrowserThread::IO, FROM_HERE, 417 NewRunnableMethod( 418 this, &SaveFileManager::ExecuteCancelSaveRequest, 419 save_file->render_process_id(), save_file->request_id())); 420 421 // UI thread will notify the render process to stop sending data, 422 // so in here, we need not to do anything, just close the save file. 423 save_file->Cancel(); 424 } else { 425 // If we did not find SaveFile in map, the saving job should either get 426 // data from other sources or have finished. 427 DCHECK(save_file->save_source() != 428 SaveFileCreateInfo::SAVE_FILE_FROM_NET || 429 !save_file->in_progress()); 430 } 431 // Whatever the save file is renamed or not, just delete it. 432 save_file_map_.erase(it); 433 delete save_file; 434 } 435 } 436 437 // It is possible that SaveItem which has specified save_id has been canceled 438 // before this function runs. So if we can not find corresponding SaveFile by 439 // using specified save_id, just return. 440 void SaveFileManager::SaveLocalFile(const GURL& original_file_url, 441 int save_id, 442 int render_process_id) { 443 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 444 SaveFile* save_file = LookupSaveFile(save_id); 445 if (!save_file) 446 return; 447 // If it has finished, just return. 448 if (!save_file->in_progress()) 449 return; 450 451 // Close the save file before the copy operation. 452 save_file->Finish(); 453 save_file->Detach(); 454 455 DCHECK(original_file_url.SchemeIsFile()); 456 FilePath file_path; 457 net::FileURLToFilePath(original_file_url, &file_path); 458 // If we can not get valid file path from original URL, treat it as 459 // disk error. 460 if (file_path.empty()) 461 SaveFinished(save_id, original_file_url, render_process_id, false); 462 463 // Copy the local file to the temporary file. It will be renamed to its 464 // final name later. 465 bool success = file_util::CopyFile(file_path, save_file->full_path()); 466 if (!success) 467 file_util::Delete(save_file->full_path(), false); 468 SaveFinished(save_id, original_file_url, render_process_id, success); 469 } 470 471 void SaveFileManager::OnDeleteDirectoryOrFile(const FilePath& full_path, 472 bool is_dir) { 473 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 474 DCHECK(!full_path.empty()); 475 476 file_util::Delete(full_path, is_dir); 477 } 478 479 // Open a saved page package, show it in a Windows Explorer window. 480 // We run on this thread to avoid blocking the UI with slow Shell operations. 481 #if !defined(OS_MACOSX) 482 void SaveFileManager::OnShowSavedFileInShell(const FilePath full_path) { 483 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 484 platform_util::ShowItemInFolder(full_path); 485 } 486 #endif 487 488 void SaveFileManager::RenameAllFiles( 489 const FinalNameList& final_names, 490 const FilePath& resource_dir, 491 int render_process_id, 492 int render_view_id, 493 int save_package_id) { 494 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 495 496 if (!resource_dir.empty() && !file_util::PathExists(resource_dir)) 497 file_util::CreateDirectory(resource_dir); 498 499 for (FinalNameList::const_iterator i = final_names.begin(); 500 i != final_names.end(); ++i) { 501 SaveFileMap::iterator it = save_file_map_.find(i->first); 502 if (it != save_file_map_.end()) { 503 SaveFile* save_file = it->second; 504 DCHECK(!save_file->in_progress()); 505 save_file->Rename(i->second); 506 delete save_file; 507 save_file_map_.erase(it); 508 } 509 } 510 511 BrowserThread::PostTask( 512 BrowserThread::UI, FROM_HERE, 513 NewRunnableMethod( 514 this, &SaveFileManager::OnFinishSavePageJob, render_process_id, 515 render_view_id, save_package_id)); 516 } 517 518 void SaveFileManager::OnFinishSavePageJob(int render_process_id, 519 int render_view_id, 520 int save_package_id) { 521 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 522 523 SavePackage* save_package = 524 GetSavePackageFromRenderIds(render_process_id, render_view_id); 525 526 if (save_package && save_package->id() == save_package_id) 527 save_package->Finish(); 528 } 529 530 void SaveFileManager::RemoveSavedFileFromFileMap( 531 const SaveIDList& save_ids) { 532 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 533 534 for (SaveIDList::const_iterator i = save_ids.begin(); 535 i != save_ids.end(); ++i) { 536 SaveFileMap::iterator it = save_file_map_.find(*i); 537 if (it != save_file_map_.end()) { 538 SaveFile* save_file = it->second; 539 DCHECK(!save_file->in_progress()); 540 file_util::Delete(save_file->full_path(), false); 541 delete save_file; 542 save_file_map_.erase(it); 543 } 544 } 545 } 546