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/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