• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • 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/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 
SaveFileManager()27 SaveFileManager::SaveFileManager()
28     : next_id_(0) {
29 }
30 
~SaveFileManager()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).
Shutdown()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.
OnShutdown()45 void SaveFileManager::OnShutdown() {
46   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
47   STLDeleteValues(&save_file_map_);
48 }
49 
LookupSaveFile(int save_id)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.
GetNextId()61 int SaveFileManager::GetNextId() {
62   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
63   return next_id_++;
64 }
65 
RegisterStartingRequest(const GURL & save_url,SavePackage * save_package)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 
UnregisterStartingRequest(const GURL & save_url,int contents_id)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.
LookupPackage(int 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
SaveURL(const GURL & url,const Referrer & referrer,int render_process_host_id,int render_view_id,SaveFileCreateInfo::SaveFileSource save_source,const base::FilePath & file_full_path,ResourceContext * context,SavePackage * save_package)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.
RemoveSaveFile(int save_id,const GURL & save_url,SavePackage * package)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
GetSavePackageFromRenderIds(int render_process_id,int render_view_id)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 
DeleteDirectoryOrFile(const base::FilePath & full_path,bool is_dir)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 
SendCancelRequest(int save_id)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.
StartSave(SaveFileCreateInfo * info)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.
UpdateSaveProgress(int save_id,net::IOBuffer * data,int data_len)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.
SaveFinished(int save_id,const GURL & save_url,int render_process_id,bool is_success)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 
OnStartSave(const SaveFileCreateInfo * info)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 
OnUpdateSaveProgress(int save_id,int64 bytes_so_far,bool write_success)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 
OnSaveFinished(int save_id,int64 bytes_so_far,bool is_success)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 
OnErrorFinished(const GURL & save_url,int contents_id)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 
OnSaveURL(const GURL & url,const Referrer & referrer,int render_process_host_id,int render_view_id,ResourceContext * context)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 
OnRequireSaveJobFromOtherSource(SaveFileCreateInfo * info)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 
ExecuteCancelSaveRequest(int render_process_id,int request_id)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.
CancelSave(int save_id)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.
SaveLocalFile(const GURL & original_file_url,int save_id,int render_process_id)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 
OnDeleteDirectoryOrFile(const base::FilePath & full_path,bool is_dir)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 
RenameAllFiles(const FinalNameList & final_names,const base::FilePath & resource_dir,int render_process_id,int render_view_id,int save_package_id)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 
OnFinishSavePageJob(int render_process_id,int render_view_id,int save_package_id)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 
RemoveSavedFileFromFileMap(const SaveIDList & save_ids)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