- add sources.
[platform/framework/web/crosswalk.git] / src / native_client_sdk / src / examples / demo / drive / drive.cc
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 int32_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
261   ReadUrlParams p;
262   p.url = base_url;
263
264   // If file_id is defined, we are actually updating an existing file.
265   if (!params.file_id.empty()) {
266     p.url += "/";
267     p.url += params.file_id;
268     p.method = "PUT";
269   } else {
270     p.method = "POST";
271   }
272
273   // We always use the multipart upload interface, but see
274   // https://developers.google.com/drive/manage-uploads for other
275   // options.
276   AddQueryParameter(&p.url, "uploadType", "multipart", true);
277   // Request a "partial response". See
278   // https://developers.google.com/drive/performance#partial for more
279   // information.
280   AddQueryParameter(&p.url, "fields", "id,downloadUrl", false);
281   AddAuthTokenHeader(&p.request_headers, auth_token);
282   AddHeader(&p.request_headers,
283             "Content-Type",
284             std::string("multipart/related; boundary=") + kBoundary + "\n");
285   p.request_body = BuildRequestBody(params);
286
287   std::string output;
288   int32_t result = ReadUrl(instance, p, &output);
289   if (result != PP_OK) {
290     return result;
291   }
292
293   Json::Reader reader(Json::Features::strictMode());
294   if (!reader.parse(output, *root, false)) {
295     return PP_ERROR_FAILED;
296   }
297
298   return PP_OK;
299 }
300
301 //
302 // Instance
303 //
304 class Instance : public pp::Instance {
305  public:
306   Instance(PP_Instance instance);
307   virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]);
308   virtual void HandleMessage(const pp::Var& var_message);
309
310   void PostMessagef(const char* format, ...);
311
312  private:
313   void ThreadSetAuthToken(int32_t, const std::string& auth_token);
314   void ThreadRequestThunk(int32_t);
315   bool ThreadRequest();
316   bool ThreadGetFileMetadata(const char* title, Json::Value* metadata);
317   bool ThreadCreateFile(const char* title,
318                         const char* description,
319                         const char* content,
320                         Json::Value* metadata);
321   bool ThreadUpdateFile(const std::string& file_id,
322                         const std::string& content,
323                         Json::Value* metadata);
324   bool ThreadDownloadFile(const Json::Value& metadata, std::string* output);
325   bool GetMetadataKey(const Json::Value& metadata,
326                       const char* key,
327                       std::string* output);
328
329   pp::SimpleThread worker_thread_;
330   pp::CompletionCallbackFactory<Instance> callback_factory_;
331   std::string auth_token_;
332   bool is_processing_request_;
333 };
334
335 Instance::Instance(PP_Instance instance)
336     : pp::Instance(instance),
337       worker_thread_(this),
338       callback_factory_(this),
339       is_processing_request_(false) {}
340
341 bool Instance::Init(uint32_t /*argc*/,
342                     const char * [] /*argn*/,
343                     const char * [] /*argv*/) {
344   worker_thread_.Start();
345   return true;
346 }
347
348 void Instance::HandleMessage(const pp::Var& var_message) {
349   const char kTokenMessage[] = "token:";
350   const size_t kTokenMessageLen = strlen(kTokenMessage);
351   const char kGetFileMessage[] = "getFile";
352
353   if (!var_message.is_string()) {
354     return;
355   }
356
357   std::string message = var_message.AsString();
358   printf("Got message: \"%s\"\n", message.c_str());
359   if (message.compare(0, kTokenMessageLen, kTokenMessage) == 0) {
360     // Auth token
361     std::string auth_token = message.substr(kTokenMessageLen);
362     worker_thread_.message_loop().PostWork(callback_factory_.NewCallback(
363         &Instance::ThreadSetAuthToken, auth_token));
364   } else if (message == kGetFileMessage) {
365     // Request
366     if (!is_processing_request_) {
367       is_processing_request_ = true;
368       worker_thread_.message_loop().PostWork(
369           callback_factory_.NewCallback(&Instance::ThreadRequestThunk));
370     }
371   }
372 }
373
374 void Instance::PostMessagef(const char* format, ...) {
375   const size_t kBufferSize = 1024;
376   char buffer[kBufferSize];
377   va_list args;
378   va_start(args, format);
379   vsnprintf(&buffer[0], kBufferSize, format, args);
380
381   PostMessage(buffer);
382 }
383
384 void Instance::ThreadSetAuthToken(int32_t /*result*/,
385                                   const std::string& auth_token) {
386   printf("Got auth token: %s\n", auth_token.c_str());
387   auth_token_ = auth_token;
388 }
389
390 void Instance::ThreadRequestThunk(int32_t /*result*/) {
391   ThreadRequest();
392   is_processing_request_ = false;
393 }
394
395 bool Instance::ThreadRequest() {
396   static int request_count = 0;
397   static const char kTitle[] = "hello nacl.txt";
398   Json::Value metadata;
399   std::string output;
400
401   PostMessagef("log:\n Got request (#%d).\n", ++request_count);
402   PostMessagef("log: Looking for file: \"%s\".\n", kTitle);
403
404   if (!ThreadGetFileMetadata(kTitle, &metadata)) {
405     PostMessage("log: Not found! Creating a new file...\n");
406     // No data found, write a new file.
407     static const char kDescription[] = "A file generated by NaCl!";
408     static const char kInitialContent[] = "Hello, Google Drive!";
409
410     if (!ThreadCreateFile(kTitle, kDescription, kInitialContent, &metadata)) {
411       PostMessage("log: Creating the new file failed...\n");
412       return false;
413     }
414   } else {
415     PostMessage("log: Found it! Downloading the file...\n");
416     // Found the file, download it's data.
417     if (!ThreadDownloadFile(metadata, &output)) {
418       PostMessage("log: Downloading the file failed...\n");
419       return false;
420     }
421
422     // Modify it.
423     output += "\nHello, again Google Drive!";
424
425     std::string file_id;
426     if (!GetMetadataKey(metadata, "id", &file_id)) {
427       PostMessage("log: Couldn't find the file id...\n");
428       return false;
429     }
430
431     PostMessage("log: Updating the file...\n");
432     if (!ThreadUpdateFile(file_id, output, &metadata)) {
433       PostMessage("log: Failed to update the file...\n");
434       return false;
435     }
436   }
437
438   PostMessage("log: Done!\n");
439   PostMessage("log: Downloading the newly written file...\n");
440   if (!ThreadDownloadFile(metadata, &output)) {
441     PostMessage("log: Downloading the file failed...\n");
442     return false;
443   }
444
445   PostMessage("log: Done!\n");
446   PostMessage(output);
447   return true;
448 }
449
450 bool Instance::ThreadGetFileMetadata(const char* title, Json::Value* metadata) {
451   ListFilesParams p;
452   p.max_results = 1;
453   p.query = "title = \'";
454   p.query += title;
455   p.query += "\'";
456
457   Json::Value root;
458   int32_t result = ListFiles(this, auth_token_, p, &root);
459   if (result != PP_OK) {
460     PostMessagef("log: ListFiles failed with result %d\n", result);
461     return false;
462   }
463
464   // Extract the first item's metadata.
465   if (!root.isMember("items")) {
466     PostMessage("log: ListFiles returned no items...\n");
467     return false;
468   }
469
470   Json::Value items = root["items"];
471   if (!items.isValidIndex(0)) {
472     PostMessage("log: Expected items[0] to be valid.\n");
473     return false;
474   }
475
476   *metadata = items[0U];
477   return true;
478 }
479
480 bool Instance::ThreadCreateFile(const char* title,
481                                 const char* description,
482                                 const char* content,
483                                 Json::Value* metadata) {
484   InsertFileParams p;
485   p.content = content;
486   p.description = description;
487   p.mime_type = "text/plain";
488   p.title = title;
489
490   int32_t result = InsertFile(this, auth_token_, p, metadata);
491   if (result != PP_OK) {
492     PostMessagef("log: Creating file failed with result %d\n", result);
493     return false;
494   }
495
496   return true;
497 }
498
499 bool Instance::ThreadUpdateFile(const std::string& file_id,
500                                 const std::string& content,
501                                 Json::Value* metadata) {
502   InsertFileParams p;
503   p.file_id = file_id;
504   p.content = content;
505   p.mime_type = "text/plain";
506
507   int32_t result = InsertFile(this, auth_token_, p, metadata);
508   if (result != PP_OK) {
509     PostMessagef("log: Updating file failed with result %d\n", result);
510     return false;
511   }
512
513   return true;
514 }
515
516 bool Instance::ThreadDownloadFile(const Json::Value& metadata,
517                                   std::string* output) {
518   ReadUrlParams p;
519   p.method = "GET";
520
521   if (!GetMetadataKey(metadata, "downloadUrl", &p.url)) {
522     return false;
523   }
524
525   AddAuthTokenHeader(&p.request_headers, auth_token_);
526
527   int32_t result = ReadUrl(this, p, output);
528   if (result != PP_OK) {
529     PostMessagef("log: Downloading failed with result %d\n", result);
530     return false;
531   }
532
533   return true;
534 }
535
536 bool Instance::GetMetadataKey(const Json::Value& metadata,
537                               const char* key,
538                               std::string* output) {
539   Json::Value value = metadata[key];
540   if (!value.isString()) {
541     PostMessagef("log: Expected metadata.%s to be a string.\n", key);
542     return false;
543   }
544
545   *output = value.asString();
546   return true;
547 }
548
549 class Module : public pp::Module {
550  public:
551   Module() : pp::Module() {}
552   virtual ~Module() {}
553
554   virtual pp::Instance* CreateInstance(PP_Instance instance) {
555     return new Instance(instance);
556   }
557 };
558
559 namespace pp {
560
561 Module* CreateModule() { return new ::Module(); }
562
563 }  // namespace pp