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/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 "content/public/browser/render_frame_host.h"
22 #include "net/base/filename_util.h"
23 #include "net/base/io_buffer.h"
28 SaveFileManager::SaveFileManager()
32 SaveFileManager::~SaveFileManager() {
33 // Check for clean shutdown.
34 DCHECK(save_file_map_
.empty());
37 // Called during the browser shutdown process to clean up any state (open files,
38 // timers) that live on the saving thread (file thread).
39 void SaveFileManager::Shutdown() {
40 BrowserThread::PostTask(
41 BrowserThread::FILE, FROM_HERE
,
42 base::Bind(&SaveFileManager::OnShutdown
, this));
45 // Stop file thread operations.
46 void SaveFileManager::OnShutdown() {
47 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
48 STLDeleteValues(&save_file_map_
);
51 SaveFile
* SaveFileManager::LookupSaveFile(int save_id
) {
52 SaveFileMap::iterator it
= save_file_map_
.find(save_id
);
53 return it
== save_file_map_
.end() ? NULL
: it
->second
;
56 // Called on the IO thread when
57 // a) The ResourceDispatcherHostImpl has decided that a request is savable.
58 // b) The resource does not come from the network, but we still need a
59 // save ID for for managing the status of the saving operation. So we
60 // file a request from the file thread to the IO thread to generate a
62 int SaveFileManager::GetNextId() {
63 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
67 void SaveFileManager::RegisterStartingRequest(const GURL
& save_url
,
68 SavePackage
* save_package
) {
69 // Make sure it runs in the UI thread.
70 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
71 int contents_id
= save_package
->contents_id();
73 // Register this starting request.
74 StartingRequestsMap
& starting_requests
=
75 contents_starting_requests_
[contents_id
];
76 bool never_present
= starting_requests
.insert(
77 StartingRequestsMap::value_type(save_url
.spec(), save_package
)).second
;
78 DCHECK(never_present
);
81 SavePackage
* SaveFileManager::UnregisterStartingRequest(
82 const GURL
& save_url
, int contents_id
) {
83 // Make sure it runs in UI thread.
84 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
86 ContentsToStartingRequestsMap::iterator it
=
87 contents_starting_requests_
.find(contents_id
);
88 if (it
!= contents_starting_requests_
.end()) {
89 StartingRequestsMap
& requests
= it
->second
;
90 StartingRequestsMap::iterator sit
= requests
.find(save_url
.spec());
91 if (sit
== requests
.end())
94 // Found, erase it from starting list and return SavePackage.
95 SavePackage
* save_package
= sit
->second
;
97 // If there is no element in requests, remove it
99 contents_starting_requests_
.erase(it
);
106 // Look up a SavePackage according to a save id.
107 SavePackage
* SaveFileManager::LookupPackage(int save_id
) {
108 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
109 SavePackageMap::iterator it
= packages_
.find(save_id
);
110 if (it
!= packages_
.end())
115 // Call from SavePackage for starting a saving job
116 void SaveFileManager::SaveURL(
118 const Referrer
& referrer
,
119 int render_process_host_id
,
122 SaveFileCreateInfo::SaveFileSource save_source
,
123 const base::FilePath
& file_full_path
,
124 ResourceContext
* context
,
125 SavePackage
* save_package
) {
126 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
128 // Register a saving job.
129 RegisterStartingRequest(url
, save_package
);
130 if (save_source
== SaveFileCreateInfo::SAVE_FILE_FROM_NET
) {
131 DCHECK(url
.is_valid());
133 BrowserThread::PostTask(
134 BrowserThread::IO
, FROM_HERE
,
135 base::Bind(&SaveFileManager::OnSaveURL
, this, url
, referrer
,
136 render_process_host_id
, render_view_id
, render_frame_id
,
139 // We manually start the save job.
140 SaveFileCreateInfo
* info
= new SaveFileCreateInfo(file_full_path
,
144 info
->render_process_id
= render_process_host_id
;
145 info
->render_frame_id
= render_frame_id
;
147 // Since the data will come from render process, so we need to start
148 // this kind of save job by ourself.
149 BrowserThread::PostTask(
150 BrowserThread::IO
, FROM_HERE
,
151 base::Bind(&SaveFileManager::OnRequireSaveJobFromOtherSource
,
156 // Utility function for look up table maintenance, called on the UI thread.
157 // A manager may have multiple save page job (SavePackage) in progress,
158 // so we just look up the save id and remove it from the tracking table.
159 // If the save id is -1, it means we just send a request to save, but the
160 // saving action has still not happened, need to call UnregisterStartingRequest
161 // to remove it from the tracking map.
162 void SaveFileManager::RemoveSaveFile(int save_id
, const GURL
& save_url
,
163 SavePackage
* package
) {
165 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
166 // A save page job (SavePackage) can only have one manager,
167 // so remove it if it exists.
169 SavePackage
* old_package
=
170 UnregisterStartingRequest(save_url
, package
->contents_id());
171 DCHECK_EQ(old_package
, package
);
173 SavePackageMap::iterator it
= packages_
.find(save_id
);
174 if (it
!= packages_
.end())
180 SavePackage
* SaveFileManager::GetSavePackageFromRenderIds(
181 int render_process_id
, int render_frame_id
) {
182 RenderFrameHost
* render_frame_host
=
183 RenderFrameHost::FromID(render_process_id
, render_frame_id
);
184 WebContentsImpl
* web_contents
=
185 static_cast<WebContentsImpl
*>(WebContents::FromRenderFrameHost(
190 return web_contents
->save_package();
193 void SaveFileManager::DeleteDirectoryOrFile(const base::FilePath
& full_path
,
195 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
196 BrowserThread::PostTask(
197 BrowserThread::FILE, FROM_HERE
,
198 base::Bind(&SaveFileManager::OnDeleteDirectoryOrFile
,
199 this, full_path
, is_dir
));
202 void SaveFileManager::SendCancelRequest(int save_id
) {
203 // Cancel the request which has specific save id.
204 DCHECK_GT(save_id
, -1);
205 BrowserThread::PostTask(
206 BrowserThread::FILE, FROM_HERE
,
207 base::Bind(&SaveFileManager::CancelSave
, this, save_id
));
210 // Notifications sent from the IO thread and run on the file thread:
212 // The IO thread created |info|, but the file thread (this method) uses it
213 // to create a SaveFile which will hold and finally destroy |info|. It will
214 // then passes |info| to the UI thread for reporting saving status.
215 void SaveFileManager::StartSave(SaveFileCreateInfo
* info
) {
216 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
218 // No need to calculate hash.
219 SaveFile
* save_file
= new SaveFile(info
, false);
221 // TODO(phajdan.jr): We should check the return value and handle errors here.
222 save_file
->Initialize();
224 DCHECK(!LookupSaveFile(info
->save_id
));
225 save_file_map_
[info
->save_id
] = save_file
;
226 info
->path
= save_file
->FullPath();
228 BrowserThread::PostTask(
229 BrowserThread::UI
, FROM_HERE
,
230 base::Bind(&SaveFileManager::OnStartSave
, this, info
));
233 // We do forward an update to the UI thread here, since we do not use timer to
234 // update the UI. If the user has canceled the saving action (in the UI
235 // thread). We may receive a few more updates before the IO thread gets the
236 // cancel message. We just delete the data since the SaveFile has been deleted.
237 void SaveFileManager::UpdateSaveProgress(int save_id
,
240 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
241 SaveFile
* save_file
= LookupSaveFile(save_id
);
243 DCHECK(save_file
->InProgress());
245 DownloadInterruptReason reason
=
246 save_file
->AppendDataToFile(data
->data(), data_len
);
247 BrowserThread::PostTask(
248 BrowserThread::UI
, FROM_HERE
,
249 base::Bind(&SaveFileManager::OnUpdateSaveProgress
,
251 save_file
->save_id(),
252 save_file
->BytesSoFar(),
253 reason
== DOWNLOAD_INTERRUPT_REASON_NONE
));
257 // The IO thread will call this when saving is completed or it got error when
258 // fetching data. In the former case, we forward the message to OnSaveFinished
259 // in UI thread. In the latter case, the save ID will be -1, which means the
260 // saving action did not even start, so we need to call OnErrorFinished in UI
261 // thread, which will use the save URL to find corresponding request record and
263 void SaveFileManager::SaveFinished(int save_id
,
264 const GURL
& save_url
,
265 int render_process_id
,
267 DVLOG(20) << " " << __FUNCTION__
<< "()"
268 << " save_id = " << save_id
269 << " save_url = \"" << save_url
.spec() << "\""
270 << " is_success = " << is_success
;
271 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
272 SaveFileMap::iterator it
= save_file_map_
.find(save_id
);
273 if (it
!= save_file_map_
.end()) {
274 SaveFile
* save_file
= it
->second
;
275 // This routine may be called twice for the same from from
276 // SaveePackage::OnReceivedSerializedHtmlData, once for the file
277 // itself, and once when all frames have been serialized.
278 // So we can't assert that the file is InProgress() here.
279 // TODO(rdsmith): Fix this logic and put the DCHECK below back in.
280 // DCHECK(save_file->InProgress());
282 DVLOG(20) << " " << __FUNCTION__
<< "()"
283 << " save_file = " << save_file
->DebugString();
284 BrowserThread::PostTask(
285 BrowserThread::UI
, FROM_HERE
,
286 base::Bind(&SaveFileManager::OnSaveFinished
, this, save_id
,
287 save_file
->BytesSoFar(), is_success
));
291 } else if (save_id
== -1) {
292 // Before saving started, we got error. We still call finish process.
293 DCHECK(!save_url
.is_empty());
294 BrowserThread::PostTask(
295 BrowserThread::UI
, FROM_HERE
,
296 base::Bind(&SaveFileManager::OnErrorFinished
, this, save_url
,
301 // Notifications sent from the file thread and run on the UI thread.
303 void SaveFileManager::OnStartSave(const SaveFileCreateInfo
* info
) {
304 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
305 SavePackage
* save_package
=
306 GetSavePackageFromRenderIds(info
->render_process_id
,
307 info
->render_frame_id
);
309 // Cancel this request.
310 SendCancelRequest(info
->save_id
);
314 // Insert started saving job to tracking list.
315 SavePackageMap::iterator sit
= packages_
.find(info
->save_id
);
316 if (sit
== packages_
.end()) {
317 // Find the registered request. If we can not find, it means we have
318 // canceled the job before.
319 SavePackage
* old_save_package
= UnregisterStartingRequest(info
->url
,
320 info
->render_process_id
);
321 if (!old_save_package
) {
322 // Cancel this request.
323 SendCancelRequest(info
->save_id
);
326 DCHECK_EQ(old_save_package
, save_package
);
327 packages_
[info
->save_id
] = save_package
;
332 // Forward this message to SavePackage.
333 save_package
->StartSave(info
);
336 void SaveFileManager::OnUpdateSaveProgress(int save_id
, int64 bytes_so_far
,
337 bool write_success
) {
338 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
339 SavePackage
* package
= LookupPackage(save_id
);
341 package
->UpdateSaveProgress(save_id
, bytes_so_far
, write_success
);
343 SendCancelRequest(save_id
);
346 void SaveFileManager::OnSaveFinished(int save_id
,
349 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
350 SavePackage
* package
= LookupPackage(save_id
);
352 package
->SaveFinished(save_id
, bytes_so_far
, is_success
);
355 void SaveFileManager::OnErrorFinished(const GURL
& save_url
, int contents_id
) {
356 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
357 SavePackage
* save_package
= UnregisterStartingRequest(save_url
, contents_id
);
359 save_package
->SaveFailed(save_url
);
362 // Notifications sent from the UI thread and run on the IO thread.
364 void SaveFileManager::OnSaveURL(
366 const Referrer
& referrer
,
367 int render_process_host_id
,
370 ResourceContext
* context
) {
371 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
372 ResourceDispatcherHostImpl::Get()->BeginSaveFile(url
,
374 render_process_host_id
,
380 void SaveFileManager::OnRequireSaveJobFromOtherSource(
381 SaveFileCreateInfo
* info
) {
382 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
383 DCHECK_EQ(info
->save_id
, -1);
384 // Generate a unique save id.
385 info
->save_id
= GetNextId();
386 // Start real saving action.
387 BrowserThread::PostTask(
388 BrowserThread::FILE, FROM_HERE
,
389 base::Bind(&SaveFileManager::StartSave
, this, info
));
392 void SaveFileManager::ExecuteCancelSaveRequest(int render_process_id
,
394 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
395 ResourceDispatcherHostImpl::Get()->CancelRequest(
396 render_process_id
, request_id
);
399 // Notifications sent from the UI thread and run on the file thread.
401 // This method will be sent via a user action, or shutdown on the UI thread,
402 // and run on the file thread. We don't post a message back for cancels,
403 // but we do forward the cancel to the IO thread. Since this message has been
404 // sent from the UI thread, the saving job may have already completed and
405 // won't exist in our map.
406 void SaveFileManager::CancelSave(int save_id
) {
407 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
408 SaveFileMap::iterator it
= save_file_map_
.find(save_id
);
409 if (it
!= save_file_map_
.end()) {
410 SaveFile
* save_file
= it
->second
;
412 if (!save_file
->InProgress()) {
413 // We've won a race with the UI thread--we finished the file before
414 // the UI thread cancelled it on us. Unfortunately, in this situation
415 // the cancel wins, so we need to delete the now detached file.
416 base::DeleteFile(save_file
->FullPath(), false);
417 } else if (save_file
->save_source() ==
418 SaveFileCreateInfo::SAVE_FILE_FROM_NET
) {
419 // If the data comes from the net IO thread and hasn't completed
420 // yet, then forward the cancel message to IO thread & cancel the
421 // save locally. If the data doesn't come from the IO thread,
422 // we can ignore the message.
423 BrowserThread::PostTask(
424 BrowserThread::IO
, FROM_HERE
,
425 base::Bind(&SaveFileManager::ExecuteCancelSaveRequest
, this,
426 save_file
->render_process_id(), save_file
->request_id()));
429 // Whatever the save file is complete or not, just delete it. This
430 // will delete the underlying file if InProgress() is true.
431 save_file_map_
.erase(it
);
436 // It is possible that SaveItem which has specified save_id has been canceled
437 // before this function runs. So if we can not find corresponding SaveFile by
438 // using specified save_id, just return.
439 void SaveFileManager::SaveLocalFile(const GURL
& original_file_url
,
441 int render_process_id
) {
442 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
443 SaveFile
* save_file
= LookupSaveFile(save_id
);
446 // If it has finished, just return.
447 if (!save_file
->InProgress())
450 // Close the save file before the copy operation.
454 DCHECK(original_file_url
.SchemeIsFile());
455 base::FilePath file_path
;
456 net::FileURLToFilePath(original_file_url
, &file_path
);
457 // If we can not get valid file path from original URL, treat it as
459 if (file_path
.empty())
460 SaveFinished(save_id
, original_file_url
, render_process_id
, false);
462 // Copy the local file to the temporary file. It will be renamed to its
464 bool success
= base::CopyFile(file_path
, save_file
->FullPath());
466 base::DeleteFile(save_file
->FullPath(), false);
467 SaveFinished(save_id
, original_file_url
, render_process_id
, success
);
470 void SaveFileManager::OnDeleteDirectoryOrFile(const base::FilePath
& full_path
,
472 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
473 DCHECK(!full_path
.empty());
475 base::DeleteFile(full_path
, is_dir
);
478 void SaveFileManager::RenameAllFiles(
479 const FinalNameList
& final_names
,
480 const base::FilePath
& resource_dir
,
481 int render_process_id
,
483 int save_package_id
) {
484 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
486 if (!resource_dir
.empty() && !base::PathExists(resource_dir
))
487 base::CreateDirectory(resource_dir
);
489 for (FinalNameList::const_iterator i
= final_names
.begin();
490 i
!= final_names
.end(); ++i
) {
491 SaveFileMap::iterator it
= save_file_map_
.find(i
->first
);
492 if (it
!= save_file_map_
.end()) {
493 SaveFile
* save_file
= it
->second
;
494 DCHECK(!save_file
->InProgress());
495 save_file
->Rename(i
->second
);
497 save_file_map_
.erase(it
);
501 BrowserThread::PostTask(
502 BrowserThread::UI
, FROM_HERE
,
503 base::Bind(&SaveFileManager::OnFinishSavePageJob
, this,
504 render_process_id
, render_frame_id
, save_package_id
));
507 void SaveFileManager::OnFinishSavePageJob(int render_process_id
,
509 int save_package_id
) {
510 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
512 SavePackage
* save_package
=
513 GetSavePackageFromRenderIds(render_process_id
, render_frame_id
);
515 if (save_package
&& save_package
->id() == save_package_id
)
516 save_package
->Finish();
519 void SaveFileManager::RemoveSavedFileFromFileMap(
520 const SaveIDList
& save_ids
) {
521 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
523 for (SaveIDList::const_iterator i
= save_ids
.begin();
524 i
!= save_ids
.end(); ++i
) {
525 SaveFileMap::iterator it
= save_file_map_
.find(*i
);
526 if (it
!= save_file_map_
.end()) {
527 SaveFile
* save_file
= it
->second
;
528 DCHECK(!save_file
->InProgress());
529 base::DeleteFile(save_file
->FullPath(), false);
531 save_file_map_
.erase(it
);
536 } // namespace content