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 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 41 Operation::~Operation() {} 42 43 void Operation::Cancel() { 44 DCHECK_CURRENTLY_ON(BrowserThread::FILE); 45 46 stage_ = image_writer_api::STAGE_NONE; 47 48 CleanUp(); 49 } 50 51 void Operation::Abort() { 52 Error(error::kAborted); 53 } 54 55 int Operation::GetProgress() { 56 return progress_; 57 } 58 59 image_writer_api::Stage Operation::GetStage() { 60 return stage_; 61 } 62 63 #if !defined(OS_CHROMEOS) 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 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