Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(428)

Side by Side Diff: native_client_sdk/src/examples/demo/drive/drive.cc

Issue 14500010: [NaCl SDK] Google Drive example (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: comments Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2013 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 <ctype.h>
6 #include <stdarg.h>
7 #include <stdio.h>
8 #include <string.h>
9
10 #include <string>
11 #include <vector>
12
13 #include "json/reader.h"
14 #include "json/writer.h"
15 #include "ppapi/c/pp_errors.h"
16 #include "ppapi/cpp/completion_callback.h"
17 #include "ppapi/cpp/instance.h"
18 #include "ppapi/cpp/module.h"
19 #include "ppapi/cpp/url_loader.h"
20 #include "ppapi/cpp/url_request_info.h"
21 #include "ppapi/cpp/url_response_info.h"
22 #include "ppapi/cpp/var.h"
23 #include "ppapi/utility/completion_callback_factory.h"
24 #include "ppapi/utility/threading/simple_thread.h"
25
26 namespace {
27
noelallen1 2013/05/03 18:31:12 Comment, what is this?
binji 2013/05/03 23:00:39 Done.
28 const char kBoundary[] = "NACL_BOUNDARY_600673";
29
30 // This is a simple implementation of JavaScript's encodeUriComponent. We
31 // assume the data is already UTF-8. See
32 // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/ encodeURIComponent.
33 std::string EncodeUriComponent(const std::string& s) {
34 char hex[] = "0123456789ABCDEF";
35 std::string result;
36 for (size_t i = 0; i < s.length(); ++i) {
37 char c = s[i];
38 if (isalpha(c) || isdigit(c) || strchr("-_.!~*'()", c)) {
39 result += c;
40 } else {
41 result += '%';
42 result += hex[(c >> 4) & 0xf];
43 result += hex[c & 0xf];
44 }
45 }
46 return result;
47 }
48
49 std::string IntToString(int x) {
50 char buffer[32];
51 snprintf(&buffer[0], 32, "%d", x);
52 return &buffer[0];
53 }
54
55 void AddQueryParameter(std::string* s,
56 const std::string& key,
57 const std::string& value,
58 bool first) {
59 *s += first ? '?' : '&';
60 *s += EncodeUriComponent(key);
61 *s += '=';
62 *s += EncodeUriComponent(value);
63 }
64
65 void AddQueryParameter(std::string* s,
66 const std::string& key,
67 int value,
68 bool first) {
69 AddQueryParameter(s, key, IntToString(value), first);
70 }
71
72 void AddAuthTokenHeader(std::string* s, const std::string& auth_token) {
73 *s += "Authorization: Bearer ";
74 *s += auth_token;
75 *s += "\n";
76 }
77
78 void AddHeader(std::string* s, const char* key, const std::string& value) {
79 *s += key;
80 *s += ": ";
81 *s += value;
82 *s += "\n";
83 }
84
85 } // namespace
86
87 //
88 // ReadUrl
89 //
90 struct ReadUrlParams {
91 std::string url;
92 std::string method;
93 std::string request_headers;
94 std::string request_body;
95 };
96
97 // This function blocks so it needs to be called off the main thread.
98 int32_t ReadUrl(pp::Instance* instance,
99 const ReadUrlParams& params,
100 std::string* output) {
101 pp::URLRequestInfo url_request(instance);
102 pp::URLLoader url_loader(instance);
103
104 url_request.SetURL(params.url);
105 url_request.SetMethod(params.method);
106 url_request.SetHeaders(params.request_headers);
107 url_request.SetRecordDownloadProgress(true);
108 if (params.request_body.size()) {
109 url_request.AppendDataToBody(params.request_body.data(),
110 params.request_body.size());
111 }
112
113 int32_t result = url_loader.Open(url_request, pp::BlockUntilComplete());
114 if (result != PP_OK) {
115 return result;
116 }
117
118 pp::URLResponseInfo url_response = url_loader.GetResponseInfo();
119 if (url_response.GetStatusCode() != 200)
120 return PP_ERROR_FAILED;
121
122 output->clear();
123
124 int64_t bytes_received = 0;
125 int64_t total_bytes_to_be_received = 0;
126 if (url_loader.GetDownloadProgress(&bytes_received,
127 &total_bytes_to_be_received)) {
128 if (total_bytes_to_be_received > 0) {
129 output->reserve(total_bytes_to_be_received);
130 }
131 }
132
133 url_request.SetRecordDownloadProgress(false);
134
135 const size_t kReadBufferSize = 16 * 1024;
136 uint8_t* buffer_ = new uint8_t[kReadBufferSize];
137
138 do {
139 result = url_loader.ReadResponseBody(
140 buffer_, kReadBufferSize, pp::BlockUntilComplete());
141 if (result > 0) {
142 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.
143 output->insert(output->end(), buffer_, buffer_ + num_bytes);
144 }
145 } while (result > 0);
146
147 delete[] buffer_;
148
149 return result;
150 }
151
152 //
153 // ListFiles
154 //
155 // This is a simplistic implementation of the files.list method defined here:
156 // https://developers.google.com/drive/v2/reference/files/list
157 //
158 struct ListFilesParams {
159 int max_results;
160 std::string page_token;
161 std::string query;
162 };
163
164 int32_t ListFiles(pp::Instance* instance,
165 const std::string& auth_token,
166 const ListFilesParams& params,
167 Json::Value* root) {
168 static const char base_url[] = "https://www.googleapis.com/drive/v2/files";
169
170 ReadUrlParams p;
171 p.method = "GET";
172 p.url = base_url;
173 AddQueryParameter(&p.url, "maxResults", params.max_results, true);
174 if (params.page_token.length())
175 AddQueryParameter(&p.url, "pageToken", params.page_token, false);
176 AddQueryParameter(&p.url, "q", params.query, false);
177 // Request a "partial response". See
178 // https://developers.google.com/drive/performance#partial for more
179 // information.
180 AddQueryParameter(&p.url, "fields", "items(id,downloadUrl)", false);
181 AddAuthTokenHeader(&p.request_headers, auth_token);
182
183 std::string output;
184 int32_t result = ReadUrl(instance, p, &output);
185 if (result != PP_OK) {
186 return result;
187 }
188
189 Json::Reader reader(Json::Features::strictMode());
190 if (!reader.parse(output, *root, false)) {
191 return PP_ERROR_FAILED;
192 }
193
194 return PP_OK;
195 }
196
197 //
198 // InsertFile
199 //
200 // This is a simplistic implementation of the files.update and files.insert
201 // methods defined here:
202 // https://developers.google.com/drive/v2/reference/files/insert
203 // https://developers.google.com/drive/v2/reference/files/update
204 //
205 struct InsertFileParams {
206 // If file_id is empty, create a new file (files.insert). If file_id is not
207 // empty, update that file (files.update)
208 std::string file_id;
209 std::string content;
210 std::string description;
211 std::string mime_type;
212 std::string title;
213 };
214
215 std::string BuildRequestBody(const InsertFileParams& params) {
216 // This generates the multipart-upload request body for InsertFile. See
217 // https://developers.google.com/drive/manage-uploads#multipart for more
218 // information.
219 std::string result;
220 result += "--";
221 result += kBoundary;
222 result += "\nContent-Type: application/json; charset=UTF-8\n\n";
223
224 Json::Value value(Json::objectValue);
225 if (!params.description.empty())
226 value["description"] = Json::Value(params.description);
227
228 if (!params.mime_type.empty())
229 value["mimeType"] = Json::Value(params.mime_type);
230
231 if (!params.title.empty())
232 value["title"] = Json::Value(params.title);
233
234 Json::FastWriter writer;
235 std::string metadata = writer.write(value);
236
237 result += metadata;
238 result += "--";
239 result += kBoundary;
240 result += "\nContent-Type: ";
241 result += params.mime_type;
242 result += "\n\n";
243 result += params.content;
244 result += "\n--";
245 result += kBoundary;
246 result += "--";
247 return result;
248 }
249
250 int32_t InsertFile(pp::Instance* instance,
251 const std::string& auth_token,
252 const InsertFileParams& params,
253 Json::Value* root) {
254 static const char base_url[] =
255 "https://www.googleapis.com/upload/drive/v2/files";
256 const char* method = "POST";
257
258 ReadUrlParams p;
259 p.url = base_url;
260
261 // If file_id is defined, we are actually updating an existing file.
262 if (!params.file_id.empty()) {
263 p.url += "/";
264 p.url += params.file_id;
265 p.method = "PUT";
266 } else {
267 p.method = "POST";
268 }
269
270 // We always use the multipart upload interface, but see
271 // https://developers.google.com/drive/manage-uploads for other
272 // options.
273 AddQueryParameter(&p.url, "uploadType", "multipart", true);
274 // Request a "partial response". See
275 // https://developers.google.com/drive/performance#partial for more
276 // information.
277 AddQueryParameter(&p.url, "fields", "id,downloadUrl", false);
278 AddAuthTokenHeader(&p.request_headers, auth_token);
279 AddHeader(&p.request_headers,
280 "Content-Type",
281 std::string("multipart/related; boundary=") + kBoundary + "\n");
282 p.request_body = BuildRequestBody(params);
283
284 std::string output;
285 int32_t result = ReadUrl(instance, p, &output);
286 if (result != PP_OK) {
287 return result;
288 }
289
290 Json::Reader reader(Json::Features::strictMode());
291 if (!reader.parse(output, *root, false)) {
292 return PP_ERROR_FAILED;
293 }
294
295 return PP_OK;
296 }
297
298 //
299 // Instance
300 //
301 class Instance : public pp::Instance {
302 public:
303 Instance(PP_Instance instance);
304 virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]);
305 virtual void HandleMessage(const pp::Var& var_message);
306
307 void PostMessagef(const char* format, ...);
308
309 private:
310 void ThreadSetAuthToken(int32_t, const std::string& auth_token);
311 void ThreadRequestThunk(int32_t);
312 bool ThreadRequest();
313 bool ThreadGetFileMetadata(const char* title, Json::Value* metadata);
314 bool ThreadCreateFile(const char* title,
315 const char* description,
316 const char* content,
317 Json::Value* metadata);
318 bool ThreadUpdateFile(const std::string& file_id,
319 const std::string& content,
320 Json::Value* metadata);
321 bool ThreadDownloadFile(const Json::Value& metadata, std::string* output);
322 bool GetMetadataKey(const Json::Value& metadata,
323 const char* key,
324 std::string* output);
325
326 pp::SimpleThread worker_thread_;
327 pp::CompletionCallbackFactory<Instance> callback_factory_;
328 std::string auth_token_;
329 bool is_processing_request_;
330 };
331
332 Instance::Instance(PP_Instance instance)
333 : pp::Instance(instance),
334 callback_factory_(this),
335 worker_thread_(this),
336 is_processing_request_(false) {}
337
338 bool Instance::Init(uint32_t /*argc*/,
339 const char * [] /*argn*/,
340 const char * [] /*argv*/) {
341 worker_thread_.Start();
342 return true;
343 }
344
345 void Instance::HandleMessage(const pp::Var& var_message) {
346 const char kTokenMessage[] = "token:";
347 const size_t kTokenMessageLen = strlen(kTokenMessage);
348 const char kGetFileMessage[] = "getFile";
349
350 if (!var_message.is_string()) {
351 return;
352 }
353
354 std::string message = var_message.AsString();
355 printf("Got message: \"%s\"\n", message.c_str());
356 if (message.compare(0, kTokenMessageLen, kTokenMessage) == 0) {
357 // Auth token
358 std::string auth_token = message.substr(kTokenMessageLen);
359 worker_thread_.message_loop().PostWork(callback_factory_.NewCallback(
360 &Instance::ThreadSetAuthToken, auth_token));
361 } else if (message == kGetFileMessage) {
362 // Request
363 if (!is_processing_request_) {
364 is_processing_request_ = true;
365 worker_thread_.message_loop().PostWork(
366 callback_factory_.NewCallback(&Instance::ThreadRequestThunk));
367 }
368 }
369 }
370
371 void Instance::PostMessagef(const char* format, ...) {
372 const size_t kBufferSize = 1024;
373 char buffer[kBufferSize];
374 va_list args;
375 va_start(args, format);
376 vsnprintf(&buffer[0], kBufferSize, format, args);
377
378 PostMessage(buffer);
379 }
380
381 void Instance::ThreadSetAuthToken(int32_t /*result*/,
382 const std::string& auth_token) {
383 printf("Got auth token: %s\n", auth_token.c_str());
384 auth_token_ = auth_token;
385 }
386
387 void Instance::ThreadRequestThunk(int32_t /*result*/) {
388 ThreadRequest();
389 is_processing_request_ = false;
390 }
391
392 bool Instance::ThreadRequest() {
393 static int request_count = 0;
394 static const char kTitle[] = "hello nacl.txt";
395 Json::Value metadata;
396 std::string output;
397
398 PostMessagef("log:\n Got request (#%d).\n", ++request_count);
399 PostMessagef("log: Looking for file: \"%s\".\n", kTitle);
400
401 if (!ThreadGetFileMetadata(kTitle, &metadata)) {
402 PostMessage("log: Not found! Creating a new file...\n");
403 // No data found, write a new file.
404 static const char kDescription[] = "A file generated by NaCl!";
405 static const char kInitialContent[] = "Hello, Google Drive!";
406
407 if (!ThreadCreateFile(kTitle, kDescription, kInitialContent, &metadata)) {
408 PostMessage("log: Creating the new file failed...\n");
409 return false;
410 }
411 } else {
412 PostMessage("log: Found it! Downloading the file...\n");
413 // Found the file, download it's data.
414 if (!ThreadDownloadFile(metadata, &output)) {
415 PostMessage("log: Downloading the file failed...\n");
416 return false;
417 }
418
419 // Modify it.
420 output += "\nHello, again Google Drive!";
421
422 std::string file_id;
423 if (!GetMetadataKey(metadata, "id", &file_id)) {
424 PostMessage("log: Couldn't find the file id...\n");
425 return false;
426 }
427
428 PostMessage("log: Updating the file...\n");
429 if (!ThreadUpdateFile(file_id, output, &metadata)) {
430 PostMessage("log: Failed to update the file...\n");
431 return false;
432 }
433 }
434
435 PostMessage("log: Done!\n");
436 PostMessage("log: Downloading the newly written file...\n");
437 if (!ThreadDownloadFile(metadata, &output)) {
438 PostMessage("log: Downloading the file failed...\n");
439 return false;
440 }
441
442 PostMessage("log: Done!\n");
443 PostMessage(output);
444 return true;
445 }
446
447 bool Instance::ThreadGetFileMetadata(const char* title, Json::Value* metadata) {
448 ListFilesParams p;
449 p.max_results = 1;
450 p.query = "title = \'";
451 p.query += title;
452 p.query += "\'";
453
454 Json::Value root;
455 int32_t result = ListFiles(this, auth_token_, p, &root);
456 if (result != PP_OK) {
457 PostMessagef("log: ListFiles failed with result %d\n", result);
458 return false;
459 }
460
461 // Extract the first item's metadata.
462 if (!root.isMember("items")) {
463 PostMessage("log: ListFiles returned no items...\n");
464 return false;
465 }
466
467 Json::Value items = root["items"];
468 if (!items.isValidIndex(0)) {
469 PostMessage("log: Expected items[0] to be valid.\n");
470 return false;
471 }
472
473 *metadata = items[0U];
474 return true;
475 }
476
477 bool Instance::ThreadCreateFile(const char* title,
478 const char* description,
479 const char* content,
480 Json::Value* metadata) {
481 InsertFileParams p;
482 p.content = content;
483 p.description = description;
484 p.mime_type = "text/plain";
485 p.title = title;
486
487 int32_t result = InsertFile(this, auth_token_, p, metadata);
488 if (result != PP_OK) {
489 PostMessagef("log: Creating file failed with result %d\n", result);
490 return false;
491 }
492
493 return true;
494 }
495
496 bool Instance::ThreadUpdateFile(const std::string& file_id,
497 const std::string& content,
498 Json::Value* metadata) {
499 InsertFileParams p;
500 p.file_id = file_id;
501 p.content = content;
502 p.mime_type = "text/plain";
503
504 int32_t result = InsertFile(this, auth_token_, p, metadata);
505 if (result != PP_OK) {
506 PostMessagef("log: Updating file failed with result %d\n", result);
507 return false;
508 }
509
510 return true;
511 }
512
513 bool Instance::ThreadDownloadFile(const Json::Value& metadata,
514 std::string* output) {
515 ReadUrlParams p;
516 p.method = "GET";
517
518 if (!GetMetadataKey(metadata, "downloadUrl", &p.url)) {
519 return false;
520 }
521
522 AddAuthTokenHeader(&p.request_headers, auth_token_);
523
524 int32_t result = ReadUrl(this, p, output);
525 if (result != PP_OK) {
526 PostMessagef("log: Downloading failed with result %d\n", result);
527 return false;
528 }
529
530 return true;
531 }
532
533 bool Instance::GetMetadataKey(const Json::Value& metadata,
534 const char* key,
535 std::string* output) {
536 Json::Value value = metadata[key];
537 if (!value.isString()) {
538 PostMessagef("log: Expected metadata.%s to be a string.\n", key);
539 return false;
540 }
541
542 *output = value.asString();
543 return true;
544 }
545
546 class Module : public pp::Module {
547 public:
548 Module() : pp::Module() {}
549 virtual ~Module() {}
550
551 virtual pp::Instance* CreateInstance(PP_Instance instance) {
552 return new Instance(instance);
553 }
554 };
555
556 namespace pp {
557
558 Module* CreateModule() { return new ::Module(); }
559
560 } // namespace pp
OLDNEW
« no previous file with comments | « native_client_sdk/src/examples/demo/drive/background.js ('k') | native_client_sdk/src/examples/demo/drive/example.dsc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698