1// Copyright 2013 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 "base/file_util.h"
6#include "base/threading/worker_pool.h"
7#include "chrome/browser/browser_process.h"
8#include "chrome/browser/extensions/api/image_writer_private/error_messages.h"
9#include "chrome/browser/extensions/api/image_writer_private/operation_manager.h"
10#include "chrome/browser/extensions/api/image_writer_private/write_from_url_operation.h"
11#include "chrome/browser/profiles/profile.h"
12#include "content/public/browser/browser_thread.h"
13#include "content/public/browser/download_manager.h"
14#include "content/public/browser/render_process_host.h"
15#include "content/public/browser/render_view_host.h"
16#include "extensions/common/error_utils.h"
17
18namespace extensions {
19namespace image_writer {
20
21using content::BrowserThread;
22
23WriteFromUrlOperation::WriteFromUrlOperation(
24    base::WeakPtr<OperationManager> manager,
25    const ExtensionId& extension_id,
26    content::RenderViewHost* rvh,
27    GURL url,
28    const std::string& hash,
29    bool saveImageAsDownload,
30    const std::string& storage_unit_id)
31    : Operation(manager, extension_id, storage_unit_id),
32      rvh_(rvh),
33      url_(url),
34      hash_(hash),
35      saveImageAsDownload_(saveImageAsDownload),
36      download_stopped_(false),
37      download_(NULL) {
38}
39
40WriteFromUrlOperation::~WriteFromUrlOperation() {
41}
42
43void WriteFromUrlOperation::Start() {
44  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
45
46  SetStage(image_writer_api::STAGE_DOWNLOAD);
47
48  if (saveImageAsDownload_){
49    BrowserThread::PostTask(
50        BrowserThread::UI,
51        FROM_HERE,
52        base::Bind(&WriteFromUrlOperation::DownloadStart, this));
53  } else {
54    BrowserThread::PostTask(
55        BrowserThread::FILE,
56        FROM_HERE,
57        base::Bind(&WriteFromUrlOperation::CreateTempFile, this));
58  }
59
60  AddCleanUpFunction(base::Bind(&WriteFromUrlOperation::DownloadCleanUp, this));
61}
62
63void WriteFromUrlOperation::CreateTempFile() {
64  if (IsCancelled()) {
65    return;
66  }
67
68  tmp_file_.reset(new base::FilePath());
69
70  if (base::CreateTemporaryFile(tmp_file_.get())) {
71    BrowserThread::PostTask(
72        BrowserThread::UI,
73        FROM_HERE,
74        base::Bind(&WriteFromUrlOperation::DownloadStart, this));
75  } else {
76    Error(error::kTempFileError);
77  }
78}
79
80// The downloader runs on the UI thread.
81void WriteFromUrlOperation::DownloadStart() {
82  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
83
84  if (download_stopped_) {
85    return;
86  }
87
88  DVLOG(1) << "Starting download of URL: " << url_;
89
90  Profile* current_profile = manager_->profile();
91
92  scoped_ptr<content::DownloadUrlParameters> download_params(
93      new content::DownloadUrlParameters(
94        url_,
95        rvh_->GetProcess()->GetID(),
96        rvh_->GetRoutingID(),
97        current_profile->GetResourceContext()));
98
99  if (tmp_file_.get()) {
100    download_params->set_file_path(*tmp_file_);
101  }
102
103  download_params->set_callback(
104      base::Bind(&WriteFromUrlOperation::OnDownloadStarted, this));
105
106  content::DownloadManager* download_manager =
107    content::BrowserContext::GetDownloadManager(current_profile);
108  download_manager->DownloadUrl(download_params.Pass());
109}
110
111void WriteFromUrlOperation::OnDownloadStarted(content::DownloadItem* item,
112                                              net::Error error) {
113  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
114
115  if (download_stopped_) {
116    // At this point DownloadCleanUp was called but the |download_| wasn't
117    // stored yet and still hasn't been cancelled.
118    item->Cancel(true);
119    return;
120  }
121
122  if (item) {
123    DCHECK_EQ(net::OK, error);
124
125    download_ = item;
126    download_->AddObserver(this);
127
128    // Run at least once.
129    OnDownloadUpdated(download_);
130  } else {
131    DCHECK_NE(net::OK, error);
132    std::string error_message = ErrorUtils::FormatErrorMessage(
133        "Download failed: *",
134        net::ErrorToString(error));
135    Error(error_message);
136  }
137}
138
139// Always called from the UI thread.
140void WriteFromUrlOperation::OnDownloadUpdated(content::DownloadItem* download) {
141  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
142
143  if (download_stopped_) {
144    return;
145  }
146
147  SetProgress(download->PercentComplete());
148
149  if (download->GetState() == content::DownloadItem::COMPLETE) {
150    download_path_ = download_->GetTargetFilePath();
151
152    download_->RemoveObserver(this);
153    download_ = NULL;
154
155    BrowserThread::PostTask(
156        BrowserThread::FILE,
157        FROM_HERE,
158        base::Bind(&WriteFromUrlOperation::DownloadComplete, this));
159
160  } else if (download->GetState() == content::DownloadItem::INTERRUPTED) {
161    Error(error::kDownloadInterrupted);
162  } else if (download->GetState() == content::DownloadItem::CANCELLED) {
163    Error(error::kDownloadCancelled);
164  }
165}
166
167void WriteFromUrlOperation::DownloadComplete() {
168  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
169  DVLOG(1) << "Download complete.";
170
171  SetProgress(kProgressComplete);
172
173  VerifyDownloadStart();
174}
175
176void WriteFromUrlOperation::DownloadCleanUp() {
177  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
178    BrowserThread::PostTask(
179        BrowserThread::UI,
180        FROM_HERE,
181        base::Bind(&WriteFromUrlOperation::DownloadCleanUp, this));
182    return;
183  }
184
185  download_stopped_ = true;
186
187  if (download_) {
188    download_->RemoveObserver(this);
189    download_->Cancel(true);
190    download_ = NULL;
191  }
192}
193
194void WriteFromUrlOperation::VerifyDownloadStart() {
195  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
196
197  if (IsCancelled()) {
198    return;
199  }
200
201  // Skip verify if no hash.
202  if (hash_.empty()) {
203    scoped_ptr<base::FilePath> download_path(
204        new base::FilePath(download_path_));
205    UnzipStart(download_path.Pass());
206    return;
207  }
208
209  DVLOG(1) << "Download verification started.";
210
211  SetStage(image_writer_api::STAGE_VERIFYDOWNLOAD);
212
213  BrowserThread::PostTask(
214      BrowserThread::FILE,
215      FROM_HERE,
216      base::Bind(&WriteFromUrlOperation::VerifyDownloadRun, this));
217}
218
219void WriteFromUrlOperation::VerifyDownloadRun() {
220  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
221  scoped_ptr<base::FilePath> download_path(new base::FilePath(download_path_));
222  GetMD5SumOfFile(
223      download_path.Pass(),
224      0,
225      0,
226      kProgressComplete,
227      base::Bind(&WriteFromUrlOperation::VerifyDownloadCompare, this));
228}
229
230void WriteFromUrlOperation::VerifyDownloadCompare(
231    scoped_ptr<std::string> download_hash) {
232  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
233  if (*download_hash != hash_) {
234    Error(error::kDownloadHashError);
235    return;
236  }
237
238  BrowserThread::PostTask(
239      BrowserThread::FILE,
240      FROM_HERE,
241      base::Bind(&WriteFromUrlOperation::VerifyDownloadComplete, this));
242}
243
244void WriteFromUrlOperation::VerifyDownloadComplete() {
245  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
246  if (IsCancelled()) {
247    return;
248  }
249
250  DVLOG(1) << "Download verification complete.";
251
252  SetProgress(kProgressComplete);
253
254  scoped_ptr<base::FilePath> download_path(new base::FilePath(download_path_));
255  UnzipStart(download_path.Pass());
256}
257
258} // namespace image_writer
259} // namespace extensions
260