root/native_client_sdk/src/examples/demo/drive/drive.cc

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

DEFINITIONS

This source file includes following definitions.
  1. EncodeUriComponent
  2. IntToString
  3. AddQueryParameter
  4. AddQueryParameter
  5. AddAuthTokenHeader
  6. AddHeader
  7. ReadUrl
  8. ListFiles
  9. BuildRequestBody
  10. InsertFile
  11. is_processing_request_
  12. Init
  13. HandleMessage
  14. PostMessagef
  15. ThreadSetAuthToken
  16. ThreadRequestThunk
  17. ThreadRequest
  18. ThreadGetFileMetadata
  19. ThreadCreateFile
  20. ThreadUpdateFile
  21. ThreadDownloadFile
  22. GetMetadataKey
  23. CreateInstance
  24. CreateModule

// Copyright (c) 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 <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#include <string>
#include <vector>

#include "json/reader.h"
#include "json/writer.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/cpp/completion_callback.h"
#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/url_loader.h"
#include "ppapi/cpp/url_request_info.h"
#include "ppapi/cpp/url_response_info.h"
#include "ppapi/cpp/var.h"
#include "ppapi/utility/completion_callback_factory.h"
#include "ppapi/utility/threading/simple_thread.h"

namespace {

// When we upload files, we also upload the metadata at the same time. To do so,
// we use the mimetype multipart/related. This mimetype requires specifying a
// boundary between the JSON metadata and the file content.
const char kBoundary[] = "NACL_BOUNDARY_600673";

// This is a simple implementation of JavaScript's encodeUriComponent. We
// assume the data is already UTF-8. See
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent.
std::string EncodeUriComponent(const std::string& s) {
  char hex[] = "0123456789ABCDEF";
  std::string result;
  for (size_t i = 0; i < s.length(); ++i) {
    char c = s[i];
    if (isalpha(c) || isdigit(c) || strchr("-_.!~*'()", c)) {
      result += c;
    } else {
      result += '%';
      result += hex[(c >> 4) & 0xf];
      result += hex[c & 0xf];
    }
  }
  return result;
}

std::string IntToString(int x) {
  char buffer[32];
  snprintf(&buffer[0], 32, "%d", x);
  return &buffer[0];
}

void AddQueryParameter(std::string* s,
                       const std::string& key,
                       const std::string& value,
                       bool first) {
  *s += first ? '?' : '&';
  *s += EncodeUriComponent(key);
  *s += '=';
  *s += EncodeUriComponent(value);
}

void AddQueryParameter(std::string* s,
                       const std::string& key,
                       int value,
                       bool first) {
  AddQueryParameter(s, key, IntToString(value), first);
}

void AddAuthTokenHeader(std::string* s, const std::string& auth_token) {
  *s += "Authorization: Bearer ";
  *s += auth_token;
  *s += "\n";
}

void AddHeader(std::string* s, const char* key, const std::string& value) {
  *s += key;
  *s += ": ";
  *s += value;
  *s += "\n";
}

}  // namespace

//
// ReadUrl
//
struct ReadUrlParams {
  std::string url;
  std::string method;
  std::string request_headers;
  std::string request_body;
};

// This function blocks so it needs to be called off the main thread.
int32_t ReadUrl(pp::Instance* instance,
                const ReadUrlParams& params,
                std::string* output) {
  pp::URLRequestInfo url_request(instance);
  pp::URLLoader url_loader(instance);

  url_request.SetURL(params.url);
  url_request.SetMethod(params.method);
  url_request.SetHeaders(params.request_headers);
  url_request.SetRecordDownloadProgress(true);
  if (params.request_body.size()) {
    url_request.AppendDataToBody(params.request_body.data(),
                                 params.request_body.size());
  }

  int32_t result = url_loader.Open(url_request, pp::BlockUntilComplete());
  if (result != PP_OK) {
    return result;
  }

  pp::URLResponseInfo url_response = url_loader.GetResponseInfo();
  if (url_response.GetStatusCode() != 200)
    return PP_ERROR_FAILED;

  output->clear();

  int64_t bytes_received = 0;
  int64_t total_bytes_to_be_received = 0;
  if (url_loader.GetDownloadProgress(&bytes_received,
                                     &total_bytes_to_be_received)) {
    if (total_bytes_to_be_received > 0) {
      output->reserve(total_bytes_to_be_received);
    }
  }

  url_request.SetRecordDownloadProgress(false);

  const int32_t kReadBufferSize = 16 * 1024;
  uint8_t* buffer_ = new uint8_t[kReadBufferSize];

  do {
    result = url_loader.ReadResponseBody(
        buffer_, kReadBufferSize, pp::BlockUntilComplete());
    if (result > 0) {
      assert(result <= kReadBufferSize);
      size_t num_bytes = result;
      output->insert(output->end(), buffer_, buffer_ + num_bytes);
    }
  } while (result > 0);

  delete[] buffer_;

  return result;
}

//
// ListFiles
//
// This is a simplistic implementation of the files.list method defined here:
// https://developers.google.com/drive/v2/reference/files/list
//
struct ListFilesParams {
  int max_results;
  std::string page_token;
  std::string query;
};

int32_t ListFiles(pp::Instance* instance,
                  const std::string& auth_token,
                  const ListFilesParams& params,
                  Json::Value* root) {
  static const char base_url[] = "https://www.googleapis.com/drive/v2/files";

  ReadUrlParams p;
  p.method = "GET";
  p.url = base_url;
  AddQueryParameter(&p.url, "maxResults", params.max_results, true);
  if (params.page_token.length())
    AddQueryParameter(&p.url, "pageToken", params.page_token, false);
  AddQueryParameter(&p.url, "q", params.query, false);
  // Request a "partial response". See
  // https://developers.google.com/drive/performance#partial for more
  // information.
  AddQueryParameter(&p.url, "fields", "items(id,downloadUrl)", false);
  AddAuthTokenHeader(&p.request_headers, auth_token);

  std::string output;
  int32_t result = ReadUrl(instance, p, &output);
  if (result != PP_OK) {
    return result;
  }

  Json::Reader reader(Json::Features::strictMode());
  if (!reader.parse(output, *root, false)) {
    return PP_ERROR_FAILED;
  }

  return PP_OK;
}

//
// InsertFile
//
// This is a simplistic implementation of the files.update and files.insert
// methods defined here:
// https://developers.google.com/drive/v2/reference/files/insert
// https://developers.google.com/drive/v2/reference/files/update
//
struct InsertFileParams {
  // If file_id is empty, create a new file (files.insert). If file_id is not
  // empty, update that file (files.update)
  std::string file_id;
  std::string content;
  std::string description;
  std::string mime_type;
  std::string title;
};

std::string BuildRequestBody(const InsertFileParams& params) {
  // This generates the multipart-upload request body for InsertFile. See
  // https://developers.google.com/drive/manage-uploads#multipart for more
  // information.
  std::string result;
  result += "--";
  result += kBoundary;
  result += "\nContent-Type: application/json; charset=UTF-8\n\n";

  Json::Value value(Json::objectValue);
  if (!params.description.empty())
    value["description"] = Json::Value(params.description);

  if (!params.mime_type.empty())
    value["mimeType"] = Json::Value(params.mime_type);

  if (!params.title.empty())
    value["title"] = Json::Value(params.title);

  Json::FastWriter writer;
  std::string metadata = writer.write(value);

  result += metadata;
  result += "--";
  result += kBoundary;
  result += "\nContent-Type: ";
  result += params.mime_type;
  result += "\n\n";
  result += params.content;
  result += "\n--";
  result += kBoundary;
  result += "--";
  return result;
}

int32_t InsertFile(pp::Instance* instance,
                   const std::string& auth_token,
                   const InsertFileParams& params,
                   Json::Value* root) {
  static const char base_url[] =
      "https://www.googleapis.com/upload/drive/v2/files";

  ReadUrlParams p;
  p.url = base_url;

  // If file_id is defined, we are actually updating an existing file.
  if (!params.file_id.empty()) {
    p.url += "/";
    p.url += params.file_id;
    p.method = "PUT";
  } else {
    p.method = "POST";
  }

  // We always use the multipart upload interface, but see
  // https://developers.google.com/drive/manage-uploads for other
  // options.
  AddQueryParameter(&p.url, "uploadType", "multipart", true);
  // Request a "partial response". See
  // https://developers.google.com/drive/performance#partial for more
  // information.
  AddQueryParameter(&p.url, "fields", "id,downloadUrl", false);
  AddAuthTokenHeader(&p.request_headers, auth_token);
  AddHeader(&p.request_headers,
            "Content-Type",
            std::string("multipart/related; boundary=") + kBoundary + "\n");
  p.request_body = BuildRequestBody(params);

  std::string output;
  int32_t result = ReadUrl(instance, p, &output);
  if (result != PP_OK) {
    return result;
  }

  Json::Reader reader(Json::Features::strictMode());
  if (!reader.parse(output, *root, false)) {
    return PP_ERROR_FAILED;
  }

  return PP_OK;
}

//
// Instance
//
class Instance : public pp::Instance {
 public:
  Instance(PP_Instance instance);
  virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]);
  virtual void HandleMessage(const pp::Var& var_message);

  void PostMessagef(const char* format, ...);

 private:
  void ThreadSetAuthToken(int32_t, const std::string& auth_token);
  void ThreadRequestThunk(int32_t);
  bool ThreadRequest();
  bool ThreadGetFileMetadata(const char* title, Json::Value* metadata);
  bool ThreadCreateFile(const char* title,
                        const char* description,
                        const char* content,
                        Json::Value* metadata);
  bool ThreadUpdateFile(const std::string& file_id,
                        const std::string& content,
                        Json::Value* metadata);
  bool ThreadDownloadFile(const Json::Value& metadata, std::string* output);
  bool GetMetadataKey(const Json::Value& metadata,
                      const char* key,
                      std::string* output);

  pp::SimpleThread worker_thread_;
  pp::CompletionCallbackFactory<Instance> callback_factory_;
  std::string auth_token_;
  bool is_processing_request_;
};

Instance::Instance(PP_Instance instance)
    : pp::Instance(instance),
      worker_thread_(this),
      callback_factory_(this),
      is_processing_request_(false) {}

bool Instance::Init(uint32_t /*argc*/,
                    const char * [] /*argn*/,
                    const char * [] /*argv*/) {
  worker_thread_.Start();
  return true;
}

void Instance::HandleMessage(const pp::Var& var_message) {
  const char kTokenMessage[] = "token:";
  const size_t kTokenMessageLen = strlen(kTokenMessage);
  const char kGetFileMessage[] = "getFile";

  if (!var_message.is_string()) {
    return;
  }

  std::string message = var_message.AsString();
  printf("Got message: \"%s\"\n", message.c_str());
  if (message.compare(0, kTokenMessageLen, kTokenMessage) == 0) {
    // Auth token
    std::string auth_token = message.substr(kTokenMessageLen);
    worker_thread_.message_loop().PostWork(callback_factory_.NewCallback(
        &Instance::ThreadSetAuthToken, auth_token));
  } else if (message == kGetFileMessage) {
    // Request
    if (!is_processing_request_) {
      is_processing_request_ = true;
      worker_thread_.message_loop().PostWork(
          callback_factory_.NewCallback(&Instance::ThreadRequestThunk));
    }
  }
}

void Instance::PostMessagef(const char* format, ...) {
  const size_t kBufferSize = 1024;
  char buffer[kBufferSize];
  va_list args;
  va_start(args, format);
  vsnprintf(&buffer[0], kBufferSize, format, args);

  PostMessage(buffer);
}

void Instance::ThreadSetAuthToken(int32_t /*result*/,
                                  const std::string& auth_token) {
  printf("Got auth token: %s\n", auth_token.c_str());
  auth_token_ = auth_token;
}

void Instance::ThreadRequestThunk(int32_t /*result*/) {
  ThreadRequest();
  is_processing_request_ = false;
}

bool Instance::ThreadRequest() {
  static int request_count = 0;
  static const char kTitle[] = "hello nacl.txt";
  Json::Value metadata;
  std::string output;

  PostMessagef("log:\n Got request (#%d).\n", ++request_count);
  PostMessagef("log: Looking for file: \"%s\".\n", kTitle);

  if (!ThreadGetFileMetadata(kTitle, &metadata)) {
    PostMessage("log: Not found! Creating a new file...\n");
    // No data found, write a new file.
    static const char kDescription[] = "A file generated by NaCl!";
    static const char kInitialContent[] = "Hello, Google Drive!";

    if (!ThreadCreateFile(kTitle, kDescription, kInitialContent, &metadata)) {
      PostMessage("log: Creating the new file failed...\n");
      return false;
    }
  } else {
    PostMessage("log: Found it! Downloading the file...\n");
    // Found the file, download it's data.
    if (!ThreadDownloadFile(metadata, &output)) {
      PostMessage("log: Downloading the file failed...\n");
      return false;
    }

    // Modify it.
    output += "\nHello, again Google Drive!";

    std::string file_id;
    if (!GetMetadataKey(metadata, "id", &file_id)) {
      PostMessage("log: Couldn't find the file id...\n");
      return false;
    }

    PostMessage("log: Updating the file...\n");
    if (!ThreadUpdateFile(file_id, output, &metadata)) {
      PostMessage("log: Failed to update the file...\n");
      return false;
    }
  }

  PostMessage("log: Done!\n");
  PostMessage("log: Downloading the newly written file...\n");
  if (!ThreadDownloadFile(metadata, &output)) {
    PostMessage("log: Downloading the file failed...\n");
    return false;
  }

  PostMessage("log: Done!\n");
  PostMessage(output);
  return true;
}

bool Instance::ThreadGetFileMetadata(const char* title, Json::Value* metadata) {
  ListFilesParams p;
  p.max_results = 1;
  p.query = "title = \'";
  p.query += title;
  p.query += "\'";

  Json::Value root;
  int32_t result = ListFiles(this, auth_token_, p, &root);
  if (result != PP_OK) {
    PostMessagef("log: ListFiles failed with result %d\n", result);
    return false;
  }

  // Extract the first item's metadata.
  if (!root.isMember("items")) {
    PostMessage("log: ListFiles returned no items...\n");
    return false;
  }

  Json::Value items = root["items"];
  if (!items.isValidIndex(0)) {
    PostMessage("log: Expected items[0] to be valid.\n");
    return false;
  }

  *metadata = items[0U];
  return true;
}

bool Instance::ThreadCreateFile(const char* title,
                                const char* description,
                                const char* content,
                                Json::Value* metadata) {
  InsertFileParams p;
  p.content = content;
  p.description = description;
  p.mime_type = "text/plain";
  p.title = title;

  int32_t result = InsertFile(this, auth_token_, p, metadata);
  if (result != PP_OK) {
    PostMessagef("log: Creating file failed with result %d\n", result);
    return false;
  }

  return true;
}

bool Instance::ThreadUpdateFile(const std::string& file_id,
                                const std::string& content,
                                Json::Value* metadata) {
  InsertFileParams p;
  p.file_id = file_id;
  p.content = content;
  p.mime_type = "text/plain";

  int32_t result = InsertFile(this, auth_token_, p, metadata);
  if (result != PP_OK) {
    PostMessagef("log: Updating file failed with result %d\n", result);
    return false;
  }

  return true;
}

bool Instance::ThreadDownloadFile(const Json::Value& metadata,
                                  std::string* output) {
  ReadUrlParams p;
  p.method = "GET";

  if (!GetMetadataKey(metadata, "downloadUrl", &p.url)) {
    return false;
  }

  AddAuthTokenHeader(&p.request_headers, auth_token_);

  int32_t result = ReadUrl(this, p, output);
  if (result != PP_OK) {
    PostMessagef("log: Downloading failed with result %d\n", result);
    return false;
  }

  return true;
}

bool Instance::GetMetadataKey(const Json::Value& metadata,
                              const char* key,
                              std::string* output) {
  Json::Value value = metadata[key];
  if (!value.isString()) {
    PostMessagef("log: Expected metadata.%s to be a string.\n", key);
    return false;
  }

  *output = value.asString();
  return true;
}

class Module : public pp::Module {
 public:
  Module() : pp::Module() {}
  virtual ~Module() {}

  virtual pp::Instance* CreateInstance(PP_Instance instance) {
    return new Instance(instance);
  }
};

namespace pp {

Module* CreateModule() { return new ::Module(); }

}  // namespace pp

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