root/media/cdm/ppapi/cdm_file_io_impl.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. IsMainThread
  2. PostOnMain
  3. io_offset_
  4. Open
  5. Read
  6. Write
  7. Close
  8. SetFileID
  9. AcquireFileLock
  10. ReleaseFileLock
  11. OpenFileSystem
  12. OnFileSystemOpened
  13. OpenFile
  14. OnFileOpened
  15. ReadFile
  16. OnFileRead
  17. SetLength
  18. OnLengthSet
  19. WriteFile
  20. OnFileWritten
  21. CloseFile
  22. OnError
  23. NotifyClientOfError

// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/cdm/ppapi/cdm_file_io_impl.h"

#include <algorithm>
#include <sstream>

#include "media/cdm/ppapi/cdm_logging.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/cpp/dev/url_util_dev.h"

namespace media {

const int kReadSize = 4 * 1024;  // Arbitrary choice.

// Call func_call and check the result. If the result is not
// PP_OK_COMPLETIONPENDING, print out logs, call OnError() and return.
#define CHECK_PP_OK_COMPLETIONPENDING(func_call, error_type)            \
  do {                                                                  \
    int32_t result = func_call;                                         \
    PP_DCHECK(result != PP_OK);                                         \
    if (result != PP_OK_COMPLETIONPENDING) {                            \
      CDM_DLOG() << #func_call << " failed with result: " << result;    \
      OnError(error_type);                                              \
      return;                                                           \
    }                                                                   \
  } while (0)

#if !defined(NDEBUG)
// PPAPI calls should only be made on the main thread. In this file, main thread
// checking is only performed in public APIs and the completion callbacks. This
// ensures all functions are running on the main thread since internal methods
// are called either by the public APIs or by the completion callbacks.
static bool IsMainThread() {
  return pp::Module::Get()->core()->IsMainThread();
}
#endif  // !defined(NDEBUG)

// Posts a task to run |cb| on the main thread. The task is posted even if the
// current thread is the main thread.
static void PostOnMain(pp::CompletionCallback cb) {
  pp::Module::Get()->core()->CallOnMainThread(0, cb, PP_OK);
}

CdmFileIOImpl::FileLockMap* CdmFileIOImpl::file_lock_map_ = NULL;

CdmFileIOImpl::ResourceTracker::ResourceTracker() {
  // Do nothing here since we lazy-initialize CdmFileIOImpl::file_lock_map_
  // in CdmFileIOImpl::AcquireFileLock().
}

CdmFileIOImpl::ResourceTracker::~ResourceTracker() {
  delete CdmFileIOImpl::file_lock_map_;
}

CdmFileIOImpl::CdmFileIOImpl(cdm::FileIOClient* client, PP_Instance pp_instance)
    : state_(FILE_UNOPENED),
      client_(client),
      pp_instance_handle_(pp_instance),
      callback_factory_(this),
      io_offset_(0) {
  PP_DCHECK(IsMainThread());
  PP_DCHECK(pp_instance);  // 0 indicates a "NULL handle".
}

CdmFileIOImpl::~CdmFileIOImpl() {
  PP_DCHECK(state_ == FILE_CLOSED);
}

// Call sequence: Open() -> OpenFileSystem() -> OpenFile() -> FILE_OPENED.
void CdmFileIOImpl::Open(const char* file_name, uint32_t file_name_size) {
  CDM_DLOG() << __FUNCTION__;
  PP_DCHECK(IsMainThread());

  if (state_ != FILE_UNOPENED) {
    CDM_DLOG() << "Open() called in an invalid state.";
    OnError(OPEN_ERROR);
    return;
  }

  // File name should not contain any path separators.
  std::string file_name_str(file_name, file_name_size);
  if (file_name_str.find('/') != std::string::npos ||
      file_name_str.find('\\') != std::string::npos) {
    CDM_DLOG() << "Invalid file name.";
    OnError(OPEN_ERROR);
    return;
  }

  // pp::FileRef only accepts path that begins with a '/' character.
  file_name_ = '/' + file_name_str;

  if (!AcquireFileLock()) {
    CDM_DLOG() << "File is in use by other cdm::FileIO objects.";
    OnError(OPEN_WHILE_IN_USE);
    return;
  }

  state_ = OPENING_FILE_SYSTEM;
  OpenFileSystem();
}

// Call sequence:
//                                       finished
// Read() -> ReadFile() -> OnFileRead() ----------> Done.
//               ^              |
//               | not finished |
//               |--------------|
void CdmFileIOImpl::Read() {
  CDM_DLOG() << __FUNCTION__;
  PP_DCHECK(IsMainThread());

  if (state_ == READING_FILE || state_ == WRITING_FILE) {
    CDM_DLOG() << "Read() called during pending read/write.";
    OnError(READ_WHILE_IN_USE);
    return;
  }

  if (state_ != FILE_OPENED) {
    CDM_DLOG() << "Read() called in an invalid state.";
    OnError(READ_ERROR);
    return;
  }

  PP_DCHECK(io_buffer_.empty());
  PP_DCHECK(cumulative_read_buffer_.empty());

  io_buffer_.resize(kReadSize);
  io_offset_ = 0;

  state_ = READING_FILE;
  ReadFile();
}

// Call sequence:
//                                            finished
// Write() -> WriteFile() -> OnFileWritten() ----------> Done.
//                ^                  |
//                |                  | not finished
//                |------------------|
void CdmFileIOImpl::Write(const uint8_t* data, uint32_t data_size) {
  CDM_DLOG() << __FUNCTION__;
  PP_DCHECK(IsMainThread());

  if (state_ == READING_FILE || state_ == WRITING_FILE) {
    CDM_DLOG() << "Write() called during pending read/write.";
    OnError(WRITE_WHILE_IN_USE);
    return;
  }

  if (state_ != FILE_OPENED) {
    CDM_DLOG() << "Write() called in an invalid state.";
    OnError(WRITE_ERROR);
    return;
  }

  PP_DCHECK(io_offset_ == 0);
  PP_DCHECK(io_buffer_.empty());
  if (data_size > 0)
    io_buffer_.assign(data, data + data_size);
  else
    PP_DCHECK(!data);

  state_ = WRITING_FILE;

  // Always SetLength() in case |data_size| is less than the file size.
  SetLength(data_size);
}

void CdmFileIOImpl::Close() {
  CDM_DLOG() << __FUNCTION__;
  PP_DCHECK(IsMainThread());
  PP_DCHECK(state_ != FILE_CLOSED);
  CloseFile();
  ReleaseFileLock();
  // All pending callbacks are canceled since |callback_factory_| is destroyed.
  delete this;
}

bool CdmFileIOImpl::SetFileID() {
  PP_DCHECK(file_id_.empty());
  PP_DCHECK(!file_name_.empty() && file_name_[0] == '/');

  // Not taking ownership of |url_util_dev| (which is a singleton).
  const pp::URLUtil_Dev* url_util_dev = pp::URLUtil_Dev::Get();
  PP_URLComponents_Dev components;
  pp::Var url_var =
      url_util_dev->GetDocumentURL(pp_instance_handle_, &components);
  if (!url_var.is_string())
    return false;
  std::string url = url_var.AsString();

  file_id_.append(url, components.scheme.begin, components.scheme.len);
  file_id_ += ':';
  file_id_.append(url, components.host.begin, components.host.len);
  file_id_ += ':';
  file_id_.append(url, components.port.begin, components.port.len);
  file_id_ += file_name_;

  return true;
}

bool CdmFileIOImpl::AcquireFileLock() {
  PP_DCHECK(IsMainThread());

  if (file_id_.empty() && !SetFileID())
    return false;

  if (!file_lock_map_) {
    file_lock_map_ = new FileLockMap();
  } else {
    FileLockMap::iterator found = file_lock_map_->find(file_id_);
    if (found != file_lock_map_->end() && found->second)
      return false;
  }

  (*file_lock_map_)[file_id_] = true;
  return true;
}

void CdmFileIOImpl::ReleaseFileLock() {
  PP_DCHECK(IsMainThread());

  if (!file_lock_map_)
    return;

  FileLockMap::iterator found = file_lock_map_->find(file_id_);
  if (found != file_lock_map_->end() && found->second)
    found->second = false;
}

void CdmFileIOImpl::OpenFileSystem() {
  PP_DCHECK(state_ == OPENING_FILE_SYSTEM);

  pp::CompletionCallbackWithOutput<pp::FileSystem> cb =
      callback_factory_.NewCallbackWithOutput(
          &CdmFileIOImpl::OnFileSystemOpened);
  isolated_file_system_ = pp::IsolatedFileSystemPrivate(
      pp_instance_handle_, PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_PLUGINPRIVATE);

  CHECK_PP_OK_COMPLETIONPENDING(isolated_file_system_.Open(cb), OPEN_ERROR);
}

void CdmFileIOImpl::OnFileSystemOpened(int32_t result,
                                       pp::FileSystem file_system) {
  PP_DCHECK(IsMainThread());
  PP_DCHECK(state_ == OPENING_FILE_SYSTEM);

  if (result != PP_OK) {
    CDM_DLOG() << "File system open failed asynchronously.";
    ReleaseFileLock();
    OnError(OPEN_ERROR);
    return;
  }

  file_system_ = file_system;
  state_ = OPENING_FILE;
  OpenFile();
}

void CdmFileIOImpl::OpenFile() {
  PP_DCHECK(state_ == OPENING_FILE);

  file_io_ = pp::FileIO(pp_instance_handle_);
  file_ref_ = pp::FileRef(file_system_, file_name_.c_str());
  int32_t file_open_flag = PP_FILEOPENFLAG_READ |
                           PP_FILEOPENFLAG_WRITE |
                           PP_FILEOPENFLAG_CREATE;
  pp::CompletionCallback cb =
      callback_factory_.NewCallback(&CdmFileIOImpl::OnFileOpened);
  CHECK_PP_OK_COMPLETIONPENDING(file_io_.Open(file_ref_, file_open_flag, cb),
                                OPEN_ERROR);
}

void CdmFileIOImpl::OnFileOpened(int32_t result) {
  PP_DCHECK(IsMainThread());
  PP_DCHECK(state_ == OPENING_FILE);

  if (result != PP_OK) {
    CDM_DLOG() << "File open failed.";
    ReleaseFileLock();
    OnError(OPEN_ERROR);
    return;
  }

  state_ = FILE_OPENED;
  client_->OnOpenComplete(cdm::FileIOClient::kSuccess);
}

void CdmFileIOImpl::ReadFile() {
  PP_DCHECK(state_ == READING_FILE);
  PP_DCHECK(!io_buffer_.empty());

  pp::CompletionCallback cb =
      callback_factory_.NewCallback(&CdmFileIOImpl::OnFileRead);
  CHECK_PP_OK_COMPLETIONPENDING(
      file_io_.Read(io_offset_, &io_buffer_[0], io_buffer_.size(), cb),
      READ_ERROR);
}

void CdmFileIOImpl::OnFileRead(int32_t bytes_read) {
  CDM_DLOG() << __FUNCTION__ << ": " << bytes_read;
  PP_DCHECK(IsMainThread());
  PP_DCHECK(state_ == READING_FILE);

  // 0 |bytes_read| indicates end-of-file reached.
  if (bytes_read < PP_OK) {
    CDM_DLOG() << "Read file failed.";
    OnError(READ_ERROR);
    return;
  }

  PP_DCHECK(static_cast<size_t>(bytes_read) <= io_buffer_.size());
  // Append |bytes_read| in |io_buffer_| to |cumulative_read_buffer_|.
  cumulative_read_buffer_.insert(cumulative_read_buffer_.end(),
                                 io_buffer_.begin(),
                                 io_buffer_.begin() + bytes_read);
  io_offset_ += bytes_read;

  // Not received end-of-file yet.
  if (bytes_read > 0) {
    ReadFile();
    return;
  }

  // We hit end-of-file. Return read data to the client.
  io_buffer_.clear();
  io_offset_ = 0;
  // Clear |cumulative_read_buffer_| in case OnReadComplete() calls Read() or
  // Write().
  std::vector<char> local_buffer;
  std::swap(cumulative_read_buffer_, local_buffer);

  state_ = FILE_OPENED;
  const uint8_t* data = local_buffer.empty() ?
      NULL : reinterpret_cast<const uint8_t*>(&local_buffer[0]);
  client_->OnReadComplete(
      cdm::FileIOClient::kSuccess, data, local_buffer.size());
}

void CdmFileIOImpl::SetLength(uint32_t length) {
  PP_DCHECK(state_ == WRITING_FILE);

  pp::CompletionCallback cb =
      callback_factory_.NewCallback(&CdmFileIOImpl::OnLengthSet);
  CHECK_PP_OK_COMPLETIONPENDING(file_io_.SetLength(length, cb), WRITE_ERROR);
}

void CdmFileIOImpl::OnLengthSet(int32_t result) {
  CDM_DLOG() << __FUNCTION__ << ": " << result;
  PP_DCHECK(IsMainThread());
  PP_DCHECK(state_ == WRITING_FILE);

  if (result != PP_OK) {
    CDM_DLOG() << "File SetLength failed.";
    OnError(WRITE_ERROR);
    return;
  }

  if (io_buffer_.empty()) {
    state_ = FILE_OPENED;
    client_->OnWriteComplete(cdm::FileIOClient::kSuccess);
    return;
  }

  WriteFile();
}

void CdmFileIOImpl::WriteFile() {
  PP_DCHECK(state_ == WRITING_FILE);
  PP_DCHECK(io_offset_ < io_buffer_.size());

  pp::CompletionCallback cb =
      callback_factory_.NewCallback(&CdmFileIOImpl::OnFileWritten);
  CHECK_PP_OK_COMPLETIONPENDING(file_io_.Write(io_offset_,
                                               &io_buffer_[io_offset_],
                                               io_buffer_.size() - io_offset_,
                                               cb),
                                WRITE_ERROR);
}

void CdmFileIOImpl::OnFileWritten(int32_t bytes_written) {
  CDM_DLOG() << __FUNCTION__ << ": " << bytes_written;
  PP_DCHECK(IsMainThread());
  PP_DCHECK(state_ == WRITING_FILE);

  if (bytes_written <= PP_OK) {
    CDM_DLOG() << "Write file failed.";
    OnError(READ_ERROR);
    return;
  }

  io_offset_ += bytes_written;
  PP_DCHECK(io_offset_ <= io_buffer_.size());

  if (io_offset_ < io_buffer_.size()) {
    WriteFile();
    return;
  }

  io_buffer_.clear();
  io_offset_ = 0;
  state_ = FILE_OPENED;
  client_->OnWriteComplete(cdm::FileIOClient::kSuccess);
}

void CdmFileIOImpl::CloseFile() {
  PP_DCHECK(IsMainThread());

  state_ = FILE_CLOSED;

  file_io_.Close();
  io_buffer_.clear();
  io_offset_ = 0;
  cumulative_read_buffer_.clear();
}

void CdmFileIOImpl::OnError(ErrorType error_type) {
  // For *_WHILE_IN_USE errors, do not reset these values. Otherwise, the
  // existing read/write operation will fail.
  if (error_type == READ_ERROR || error_type == WRITE_ERROR) {
    io_buffer_.clear();
    io_offset_ = 0;
    cumulative_read_buffer_.clear();
  }
  PostOnMain(callback_factory_.NewCallback(&CdmFileIOImpl::NotifyClientOfError,
                                           error_type));
}

void CdmFileIOImpl::NotifyClientOfError(int32_t result,
                                        ErrorType error_type) {
  PP_DCHECK(result == PP_OK);
  switch (error_type) {
    case OPEN_ERROR:
      client_->OnOpenComplete(cdm::FileIOClient::kError);
      break;
    case READ_ERROR:
      client_->OnReadComplete(cdm::FileIOClient::kError, NULL, 0);
      break;
    case WRITE_ERROR:
      client_->OnWriteComplete(cdm::FileIOClient::kError);
      break;
    case OPEN_WHILE_IN_USE:
      client_->OnOpenComplete(cdm::FileIOClient::kInUse);
      break;
    case READ_WHILE_IN_USE:
      client_->OnReadComplete(cdm::FileIOClient::kInUse, NULL, 0);
      break;
    case WRITE_WHILE_IN_USE:
      client_->OnWriteComplete(cdm::FileIOClient::kInUse);
      break;
  }
}

}  // namespace media

/* [<][>][^][v][top][bottom][index][help] */