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..4e9bd98c53966cd9a19efc217b58dd3f3d8f413a |
--- /dev/null |
+++ b/native_client_sdk/src/examples/demo/drive/drive.cc |
@@ -0,0 +1,543 @@ |
+// 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" |
+ |
+namespace { |
+ |
+const char kTokenMessage[] = "token:"; |
+const char kGetFileMessage[] = "getFile"; |
+const char kBoundary[] = "NACL_BOUNDARY_600673"; |
+ |
+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, |
noelallen1
2013/04/30 18:46:08
Incorrect indent.
binji
2013/04/30 20:07:27
Done.
|
+ 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 |
+ |
+// |
+// UrlReader |
+// |
+struct UrlReaderParams { |
+ std::string url; |
+ std::string method; |
+ std::string request_headers; |
+ std::string request_body; |
+}; |
+ |
+int32_t ReadUrl(pp::Instance* instance, |
+ const UrlReaderParams& 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; |
+ output->insert(output->end(), buffer_, buffer_ + num_bytes); |
+ } |
+ } while (result > 0); |
+ |
+ delete[] buffer_; |
+ |
+ return result; |
+} |
+ |
+// |
+// FilesList |
+// |
+struct FilesListParams { |
+ int max_results; |
+ std::string page_token; |
+ std::string q; |
+}; |
+ |
+int32_t ListFiles(pp::Instance* instance, |
+ const std::string& auth_token, |
+ const FilesListParams& params, |
+ Json::Value* root) { |
+ static const char base_url[] = "https://www.googleapis.com/drive/v2/files"; |
+ |
+ UrlReaderParams 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.q, false); |
+ 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; |
+} |
+ |
+// |
+// FilesInsert |
+// |
+struct FilesInsertParams { |
+ std::string file_id; |
+ std::string content; |
+ std::string description; |
+ std::string mime_type; |
+ std::string title; |
+}; |
+ |
+std::string BuildRequestBody(const FilesInsertParams& params) { |
+ 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 FilesInsertParams& params, |
+ Json::Value* root) { |
+ static const char base_url[] = |
+ "https://www.googleapis.com/upload/drive/v2/files"; |
+ const char* method = "POST"; |
+ |
+ UrlReaderParams 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"; |
+ } |
+ |
+ AddQueryParameter(&p.url, "uploadType", "multipart", true); |
+ 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: |
+ explicit Instance(PP_Instance instance); |
+ virtual ~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: |
+ static void* ThreadThunk(void* param); |
+ void ThreadMainLoop(); |
+ 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); |
+ |
+ std::string auth_token_; |
+ pthread_t thread_; |
+ pthread_mutex_t mutex_; |
+ pthread_cond_t cond_; |
+ bool has_request_; |
+}; |
+ |
+Instance::Instance(PP_Instance instance) : pp::Instance(instance) {} |
+ |
+Instance::~Instance() {} |
noelallen1
2013/04/30 18:46:08
required?
binji
2013/04/30 20:07:27
Done.
|
+ |
+bool Instance::Init(uint32_t argc, const char* argn[], const char* argv[]) { |
noelallen1
2013/04/30 18:46:08
Unused args. We should be consistent, see file_io
binji
2013/04/30 20:07:27
Done.
|
+ pthread_create(&thread_, NULL, &Instance::ThreadThunk, this); |
+ pthread_mutex_init(&mutex_, NULL); |
+ pthread_cond_init(&cond_, NULL); |
+ return true; |
+} |
+ |
+void Instance::HandleMessage(const pp::Var& var_message) { |
+ if (!var_message.is_string()) { |
+ return; |
+ } |
+ |
+ std::string message = var_message.AsString(); |
+ if (!strncmp(message.c_str(), kTokenMessage, strlen(kTokenMessage))) { |
+ // Auth token |
+ auth_token_ = message.c_str() + strlen(kTokenMessage); |
noelallen1
2013/04/30 18:46:08
message.subst(strlen(kTokenMessage))?
noelallen1
2013/04/30 18:46:08
Is this thread safe? You are setting auth_token_
binji
2013/04/30 20:07:27
Done.
|
+ } else if (!strncmp( |
+ message.c_str(), kGetFileMessage, strlen(kGetFileMessage))) { |
noelallen1
2013/04/30 18:46:08
message.compare(0, strlen(kGetFileMessage), kGetFi
binji
2013/04/30 20:07:27
Done.
|
+ // getFile |
+ pthread_mutex_lock(&mutex_); |
+ has_request_ = true; |
+ pthread_cond_signal(&cond_); |
+ pthread_mutex_unlock(&mutex_); |
+ } |
+} |
+ |
+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); |
+} |
+ |
+// static |
+void* Instance::ThreadThunk(void* param) { |
+ static_cast<Instance*>(param)->ThreadMainLoop(); |
+ return NULL; |
+} |
+ |
+void Instance::ThreadMainLoop() { |
+ while (1) { |
+ pthread_mutex_lock(&mutex_); |
+ while (!has_request_) { |
+ pthread_cond_wait(&cond_, &mutex_); |
+ } |
+ has_request_ = false; |
+ pthread_mutex_unlock(&mutex_); |
+ |
+ // Perform the request. |
+ ThreadRequest(); |
+ } |
+} |
+ |
+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)) { |
+ PostMessagef("log: Not found! Creating a new file...\n"); |
+ // No data found, write a new file. |
+ static const char kDescription[] = "A file generated by NaCl!"; |
noelallen1
2013/04/30 18:46:08
Why is this local to ThreadRequest, but kToken is
binji
2013/04/30 20:07:27
Done.
|
+ static const char kInitialContent[] = "Hello, Google Drive!"; |
+ |
+ if (!ThreadCreateFile(kTitle, kDescription, kInitialContent, &metadata)) { |
+ PostMessagef("log: Creating the new file failed...\n"); |
+ return false; |
+ } |
+ } else { |
+ PostMessagef("log: Found it! Downloading the file...\n"); |
+ // Found the file, download it's data. |
+ output.clear(); |
+ if (!ThreadDownloadFile(metadata, &output)) { |
+ PostMessagef("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)) { |
+ PostMessagef("log: Couldn't find the file id...\n"); |
+ return false; |
+ } |
+ |
+ PostMessagef("log: Updating the file...\n"); |
+ if (!ThreadUpdateFile(file_id, output, &metadata)) { |
+ PostMessagef("log: Failed to update the file...\n"); |
+ return false; |
+ } |
+ } |
+ |
+ PostMessagef("log: Done!\n"); |
+ PostMessagef("log: Downloading the newly written file...\n"); |
+ output.clear(); |
+ if (!ThreadDownloadFile(metadata, &output)) { |
+ PostMessagef("log: Downloading the file failed...\n"); |
+ return false; |
+ } |
+ |
+ PostMessagef("log: Done!\n"); |
+ PostMessage(output); |
noelallen1
2013/04/30 18:46:08
Why PostMessage here but not above? Both cases ar
binji
2013/04/30 20:07:27
Done.
|
+ return true; |
+} |
+ |
+bool Instance::ThreadGetFileMetadata(const char* title, Json::Value* metadata) { |
+ FilesListParams p; |
+ p.max_results = 1; |
+ p.q = "title = \'"; |
noelallen1
2013/04/30 18:46:08
Var name p.q?
binji
2013/04/30 20:07:27
Done.
|
+ p.q += title; |
+ p.q += "\'"; |
+ |
+ 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")) { |
+ PostMessagef("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) { |
+ FilesInsertParams 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) { |
+ FilesInsertParams 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) { |
+ UrlReaderParams 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 |