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