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 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 47 Operation::~Operation() {} 48 49 void Operation::Cancel() { 50 DCHECK_CURRENTLY_ON(BrowserThread::FILE); 51 52 stage_ = image_writer_api::STAGE_NONE; 53 54 CleanUp(); 55 } 56 57 void Operation::Abort() { 58 Error(error::kAborted); 59 } 60 61 int Operation::GetProgress() { 62 return progress_; 63 } 64 65 image_writer_api::Stage Operation::GetStage() { 66 return stage_; 67 } 68 69 #if !defined(OS_CHROMEOS) 70 // static 71 void Operation::SetUtilityClientForTesting( 72 scoped_refptr<ImageWriterUtilityClient> client) { 73 g_utility_client.Get() = client; 74 } 75 #endif 76 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