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