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 "chrome/browser/extensions/api/image_writer_private/operation.h"
6
7 #include "base/file_util.h"
8 #include "base/files/file_enumerator.h"
9 #include "base/threading/worker_pool.h"
10 #include "chrome/browser/extensions/api/image_writer_private/error_messages.h"
11 #include "chrome/browser/extensions/api/image_writer_private/operation_manager.h"
12 #include "content/public/browser/browser_thread.h"
13
14 namespace extensions {
15 namespace image_writer {
16
17 using content::BrowserThread;
18
19 const int kMD5BufferSize = 1024;
20 #if defined(OS_CHROMEOS)
21 // Chrome OS only has a 1 GB temporary partition. This is too small to hold our
22 // unzipped image. Fortunately we mount part of the temporary partition under
23 // /var/tmp.
24 const char kChromeOSTempRoot[] = "/var/tmp";
25 #endif
26
Operation(base::WeakPtr<OperationManager> manager,const ExtensionId & extension_id,const std::string & device_path)27 Operation::Operation(base::WeakPtr<OperationManager> manager,
28 const ExtensionId& extension_id,
29 const std::string& device_path)
30 : manager_(manager),
31 extension_id_(extension_id),
32 #if defined(OS_WIN)
33 device_path_(base::FilePath::FromUTF8Unsafe(device_path)),
34 #else
35 device_path_(device_path),
36 #endif
37 stage_(image_writer_api::STAGE_UNKNOWN),
38 progress_(0) {
39 }
40
~Operation()41 Operation::~Operation() {}
42
Cancel()43 void Operation::Cancel() {
44 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
45
46 stage_ = image_writer_api::STAGE_NONE;
47
48 CleanUp();
49 }
50
Abort()51 void Operation::Abort() {
52 Error(error::kAborted);
53 }
54
GetProgress()55 int Operation::GetProgress() {
56 return progress_;
57 }
58
GetStage()59 image_writer_api::Stage Operation::GetStage() {
60 return stage_;
61 }
62
63 #if !defined(OS_CHROMEOS)
SetUtilityClientForTesting(scoped_refptr<ImageWriterUtilityClient> client)64 void Operation::SetUtilityClientForTesting(
65 scoped_refptr<ImageWriterUtilityClient> client) {
66 image_writer_client_ = client;
67 AddCleanUpFunction(
68 base::Bind(&ImageWriterUtilityClient::Shutdown, image_writer_client_));
69 }
70 #endif
71
Start()72 void Operation::Start() {
73 #if defined(OS_CHROMEOS)
74 if (!temp_dir_.CreateUniqueTempDirUnderPath(
75 base::FilePath(kChromeOSTempRoot))) {
76 #else
77 if (!temp_dir_.CreateUniqueTempDir()) {
78 #endif
79 Error(error::kTempDirError);
80 return;
81 }
82
83 AddCleanUpFunction(
84 base::Bind(base::IgnoreResult(&base::ScopedTempDir::Delete),
85 base::Unretained(&temp_dir_)));
86
87 StartImpl();
88 }
89
90 void Operation::Unzip(const base::Closure& continuation) {
91 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
92 if (IsCancelled()) {
93 return;
94 }
95
96 if (image_path_.Extension() != FILE_PATH_LITERAL(".zip")) {
97 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, continuation);
98 return;
99 }
100
101 SetStage(image_writer_api::STAGE_UNZIP);
102
103 if (!(zip_reader_.Open(image_path_) && zip_reader_.AdvanceToNextEntry() &&
104 zip_reader_.OpenCurrentEntryInZip())) {
105 Error(error::kUnzipGenericError);
106 return;
107 }
108
109 if (zip_reader_.HasMore()) {
110 Error(error::kUnzipInvalidArchive);
111 return;
112 }
113
114 // Create a new target to unzip to. The original file is opened by the
115 // zip_reader_.
116 zip::ZipReader::EntryInfo* entry_info = zip_reader_.current_entry_info();
117 if (entry_info) {
118 image_path_ = temp_dir_.path().Append(entry_info->file_path().BaseName());
119 } else {
120 Error(error::kTempDirError);
121 return;
122 }
123
124 zip_reader_.ExtractCurrentEntryToFilePathAsync(
125 image_path_,
126 base::Bind(&Operation::CompleteAndContinue, this, continuation),
127 base::Bind(&Operation::OnUnzipFailure, this),
128 base::Bind(&Operation::OnUnzipProgress,
129 this,
130 zip_reader_.current_entry_info()->original_size()));
131 }
132
133 void Operation::Finish() {
134 if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
135 BrowserThread::PostTask(
136 BrowserThread::FILE, FROM_HERE, base::Bind(&Operation::Finish, this));
137 return;
138 }
139
140 CleanUp();
141
142 BrowserThread::PostTask(
143 BrowserThread::UI,
144 FROM_HERE,
145 base::Bind(&OperationManager::OnComplete, manager_, extension_id_));
146 }
147
148 void Operation::Error(const std::string& error_message) {
149 if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
150 BrowserThread::PostTask(BrowserThread::FILE,
151 FROM_HERE,
152 base::Bind(&Operation::Error, this, error_message));
153 return;
154 }
155
156 BrowserThread::PostTask(
157 BrowserThread::UI,
158 FROM_HERE,
159 base::Bind(&OperationManager::OnError,
160 manager_,
161 extension_id_,
162 stage_,
163 progress_,
164 error_message));
165
166 CleanUp();
167 }
168
169 void Operation::SetProgress(int progress) {
170 if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
171 BrowserThread::PostTask(
172 BrowserThread::FILE,
173 FROM_HERE,
174 base::Bind(&Operation::SetProgress,
175 this,
176 progress));
177 return;
178 }
179
180 if (progress <= progress_) {
181 return;
182 }
183
184 if (IsCancelled()) {
185 return;
186 }
187
188 progress_ = progress;
189
190 BrowserThread::PostTask(BrowserThread::UI,
191 FROM_HERE,
192 base::Bind(&OperationManager::OnProgress,
193 manager_,
194 extension_id_,
195 stage_,
196 progress_));
197 }
198
199 void Operation::SetStage(image_writer_api::Stage stage) {
200 if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
201 BrowserThread::PostTask(
202 BrowserThread::FILE,
203 FROM_HERE,
204 base::Bind(&Operation::SetStage,
205 this,
206 stage));
207 return;
208 }
209
210 if (IsCancelled()) {
211 return;
212 }
213
214 stage_ = stage;
215 progress_ = 0;
216
217 BrowserThread::PostTask(
218 BrowserThread::UI,
219 FROM_HERE,
220 base::Bind(&OperationManager::OnProgress,
221 manager_,
222 extension_id_,
223 stage_,
224 progress_));
225 }
226
227 bool Operation::IsCancelled() {
228 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
229
230 return stage_ == image_writer_api::STAGE_NONE;
231 }
232
233 void Operation::AddCleanUpFunction(const base::Closure& callback) {
234 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
235 cleanup_functions_.push_back(callback);
236 }
237
238 void Operation::CompleteAndContinue(const base::Closure& continuation) {
239 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
240 SetProgress(kProgressComplete);
241 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, continuation);
242 }
243
244 #if !defined(OS_CHROMEOS)
245 void Operation::StartUtilityClient() {
246 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
247 if (!image_writer_client_) {
248 image_writer_client_ = new ImageWriterUtilityClient();
249 AddCleanUpFunction(base::Bind(&Operation::StopUtilityClient, this));
250 }
251 }
252
253 void Operation::StopUtilityClient() {
254 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
255 BrowserThread::PostTask(
256 BrowserThread::IO,
257 FROM_HERE,
258 base::Bind(&ImageWriterUtilityClient::Shutdown, image_writer_client_));
259 }
260
261 void Operation::WriteImageProgress(int64 total_bytes, int64 curr_bytes) {
262 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
263 if (IsCancelled()) {
264 return;
265 }
266
267 int progress = kProgressComplete * curr_bytes / total_bytes;
268
269 if (progress > GetProgress()) {
270 SetProgress(progress);
271 }
272 }
273 #endif
274
275 void Operation::GetMD5SumOfFile(
276 const base::FilePath& file_path,
277 int64 file_size,
278 int progress_offset,
279 int progress_scale,
280 const base::Callback<void(const std::string&)>& callback) {
281 if (IsCancelled()) {
282 return;
283 }
284
285 base::MD5Init(&md5_context_);
286
287 base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
288 if (!file.IsValid()) {
289 Error(error::kImageOpenError);
290 return;
291 }
292
293 if (file_size <= 0) {
294 file_size = file.GetLength();
295 if (file_size < 0) {
296 Error(error::kImageOpenError);
297 return;
298 }
299 }
300
301 BrowserThread::PostTask(BrowserThread::FILE,
302 FROM_HERE,
303 base::Bind(&Operation::MD5Chunk,
304 this,
305 Passed(file.Pass()),
306 0,
307 file_size,
308 progress_offset,
309 progress_scale,
310 callback));
311 }
312
313 void Operation::MD5Chunk(
314 base::File file,
315 int64 bytes_processed,
316 int64 bytes_total,
317 int progress_offset,
318 int progress_scale,
319 const base::Callback<void(const std::string&)>& callback) {
320 if (IsCancelled())
321 return;
322
323 CHECK_LE(bytes_processed, bytes_total);
324
325 scoped_ptr<char[]> buffer(new char[kMD5BufferSize]);
326 int read_size = std::min(bytes_total - bytes_processed,
327 static_cast<int64>(kMD5BufferSize));
328
329 if (read_size == 0) {
330 // Nothing to read, we are done.
331 base::MD5Digest digest;
332 base::MD5Final(&digest, &md5_context_);
333 callback.Run(base::MD5DigestToBase16(digest));
334 } else {
335 int len = file.Read(bytes_processed, buffer.get(), read_size);
336
337 if (len == read_size) {
338 // Process data.
339 base::MD5Update(&md5_context_, base::StringPiece(buffer.get(), len));
340 int percent_curr =
341 ((bytes_processed + len) * progress_scale) / bytes_total +
342 progress_offset;
343 SetProgress(percent_curr);
344
345 BrowserThread::PostTask(BrowserThread::FILE,
346 FROM_HERE,
347 base::Bind(&Operation::MD5Chunk,
348 this,
349 Passed(file.Pass()),
350 bytes_processed + len,
351 bytes_total,
352 progress_offset,
353 progress_scale,
354 callback));
355 // Skip closing the file.
356 return;
357 } else {
358 // We didn't read the bytes we expected.
359 Error(error::kHashReadError);
360 }
361 }
362 }
363
364 void Operation::OnUnzipFailure() {
365 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
366 Error(error::kUnzipGenericError);
367 }
368
369 void Operation::OnUnzipProgress(int64 total_bytes, int64 progress_bytes) {
370 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
371
372 int progress_percent = kProgressComplete * progress_bytes / total_bytes;
373 SetProgress(progress_percent);
374 }
375
376 void Operation::CleanUp() {
377 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
378 for (std::vector<base::Closure>::iterator it = cleanup_functions_.begin();
379 it != cleanup_functions_.end();
380 ++it) {
381 it->Run();
382 }
383 cleanup_functions_.clear();
384 }
385
386 } // namespace image_writer
387 } // namespace extensions
388