Home | History | Annotate | Download | only in ppapi
      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 "media/cdm/ppapi/cdm_file_io_impl.h"
      6 
      7 #include <algorithm>
      8 #include <sstream>
      9 
     10 #include "media/cdm/ppapi/cdm_logging.h"
     11 #include "ppapi/c/pp_errors.h"
     12 #include "ppapi/cpp/dev/url_util_dev.h"
     13 
     14 namespace media {
     15 
     16 const int kReadSize = 4 * 1024;  // Arbitrary choice.
     17 
     18 // Call func_call and check the result. If the result is not
     19 // PP_OK_COMPLETIONPENDING, print out logs, call OnError() and return.
     20 #define CHECK_PP_OK_COMPLETIONPENDING(func_call, error_type)            \
     21   do {                                                                  \
     22     int32_t result = func_call;                                         \
     23     PP_DCHECK(result != PP_OK);                                         \
     24     if (result != PP_OK_COMPLETIONPENDING) {                            \
     25       CDM_DLOG() << #func_call << " failed with result: " << result;    \
     26       OnError(error_type);                                              \
     27       return;                                                           \
     28     }                                                                   \
     29   } while (0)
     30 
     31 #if !defined(NDEBUG)
     32 // PPAPI calls should only be made on the main thread. In this file, main thread
     33 // checking is only performed in public APIs and the completion callbacks. This
     34 // ensures all functions are running on the main thread since internal methods
     35 // are called either by the public APIs or by the completion callbacks.
     36 static bool IsMainThread() {
     37   return pp::Module::Get()->core()->IsMainThread();
     38 }
     39 #endif  // !defined(NDEBUG)
     40 
     41 // Posts a task to run |cb| on the main thread. The task is posted even if the
     42 // current thread is the main thread.
     43 static void PostOnMain(pp::CompletionCallback cb) {
     44   pp::Module::Get()->core()->CallOnMainThread(0, cb, PP_OK);
     45 }
     46 
     47 CdmFileIOImpl::FileLockMap* CdmFileIOImpl::file_lock_map_ = NULL;
     48 
     49 CdmFileIOImpl::ResourceTracker::ResourceTracker() {
     50   // Do nothing here since we lazy-initialize CdmFileIOImpl::file_lock_map_
     51   // in CdmFileIOImpl::AcquireFileLock().
     52 }
     53 
     54 CdmFileIOImpl::ResourceTracker::~ResourceTracker() {
     55   delete CdmFileIOImpl::file_lock_map_;
     56 }
     57 
     58 CdmFileIOImpl::CdmFileIOImpl(cdm::FileIOClient* client, PP_Instance pp_instance)
     59     : state_(FILE_UNOPENED),
     60       client_(client),
     61       pp_instance_handle_(pp_instance),
     62       callback_factory_(this),
     63       io_offset_(0) {
     64   PP_DCHECK(IsMainThread());
     65   PP_DCHECK(pp_instance);  // 0 indicates a "NULL handle".
     66 }
     67 
     68 CdmFileIOImpl::~CdmFileIOImpl() {
     69   PP_DCHECK(state_ == FILE_CLOSED);
     70 }
     71 
     72 // Call sequence: Open() -> OpenFileSystem() -> OpenFile() -> FILE_OPENED.
     73 void CdmFileIOImpl::Open(const char* file_name, uint32_t file_name_size) {
     74   CDM_DLOG() << __FUNCTION__;
     75   PP_DCHECK(IsMainThread());
     76 
     77   if (state_ != FILE_UNOPENED) {
     78     CDM_DLOG() << "Open() called in an invalid state.";
     79     OnError(OPEN_ERROR);
     80     return;
     81   }
     82 
     83   // File name should not contain any path separators.
     84   std::string file_name_str(file_name, file_name_size);
     85   if (file_name_str.find('/') != std::string::npos ||
     86       file_name_str.find('\\') != std::string::npos) {
     87     CDM_DLOG() << "Invalid file name.";
     88     OnError(OPEN_ERROR);
     89     return;
     90   }
     91 
     92   // pp::FileRef only accepts path that begins with a '/' character.
     93   file_name_ = '/' + file_name_str;
     94 
     95   if (!AcquireFileLock()) {
     96     CDM_DLOG() << "File is in use by other cdm::FileIO objects.";
     97     OnError(OPEN_WHILE_IN_USE);
     98     return;
     99   }
    100 
    101   state_ = OPENING_FILE_SYSTEM;
    102   OpenFileSystem();
    103 }
    104 
    105 // Call sequence:
    106 //                                       finished
    107 // Read() -> ReadFile() -> OnFileRead() ----------> Done.
    108 //               ^              |
    109 //               | not finished |
    110 //               |--------------|
    111 void CdmFileIOImpl::Read() {
    112   CDM_DLOG() << __FUNCTION__;
    113   PP_DCHECK(IsMainThread());
    114 
    115   if (state_ == READING_FILE || state_ == WRITING_FILE) {
    116     CDM_DLOG() << "Read() called during pending read/write.";
    117     OnError(READ_WHILE_IN_USE);
    118     return;
    119   }
    120 
    121   if (state_ != FILE_OPENED) {
    122     CDM_DLOG() << "Read() called in an invalid state.";
    123     OnError(READ_ERROR);
    124     return;
    125   }
    126 
    127   PP_DCHECK(io_buffer_.empty());
    128   PP_DCHECK(cumulative_read_buffer_.empty());
    129 
    130   io_buffer_.resize(kReadSize);
    131   io_offset_ = 0;
    132 
    133   state_ = READING_FILE;
    134   ReadFile();
    135 }
    136 
    137 // Call sequence:
    138 //                                            finished
    139 // Write() -> WriteFile() -> OnFileWritten() ----------> Done.
    140 //                ^                  |
    141 //                |                  | not finished
    142 //                |------------------|
    143 void CdmFileIOImpl::Write(const uint8_t* data, uint32_t data_size) {
    144   CDM_DLOG() << __FUNCTION__;
    145   PP_DCHECK(IsMainThread());
    146 
    147   if (state_ == READING_FILE || state_ == WRITING_FILE) {
    148     CDM_DLOG() << "Write() called during pending read/write.";
    149     OnError(WRITE_WHILE_IN_USE);
    150     return;
    151   }
    152 
    153   if (state_ != FILE_OPENED) {
    154     CDM_DLOG() << "Write() called in an invalid state.";
    155     OnError(WRITE_ERROR);
    156     return;
    157   }
    158 
    159   PP_DCHECK(io_offset_ == 0);
    160   PP_DCHECK(io_buffer_.empty());
    161   if (data_size > 0)
    162     io_buffer_.assign(data, data + data_size);
    163   else
    164     PP_DCHECK(!data);
    165 
    166   state_ = WRITING_FILE;
    167 
    168   // Always SetLength() in case |data_size| is less than the file size.
    169   SetLength(data_size);
    170 }
    171 
    172 void CdmFileIOImpl::Close() {
    173   CDM_DLOG() << __FUNCTION__;
    174   PP_DCHECK(IsMainThread());
    175   PP_DCHECK(state_ != FILE_CLOSED);
    176   CloseFile();
    177   ReleaseFileLock();
    178   // All pending callbacks are canceled since |callback_factory_| is destroyed.
    179   delete this;
    180 }
    181 
    182 bool CdmFileIOImpl::SetFileID() {
    183   PP_DCHECK(file_id_.empty());
    184   PP_DCHECK(!file_name_.empty() && file_name_[0] == '/');
    185 
    186   // Not taking ownership of |url_util_dev| (which is a singleton).
    187   const pp::URLUtil_Dev* url_util_dev = pp::URLUtil_Dev::Get();
    188   PP_URLComponents_Dev components;
    189   pp::Var url_var =
    190       url_util_dev->GetDocumentURL(pp_instance_handle_, &components);
    191   if (!url_var.is_string())
    192     return false;
    193   std::string url = url_var.AsString();
    194 
    195   file_id_.append(url, components.scheme.begin, components.scheme.len);
    196   file_id_ += ':';
    197   file_id_.append(url, components.host.begin, components.host.len);
    198   file_id_ += ':';
    199   file_id_.append(url, components.port.begin, components.port.len);
    200   file_id_ += file_name_;
    201 
    202   return true;
    203 }
    204 
    205 bool CdmFileIOImpl::AcquireFileLock() {
    206   PP_DCHECK(IsMainThread());
    207 
    208   if (file_id_.empty() && !SetFileID())
    209     return false;
    210 
    211   if (!file_lock_map_) {
    212     file_lock_map_ = new FileLockMap();
    213   } else {
    214     FileLockMap::iterator found = file_lock_map_->find(file_id_);
    215     if (found != file_lock_map_->end() && found->second)
    216       return false;
    217   }
    218 
    219   (*file_lock_map_)[file_id_] = true;
    220   return true;
    221 }
    222 
    223 void CdmFileIOImpl::ReleaseFileLock() {
    224   PP_DCHECK(IsMainThread());
    225 
    226   if (!file_lock_map_)
    227     return;
    228 
    229   FileLockMap::iterator found = file_lock_map_->find(file_id_);
    230   if (found != file_lock_map_->end() && found->second)
    231     found->second = false;
    232 }
    233 
    234 void CdmFileIOImpl::OpenFileSystem() {
    235   PP_DCHECK(state_ == OPENING_FILE_SYSTEM);
    236 
    237   pp::CompletionCallbackWithOutput<pp::FileSystem> cb =
    238       callback_factory_.NewCallbackWithOutput(
    239           &CdmFileIOImpl::OnFileSystemOpened);
    240   isolated_file_system_ = pp::IsolatedFileSystemPrivate(
    241       pp_instance_handle_, PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_PLUGINPRIVATE);
    242 
    243   CHECK_PP_OK_COMPLETIONPENDING(isolated_file_system_.Open(cb), OPEN_ERROR);
    244 }
    245 
    246 void CdmFileIOImpl::OnFileSystemOpened(int32_t result,
    247                                        pp::FileSystem file_system) {
    248   PP_DCHECK(IsMainThread());
    249   PP_DCHECK(state_ == OPENING_FILE_SYSTEM);
    250 
    251   if (result != PP_OK) {
    252     CDM_DLOG() << "File system open failed asynchronously.";
    253     ReleaseFileLock();
    254     OnError(OPEN_ERROR);
    255     return;
    256   }
    257 
    258   file_system_ = file_system;
    259   state_ = OPENING_FILE;
    260   OpenFile();
    261 }
    262 
    263 void CdmFileIOImpl::OpenFile() {
    264   PP_DCHECK(state_ == OPENING_FILE);
    265 
    266   file_io_ = pp::FileIO(pp_instance_handle_);
    267   file_ref_ = pp::FileRef(file_system_, file_name_.c_str());
    268   int32_t file_open_flag = PP_FILEOPENFLAG_READ |
    269                            PP_FILEOPENFLAG_WRITE |
    270                            PP_FILEOPENFLAG_CREATE;
    271   pp::CompletionCallback cb =
    272       callback_factory_.NewCallback(&CdmFileIOImpl::OnFileOpened);
    273   CHECK_PP_OK_COMPLETIONPENDING(file_io_.Open(file_ref_, file_open_flag, cb),
    274                                 OPEN_ERROR);
    275 }
    276 
    277 void CdmFileIOImpl::OnFileOpened(int32_t result) {
    278   PP_DCHECK(IsMainThread());
    279   PP_DCHECK(state_ == OPENING_FILE);
    280 
    281   if (result != PP_OK) {
    282     CDM_DLOG() << "File open failed.";
    283     ReleaseFileLock();
    284     OnError(OPEN_ERROR);
    285     return;
    286   }
    287 
    288   state_ = FILE_OPENED;
    289   client_->OnOpenComplete(cdm::FileIOClient::kSuccess);
    290 }
    291 
    292 void CdmFileIOImpl::ReadFile() {
    293   PP_DCHECK(state_ == READING_FILE);
    294   PP_DCHECK(!io_buffer_.empty());
    295 
    296   pp::CompletionCallback cb =
    297       callback_factory_.NewCallback(&CdmFileIOImpl::OnFileRead);
    298   CHECK_PP_OK_COMPLETIONPENDING(
    299       file_io_.Read(io_offset_, &io_buffer_[0], io_buffer_.size(), cb),
    300       READ_ERROR);
    301 }
    302 
    303 void CdmFileIOImpl::OnFileRead(int32_t bytes_read) {
    304   CDM_DLOG() << __FUNCTION__ << ": " << bytes_read;
    305   PP_DCHECK(IsMainThread());
    306   PP_DCHECK(state_ == READING_FILE);
    307 
    308   // 0 |bytes_read| indicates end-of-file reached.
    309   if (bytes_read < PP_OK) {
    310     CDM_DLOG() << "Read file failed.";
    311     OnError(READ_ERROR);
    312     return;
    313   }
    314 
    315   PP_DCHECK(static_cast<size_t>(bytes_read) <= io_buffer_.size());
    316   // Append |bytes_read| in |io_buffer_| to |cumulative_read_buffer_|.
    317   cumulative_read_buffer_.insert(cumulative_read_buffer_.end(),
    318                                  io_buffer_.begin(),
    319                                  io_buffer_.begin() + bytes_read);
    320   io_offset_ += bytes_read;
    321 
    322   // Not received end-of-file yet.
    323   if (bytes_read > 0) {
    324     ReadFile();
    325     return;
    326   }
    327 
    328   // We hit end-of-file. Return read data to the client.
    329   io_buffer_.clear();
    330   io_offset_ = 0;
    331   // Clear |cumulative_read_buffer_| in case OnReadComplete() calls Read() or
    332   // Write().
    333   std::vector<char> local_buffer;
    334   std::swap(cumulative_read_buffer_, local_buffer);
    335 
    336   state_ = FILE_OPENED;
    337   const uint8_t* data = local_buffer.empty() ?
    338       NULL : reinterpret_cast<const uint8_t*>(&local_buffer[0]);
    339   client_->OnReadComplete(
    340       cdm::FileIOClient::kSuccess, data, local_buffer.size());
    341 }
    342 
    343 void CdmFileIOImpl::SetLength(uint32_t length) {
    344   PP_DCHECK(state_ == WRITING_FILE);
    345 
    346   pp::CompletionCallback cb =
    347       callback_factory_.NewCallback(&CdmFileIOImpl::OnLengthSet);
    348   CHECK_PP_OK_COMPLETIONPENDING(file_io_.SetLength(length, cb), WRITE_ERROR);
    349 }
    350 
    351 void CdmFileIOImpl::OnLengthSet(int32_t result) {
    352   CDM_DLOG() << __FUNCTION__ << ": " << result;
    353   PP_DCHECK(IsMainThread());
    354   PP_DCHECK(state_ == WRITING_FILE);
    355 
    356   if (result != PP_OK) {
    357     CDM_DLOG() << "File SetLength failed.";
    358     OnError(WRITE_ERROR);
    359     return;
    360   }
    361 
    362   if (io_buffer_.empty()) {
    363     state_ = FILE_OPENED;
    364     client_->OnWriteComplete(cdm::FileIOClient::kSuccess);
    365     return;
    366   }
    367 
    368   WriteFile();
    369 }
    370 
    371 void CdmFileIOImpl::WriteFile() {
    372   PP_DCHECK(state_ == WRITING_FILE);
    373   PP_DCHECK(io_offset_ < io_buffer_.size());
    374 
    375   pp::CompletionCallback cb =
    376       callback_factory_.NewCallback(&CdmFileIOImpl::OnFileWritten);
    377   CHECK_PP_OK_COMPLETIONPENDING(file_io_.Write(io_offset_,
    378                                                &io_buffer_[io_offset_],
    379                                                io_buffer_.size() - io_offset_,
    380                                                cb),
    381                                 WRITE_ERROR);
    382 }
    383 
    384 void CdmFileIOImpl::OnFileWritten(int32_t bytes_written) {
    385   CDM_DLOG() << __FUNCTION__ << ": " << bytes_written;
    386   PP_DCHECK(IsMainThread());
    387   PP_DCHECK(state_ == WRITING_FILE);
    388 
    389   if (bytes_written <= PP_OK) {
    390     CDM_DLOG() << "Write file failed.";
    391     OnError(READ_ERROR);
    392     return;
    393   }
    394 
    395   io_offset_ += bytes_written;
    396   PP_DCHECK(io_offset_ <= io_buffer_.size());
    397 
    398   if (io_offset_ < io_buffer_.size()) {
    399     WriteFile();
    400     return;
    401   }
    402 
    403   io_buffer_.clear();
    404   io_offset_ = 0;
    405   state_ = FILE_OPENED;
    406   client_->OnWriteComplete(cdm::FileIOClient::kSuccess);
    407 }
    408 
    409 void CdmFileIOImpl::CloseFile() {
    410   PP_DCHECK(IsMainThread());
    411 
    412   state_ = FILE_CLOSED;
    413 
    414   file_io_.Close();
    415   io_buffer_.clear();
    416   io_offset_ = 0;
    417   cumulative_read_buffer_.clear();
    418 }
    419 
    420 void CdmFileIOImpl::OnError(ErrorType error_type) {
    421   // For *_WHILE_IN_USE errors, do not reset these values. Otherwise, the
    422   // existing read/write operation will fail.
    423   if (error_type == READ_ERROR || error_type == WRITE_ERROR) {
    424     io_buffer_.clear();
    425     io_offset_ = 0;
    426     cumulative_read_buffer_.clear();
    427   }
    428   PostOnMain(callback_factory_.NewCallback(&CdmFileIOImpl::NotifyClientOfError,
    429                                            error_type));
    430 }
    431 
    432 void CdmFileIOImpl::NotifyClientOfError(int32_t result,
    433                                         ErrorType error_type) {
    434   PP_DCHECK(result == PP_OK);
    435   switch (error_type) {
    436     case OPEN_ERROR:
    437       client_->OnOpenComplete(cdm::FileIOClient::kError);
    438       break;
    439     case READ_ERROR:
    440       client_->OnReadComplete(cdm::FileIOClient::kError, NULL, 0);
    441       break;
    442     case WRITE_ERROR:
    443       client_->OnWriteComplete(cdm::FileIOClient::kError);
    444       break;
    445     case OPEN_WHILE_IN_USE:
    446       client_->OnOpenComplete(cdm::FileIOClient::kInUse);
    447       break;
    448     case READ_WHILE_IN_USE:
    449       client_->OnReadComplete(cdm::FileIOClient::kInUse, NULL, 0);
    450       break;
    451     case WRITE_WHILE_IN_USE:
    452       client_->OnWriteComplete(cdm::FileIOClient::kInUse);
    453       break;
    454   }
    455 }
    456 
    457 }  // namespace media
    458