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