Home | History | Annotate | Download | only in image_writer_private
      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