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