Home | History | Annotate | Download | only in download
      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