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.
5 #include "build/build_config.h"
7 #include "content/browser/download/save_file_manager.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 "googleurl/src/gurl.h"
22 #include "net/base/io_buffer.h"
23 #include "net/base/net_util.h"
27 SaveFileManager::SaveFileManager()
31 SaveFileManager::~SaveFileManager() {
32 // Check for clean shutdown.
33 DCHECK(save_file_map_
.empty());
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));
44 // Stop file thread operations.
45 void SaveFileManager::OnShutdown() {
46 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
47 STLDeleteValues(&save_file_map_
);
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
;
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
61 int SaveFileManager::GetNextId() {
62 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
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();
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
);
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
));
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())
93 // Found, erase it from starting list and return SavePackage.
94 SavePackage
* save_package
= sit
->second
;
96 // If there is no element in requests, remove it
98 contents_starting_requests_
.erase(it
);
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())
114 // Call from SavePackage for starting a saving job
115 void SaveFileManager::SaveURL(
117 const Referrer
& referrer
,
118 int render_process_host_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
));
126 // Register a saving job.
127 RegisterStartingRequest(url
, save_package
);
128 if (save_source
== SaveFileCreateInfo::SAVE_FILE_FROM_NET
) {
129 DCHECK(url
.is_valid());
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
));
136 // We manually start the save job.
137 SaveFileCreateInfo
* info
= new SaveFileCreateInfo(file_full_path
,
141 info
->render_process_id
= render_process_host_id
;
142 info
->render_view_id
= render_view_id
;
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
,
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
) {
162 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
163 // A save page job (SavePackage) can only have one manager,
164 // so remove it if it exists.
166 SavePackage
* old_package
=
167 UnregisterStartingRequest(save_url
, package
->contents_id());
168 DCHECK_EQ(old_package
, package
);
170 SavePackageMap::iterator it
= packages_
.find(save_id
);
171 if (it
!= packages_
.end())
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
)
184 WebContentsImpl
* contents
= static_cast<WebContentsImpl
*>(
185 render_view_host
->GetDelegate()->GetAsWebContents());
189 return contents
->save_package();
192 void SaveFileManager::DeleteDirectoryOrFile(const base::FilePath
& full_path
,
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
));
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
));
209 // Notifications sent from the IO thread and run on the file thread:
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));
217 // No need to calculate hash.
218 SaveFile
* save_file
= new SaveFile(info
, false);
220 // TODO(phajdan.jr): We should check the return value and handle errors here.
221 save_file
->Initialize();
223 DCHECK(!LookupSaveFile(info
->save_id
));
224 save_file_map_
[info
->save_id
] = save_file
;
225 info
->path
= save_file
->FullPath();
227 BrowserThread::PostTask(
228 BrowserThread::UI
, FROM_HERE
,
229 base::Bind(&SaveFileManager::OnStartSave
, this, info
));
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
,
239 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
240 SaveFile
* save_file
= LookupSaveFile(save_id
);
242 DCHECK(save_file
->InProgress());
244 DownloadInterruptReason reason
=
245 save_file
->AppendDataToFile(data
->data(), data_len
);
246 BrowserThread::PostTask(
247 BrowserThread::UI
, FROM_HERE
,
248 base::Bind(&SaveFileManager::OnUpdateSaveProgress
,
250 save_file
->save_id(),
251 save_file
->BytesSoFar(),
252 reason
== DOWNLOAD_INTERRUPT_REASON_NONE
));
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
262 void SaveFileManager::SaveFinished(int save_id
,
263 const GURL
& save_url
,
264 int render_process_id
,
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());
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
));
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
,
300 // Notifications sent from the file thread and run on the UI thread.
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
);
308 // Cancel this request.
309 SendCancelRequest(info
->save_id
);
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
);
325 DCHECK_EQ(old_save_package
, save_package
);
326 packages_
[info
->save_id
] = save_package
;
331 // Forward this message to SavePackage.
332 save_package
->StartSave(info
);
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
);
340 package
->UpdateSaveProgress(save_id
, bytes_so_far
, write_success
);
342 SendCancelRequest(save_id
);
345 void SaveFileManager::OnSaveFinished(int save_id
,
348 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
349 SavePackage
* package
= LookupPackage(save_id
);
351 package
->SaveFinished(save_id
, bytes_so_far
, is_success
);
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
);
358 save_package
->SaveFailed(save_url
);
361 // Notifications sent from the UI thread and run on the IO thread.
363 void SaveFileManager::OnSaveURL(
365 const Referrer
& referrer
,
366 int render_process_host_id
,
368 ResourceContext
* context
) {
369 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
370 ResourceDispatcherHostImpl::Get()->BeginSaveFile(url
,
372 render_process_host_id
,
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
));
389 void SaveFileManager::ExecuteCancelSaveRequest(int render_process_id
,
391 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
392 ResourceDispatcherHostImpl::Get()->CancelRequest(render_process_id
,
397 // Notifications sent from the UI thread and run on the file thread.
399 // This method will be sent via a user action, or shutdown on the UI thread,
400 // and run on the file thread. We don't post a message back for cancels,
401 // but we do forward the cancel to the IO thread. Since this message has been
402 // sent from the UI thread, the saving job may have already completed and
403 // won't exist in our map.
404 void SaveFileManager::CancelSave(int save_id
) {
405 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
406 SaveFileMap::iterator it
= save_file_map_
.find(save_id
);
407 if (it
!= save_file_map_
.end()) {
408 SaveFile
* save_file
= it
->second
;
410 if (!save_file
->InProgress()) {
411 // We've won a race with the UI thread--we finished the file before
412 // the UI thread cancelled it on us. Unfortunately, in this situation
413 // the cancel wins, so we need to delete the now detached file.
414 file_util::Delete(save_file
->FullPath(), false);
415 } else if (save_file
->save_source() ==
416 SaveFileCreateInfo::SAVE_FILE_FROM_NET
) {
417 // If the data comes from the net IO thread and hasn't completed
418 // yet, then forward the cancel message to IO thread & cancel the
419 // save locally. If the data doesn't come from the IO thread,
420 // we can ignore the message.
421 BrowserThread::PostTask(
422 BrowserThread::IO
, FROM_HERE
,
423 base::Bind(&SaveFileManager::ExecuteCancelSaveRequest
, this,
424 save_file
->render_process_id(), save_file
->request_id()));
427 // Whatever the save file is complete or not, just delete it. This
428 // will delete the underlying file if InProgress() is true.
429 save_file_map_
.erase(it
);
434 // It is possible that SaveItem which has specified save_id has been canceled
435 // before this function runs. So if we can not find corresponding SaveFile by
436 // using specified save_id, just return.
437 void SaveFileManager::SaveLocalFile(const GURL
& original_file_url
,
439 int render_process_id
) {
440 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
441 SaveFile
* save_file
= LookupSaveFile(save_id
);
444 // If it has finished, just return.
445 if (!save_file
->InProgress())
448 // Close the save file before the copy operation.
452 DCHECK(original_file_url
.SchemeIsFile());
453 base::FilePath file_path
;
454 net::FileURLToFilePath(original_file_url
, &file_path
);
455 // If we can not get valid file path from original URL, treat it as
457 if (file_path
.empty())
458 SaveFinished(save_id
, original_file_url
, render_process_id
, false);
460 // Copy the local file to the temporary file. It will be renamed to its
462 bool success
= file_util::CopyFile(file_path
, save_file
->FullPath());
464 file_util::Delete(save_file
->FullPath(), false);
465 SaveFinished(save_id
, original_file_url
, render_process_id
, success
);
468 void SaveFileManager::OnDeleteDirectoryOrFile(const base::FilePath
& full_path
,
470 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
471 DCHECK(!full_path
.empty());
473 file_util::Delete(full_path
, is_dir
);
476 void SaveFileManager::RenameAllFiles(
477 const FinalNameList
& final_names
,
478 const base::FilePath
& resource_dir
,
479 int render_process_id
,
481 int save_package_id
) {
482 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
484 if (!resource_dir
.empty() && !file_util::PathExists(resource_dir
))
485 file_util::CreateDirectory(resource_dir
);
487 for (FinalNameList::const_iterator i
= final_names
.begin();
488 i
!= final_names
.end(); ++i
) {
489 SaveFileMap::iterator it
= save_file_map_
.find(i
->first
);
490 if (it
!= save_file_map_
.end()) {
491 SaveFile
* save_file
= it
->second
;
492 DCHECK(!save_file
->InProgress());
493 save_file
->Rename(i
->second
);
495 save_file_map_
.erase(it
);
499 BrowserThread::PostTask(
500 BrowserThread::UI
, FROM_HERE
,
501 base::Bind(&SaveFileManager::OnFinishSavePageJob
, this,
502 render_process_id
, render_view_id
, save_package_id
));
505 void SaveFileManager::OnFinishSavePageJob(int render_process_id
,
507 int save_package_id
) {
508 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
510 SavePackage
* save_package
=
511 GetSavePackageFromRenderIds(render_process_id
, render_view_id
);
513 if (save_package
&& save_package
->id() == save_package_id
)
514 save_package
->Finish();
517 void SaveFileManager::RemoveSavedFileFromFileMap(
518 const SaveIDList
& save_ids
) {
519 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
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
->InProgress());
527 file_util::Delete(save_file
->FullPath(), false);
529 save_file_map_
.erase(it
);
534 } // namespace content