text: // Copyright 2018 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 "content/browser/devtools/devtools_stream_blob.h"
  
  #include "base/base64.h"
  #include "base/bind.h"
  #include "base/strings/string_piece.h"
  #include "content/browser/blob_storage/chrome_blob_storage_context.h"
  #include "content/public/browser/browser_thread.h"
  #include "content/public/browser/storage_partition.h"
  #include "net/base/io_buffer.h"
  #include "storage/browser/blob/blob_data_handle.h"
  #include "storage/browser/blob/blob_reader.h"
  #include "storage/browser/blob/blob_storage_context.h"
  #include "storage/common/blob_storage/blob_storage_constants.h"
  
  namespace content {
  
  using storage::BlobReader;
  
  DevToolsStreamBlob::ReadRequest::ReadRequest(off_t position,
                                               size_t max_size,
                                               ReadCallback callback)
      : position(position), max_size(max_size), callback(std::move(callback)) {}
  
  DevToolsStreamBlob::ReadRequest::~ReadRequest() = default;
  
  DevToolsStreamBlob::DevToolsStreamBlob()
      : DevToolsIOContext::Stream(
            BrowserThread::GetTaskRunnerForThread(BrowserThread::IO)),
        last_read_pos_(0),
        failed_(false),
        is_binary_(false) {}
  
  DevToolsStreamBlob::~DevToolsStreamBlob() {
    if (blob_reader_)
      blob_reader_->Kill();
  }
  
  namespace {
  void UnregisterIfOpenFailed(base::WeakPtr<DevToolsIOContext> context,
                              const std::string& handle,
                              bool success) {
    if (!success && context)
      context->Close(handle);
  }
  }  // namespace
  
  // static
  scoped_refptr<DevToolsIOContext::Stream> DevToolsStreamBlob::Create(
      DevToolsIOContext* io_context,
      ChromeBlobStorageContext* blob_context,
      StoragePartition* partition,
      const std::string& handle,
      const std::string& uuid) {
    scoped_refptr<DevToolsStreamBlob> result = new DevToolsStreamBlob();
    result->Register(io_context, handle);
    result->Open(
        blob_context, partition, uuid,
        base::BindOnce(&UnregisterIfOpenFailed, io_context->AsWeakPtr(), handle));
    return std::move(result);
  }
  
  void DevToolsStreamBlob::ReadRequest::Fail() {
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                            base::BindOnce(std::move(callback), nullptr, false,
                                           Stream::StatusFailure));
  }
  
  void DevToolsStreamBlob::Open(scoped_refptr<ChromeBlobStorageContext> context,
                                StoragePartition* partition,
                                const std::string& handle,
                                OpenCallback callback) {
    BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
                            base::BindOnce(&DevToolsStreamBlob::OpenOnIO, this,
                                           context, handle, std::move(callback)));
  }
  
  void DevToolsStreamBlob::Read(off_t position,
                                size_t max_size,
                                ReadCallback callback) {
    std::unique_ptr<ReadRequest> request(
        new ReadRequest(position, max_size, std::move(callback)));
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::BindOnce(&DevToolsStreamBlob::ReadOnIO, this, std::move(request)));
  }
  
  void DevToolsStreamBlob::OpenOnIO(
      scoped_refptr<ChromeBlobStorageContext> blob_context,
      const std::string& uuid,
      OpenCallback callback) {
    DCHECK(!blob_handle_);
  
    storage::BlobStorageContext* bsc = blob_context->context();
    blob_handle_ = bsc->GetBlobDataFromUUID(uuid);
    if (!blob_handle_) {
      LOG(ERROR) << "No blob with uuid: " << uuid;
      FailOnIO(std::move(callback));
      return;
    }
    is_binary_ = !DevToolsIOContext::IsTextMimeType(blob_handle_->content_type());
    open_callback_ = std::move(callback);
    blob_handle_->RunOnConstructionComplete(
        base::BindOnce(&DevToolsStreamBlob::OnBlobConstructionComplete, this));
  }
  
  void DevToolsStreamBlob::OnBlobConstructionComplete(
      storage::BlobStatus status) {
    DCHECK(!BlobStatusIsPending(status));
    if (BlobStatusIsError(status)) {
      LOG(ERROR) << "Blob building failed: " << static_cast<int>(status);
      FailOnIO(std::move(open_callback_));
      return;
    }
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                            base::BindOnce(std::move(open_callback_), true));
    if (!pending_reads_.empty())
      StartReadRequest();
  }
  
  void DevToolsStreamBlob::ReadOnIO(std::unique_ptr<ReadRequest> request) {
    if (failed_) {
      request->Fail();
      return;
    }
    pending_reads_.push(std::move(request));
    if (pending_reads_.size() > 1 || open_callback_)
      return;
    StartReadRequest();
  }
  
  void DevToolsStreamBlob::FailOnIO() {
    failed_ = true;
    while (!pending_reads_.empty()) {
      pending_reads_.front()->Fail();
      pending_reads_.pop();
    }
  }
  
  void DevToolsStreamBlob::FailOnIO(OpenCallback callback) {
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                            base::BindOnce(std::move(callback), false));
    FailOnIO();
  }
  
  void DevToolsStreamBlob::StartReadRequest() {
    DCHECK_GE(pending_reads_.size(), 1UL);
    DCHECK(blob_handle_);
    DCHECK(!failed_);
  
    ReadRequest& request = *pending_reads_.front();
    if (request.position < 0)
      request.position = last_read_pos_;
    if (request.position != last_read_pos_)
      blob_reader_.reset();
    if (!blob_reader_)
      CreateReader();
    else
      BeginRead();
  }
  
  void DevToolsStreamBlob::BeginRead() {
    DCHECK_GE(pending_reads_.size(), 1UL);
    ReadRequest& request = *pending_reads_.front();
    if (!io_buf_ || static_cast<size_t>(io_buf_->size()) < request.max_size)
      io_buf_ = new net::IOBufferWithSize(request.max_size);
    int bytes_read;
    BlobReader::Status status = blob_reader_->Read(
        io_buf_.get(), request.max_size, &bytes_read,
        base::BindOnce(&DevToolsStreamBlob::OnReadComplete, this));
    if (status == BlobReader::Status::IO_PENDING)
      return;
    // This is for uniformity with the asynchronous case.
    if (status == BlobReader::Status::NET_ERROR) {
      bytes_read = blob_reader_->net_error();
      DCHECK_LT(0, bytes_read);
    }
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::BindOnce(&DevToolsStreamBlob::OnReadComplete, this, bytes_read));
  }
  
  void DevToolsStreamBlob::OnReadComplete(int bytes_read) {
    std::unique_ptr<ReadRequest> request = std::move(pending_reads_.front());
    pending_reads_.pop();
  
    Status status;
    std::unique_ptr<std::string> data(new std::string());
    bool base64_encoded = false;
  
    if (bytes_read < 0) {
      status = StatusFailure;
      LOG(ERROR) << "Error reading blob: " << net::ErrorToString(bytes_read);
    } else if (!bytes_read) {
      status = StatusEOF;
    } else {
      last_read_pos_ += bytes_read;
      status = blob_reader_->remaining_bytes() ? StatusSuccess : StatusEOF;
      if (is_binary_) {
        base64_encoded = true;
        Base64Encode(base::StringPiece(io_buf_->data(), bytes_read), data.get());
      } else {
        // TODO(caseq): truncate at UTF8 boundary.
        *data = std::string(io_buf_->data(), bytes_read);
      }
    }
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::BindOnce(std::move(request->callback), std::move(data),
                       base64_encoded, status));
    if (!pending_reads_.empty())
      StartReadRequest();
  }
  
  void DevToolsStreamBlob::CreateReader() {
    DCHECK(!blob_reader_);
    blob_reader_ = blob_handle_->CreateReader();
    BlobReader::Status status = blob_reader_->CalculateSize(
        base::BindOnce(&DevToolsStreamBlob::OnCalculateSizeComplete, this));
    if (status != BlobReader::Status::IO_PENDING) {
      OnCalculateSizeComplete(status == BlobReader::Status::NET_ERROR
                                  ? blob_reader_->net_error()
                                  : net::OK);
    }
  }
  
  void DevToolsStreamBlob::OnCalculateSizeComplete(int net_error) {
    if (net_error != net::OK) {
      FailOnIO();
      return;
    }
    off_t seek_to = pending_reads_.front()->position;
    if (seek_to != 0UL) {
      if (seek_to >= static_cast<off_t>(blob_reader_->total_size())) {
        OnReadComplete(0);
        return;
      }
      BlobReader::Status status = blob_reader_->SetReadRange(
          seek_to, blob_reader_->total_size() - seek_to);
      if (status != BlobReader::Status::DONE) {
        FailOnIO();
        return;
      }
    }
    BeginRead();
  }
  
  }  // namespace content
  