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

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