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
25namespace content {
26
27SaveFileManager::SaveFileManager()
28    : next_id_(0) {
29}
30
31SaveFileManager::~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).
38void SaveFileManager::Shutdown() {
39  BrowserThread::PostTask(
40      BrowserThread::FILE, FROM_HERE,
41      base::Bind(&SaveFileManager::OnShutdown, this));
42}
43
44// Stop file thread operations.
45void SaveFileManager::OnShutdown() {
46  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
47  STLDeleteValues(&save_file_map_);
48}
49
50SaveFile* 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.
61int SaveFileManager::GetNextId() {
62  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
63  return next_id_++;
64}
65
66void 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
80SavePackage* 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.
106SavePackage* 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
115void 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.
159void 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
177SavePackage* 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
192void 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
201void 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.
214void 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.
236void 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.
262void 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
302void 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
335void 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
345void 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
354void 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
363void 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
377void 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
389void 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.
403void 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.
436void 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
467void 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
475void 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
504void 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
516void 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