 Chromium Code Reviews
 Chromium Code Reviews Issue 14500010:
  [NaCl SDK] Google Drive example  (Closed) 
  Base URL: svn://svn.chromium.org/chrome/trunk/src
    
  
    Issue 14500010:
  [NaCl SDK] Google Drive example  (Closed) 
  Base URL: svn://svn.chromium.org/chrome/trunk/src| Index: native_client_sdk/src/examples/demo/drive/drive.cc | 
| diff --git a/native_client_sdk/src/examples/demo/drive/drive.cc b/native_client_sdk/src/examples/demo/drive/drive.cc | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..d70a6d399d756fda829b71b5c664d802847c9777 | 
| --- /dev/null | 
| +++ b/native_client_sdk/src/examples/demo/drive/drive.cc | 
| @@ -0,0 +1,560 @@ | 
| +// 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 { | 
| + | 
| 
noelallen1
2013/05/03 18:31:12
Comment, what is this?
 
binji
2013/05/03 23:00:39
Done.
 | 
| +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 size_t kReadBufferSize = 16 * 1024; | 
| + uint8_t* buffer_ = new uint8_t[kReadBufferSize]; | 
| + | 
| + do { | 
| + result = url_loader.ReadResponseBody( | 
| + buffer_, kReadBufferSize, pp::BlockUntilComplete()); | 
| + if (result > 0) { | 
| + size_t num_bytes = result > kReadBufferSize ? kReadBufferSize : result; | 
| 
noelallen1
2013/05/03 18:31:12
How can result be larger than kReadBufferSize?
 
binji
2013/05/03 23:00:39
Done.
 | 
| + 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"; | 
| + const char* method = "POST"; | 
| + | 
| + 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), | 
| + callback_factory_(this), | 
| + worker_thread_(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 |