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