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