1// Copyright (c) 2012 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 <stdio.h>
6#include <stdlib.h>
7#include <algorithm>
8#include "ppapi/c/pp_errors.h"
9#include "ppapi/c/ppb_instance.h"
10#include "ppapi/cpp/module.h"
11#include "ppapi/cpp/var.h"
12
13#include "url_loader_handler.h"
14
15#ifdef WIN32
16#undef min
17#undef max
18#undef PostMessage
19
20// Allow 'this' in initializer list
21#pragma warning(disable : 4355)
22#endif
23
24URLLoaderHandler* URLLoaderHandler::Create(pp::Instance* instance,
25                                           const std::string& url) {
26  return new URLLoaderHandler(instance, url);
27}
28
29URLLoaderHandler::URLLoaderHandler(pp::Instance* instance,
30                                   const std::string& url)
31    : instance_(instance),
32      url_(url),
33      url_request_(instance),
34      url_loader_(instance),
35      buffer_(new char[READ_BUFFER_SIZE]),
36      cc_factory_(this) {
37  url_request_.SetURL(url);
38  url_request_.SetMethod("GET");
39  url_request_.SetRecordDownloadProgress(true);
40}
41
42URLLoaderHandler::~URLLoaderHandler() {
43  delete[] buffer_;
44  buffer_ = NULL;
45}
46
47void URLLoaderHandler::Start() {
48  pp::CompletionCallback cc =
49      cc_factory_.NewCallback(&URLLoaderHandler::OnOpen);
50  url_loader_.Open(url_request_, cc);
51}
52
53void URLLoaderHandler::OnOpen(int32_t result) {
54  if (result != PP_OK) {
55    ReportResultAndDie(url_, "pp::URLLoader::Open() failed", false);
56    return;
57  }
58  // Here you would process the headers. A real program would want to at least
59  // check the HTTP code and potentially cancel the request.
60  // pp::URLResponseInfo response = loader_.GetResponseInfo();
61
62  // Try to figure out how many bytes of data are going to be downloaded in
63  // order to allocate memory for the response body in advance (this will
64  // reduce heap traffic and also the amount of memory allocated).
65  // It is not a problem if this fails, it just means that the
66  // url_response_body_.insert() call in URLLoaderHandler::AppendDataBytes()
67  // will allocate the memory later on.
68  int64_t bytes_received = 0;
69  int64_t total_bytes_to_be_received = 0;
70  if (url_loader_.GetDownloadProgress(&bytes_received,
71                                      &total_bytes_to_be_received)) {
72    if (total_bytes_to_be_received > 0) {
73      url_response_body_.reserve(total_bytes_to_be_received);
74    }
75  }
76  // We will not use the download progress anymore, so just disable it.
77  url_request_.SetRecordDownloadProgress(false);
78
79  // Start streaming.
80  ReadBody();
81}
82
83void URLLoaderHandler::AppendDataBytes(const char* buffer, int32_t num_bytes) {
84  if (num_bytes <= 0)
85    return;
86  // Make sure we don't get a buffer overrun.
87  num_bytes = std::min(READ_BUFFER_SIZE, num_bytes);
88  // Note that we do *not* try to minimally increase the amount of allocated
89  // memory here by calling url_response_body_.reserve().  Doing so causes a
90  // lot of string reallocations that kills performance for large files.
91  url_response_body_.insert(
92      url_response_body_.end(), buffer, buffer + num_bytes);
93}
94
95void URLLoaderHandler::OnRead(int32_t result) {
96  if (result == PP_OK) {
97    // Streaming the file is complete, delete the read buffer since it is
98    // no longer needed.
99    delete[] buffer_;
100    buffer_ = NULL;
101    ReportResultAndDie(url_, url_response_body_, true);
102  } else if (result > 0) {
103    // The URLLoader just filled "result" number of bytes into our buffer.
104    // Save them and perform another read.
105    AppendDataBytes(buffer_, result);
106    ReadBody();
107  } else {
108    // A read error occurred.
109    ReportResultAndDie(
110        url_, "pp::URLLoader::ReadResponseBody() result<0", false);
111  }
112}
113
114void URLLoaderHandler::ReadBody() {
115  // Note that you specifically want an "optional" callback here. This will
116  // allow ReadBody() to return synchronously, ignoring your completion
117  // callback, if data is available. For fast connections and large files,
118  // reading as fast as we can will make a large performance difference
119  // However, in the case of a synchronous return, we need to be sure to run
120  // the callback we created since the loader won't do anything with it.
121  pp::CompletionCallback cc =
122      cc_factory_.NewOptionalCallback(&URLLoaderHandler::OnRead);
123  int32_t result = PP_OK;
124  do {
125    result = url_loader_.ReadResponseBody(buffer_, READ_BUFFER_SIZE, cc);
126    // Handle streaming data directly. Note that we *don't* want to call
127    // OnRead here, since in the case of result > 0 it will schedule
128    // another call to this function. If the network is very fast, we could
129    // end up with a deeply recursive stack.
130    if (result > 0) {
131      AppendDataBytes(buffer_, result);
132    }
133  } while (result > 0);
134
135  if (result != PP_OK_COMPLETIONPENDING) {
136    // Either we reached the end of the stream (result == PP_OK) or there was
137    // an error. We want OnRead to get called no matter what to handle
138    // that case, whether the error is synchronous or asynchronous. If the
139    // result code *is* COMPLETIONPENDING, our callback will be called
140    // asynchronously.
141    cc.Run(result);
142  }
143}
144
145void URLLoaderHandler::ReportResultAndDie(const std::string& fname,
146                                          const std::string& text,
147                                          bool success) {
148  ReportResult(fname, text, success);
149  delete this;
150}
151
152void URLLoaderHandler::ReportResult(const std::string& fname,
153                                    const std::string& text,
154                                    bool success) {
155  if (success)
156    printf("URLLoaderHandler::ReportResult(Ok).\n");
157  else
158    printf("URLLoaderHandler::ReportResult(Err). %s\n", text.c_str());
159  fflush(stdout);
160  if (instance_) {
161    pp::Var var_result(fname + "\n" + text);
162    instance_->PostMessage(var_result);
163  }
164}
165