- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / google_apis / gdata_wapi_parser.cc
1 // Copyright (c) 2012 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 "chrome/browser/google_apis/gdata_wapi_parser.h"
6
7 #include <algorithm>
8 #include <string>
9
10 #include "base/basictypes.h"
11 #include "base/files/file_path.h"
12 #include "base/json/json_value_converter.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_piece.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/values.h"
19 #include "chrome/browser/google_apis/time_util.h"
20
21 using base::Value;
22 using base::DictionaryValue;
23 using base::ListValue;
24
25 namespace google_apis {
26
27 namespace {
28
29 // Term values for kSchemeKind category:
30 const char kTermPrefix[] = "http://schemas.google.com/docs/2007#";
31
32 // Node names.
33 const char kEntryNode[] = "entry";
34
35 // Field names.
36 const char kAuthorField[] = "author";
37 const char kCategoryField[] = "category";
38 const char kChangestampField[] = "docs$changestamp.value";
39 const char kContentField[] = "content";
40 const char kDeletedField[] = "gd$deleted";
41 const char kETagField[] = "gd$etag";
42 const char kEmailField[] = "email.$t";
43 const char kEntryField[] = "entry";
44 const char kFeedField[] = "feed";
45 const char kFeedLinkField[] = "gd$feedLink";
46 const char kFileNameField[] = "docs$filename.$t";
47 const char kHrefField[] = "href";
48 const char kIDField[] = "id.$t";
49 const char kInstalledAppField[] = "docs$installedApp";
50 const char kInstalledAppNameField[] = "docs$installedAppName";
51 const char kInstalledAppIdField[] = "docs$installedAppId";
52 const char kInstalledAppIconField[] = "docs$installedAppIcon";
53 const char kInstalledAppIconCategoryField[] = "docs$installedAppIconCategory";
54 const char kInstalledAppIconSizeField[] = "docs$installedAppIconSize";
55 const char kInstalledAppObjectTypeField[] = "docs$installedAppObjectType";
56 const char kInstalledAppPrimaryFileExtensionField[] =
57     "docs$installedAppPrimaryFileExtension";
58 const char kInstalledAppPrimaryMimeTypeField[] =
59     "docs$installedAppPrimaryMimeType";
60 const char kInstalledAppSecondaryFileExtensionField[] =
61     "docs$installedAppSecondaryFileExtension";
62 const char kInstalledAppSecondaryMimeTypeField[] =
63     "docs$installedAppSecondaryMimeType";
64 const char kInstalledAppSupportsCreateField[] =
65     "docs$installedAppSupportsCreate";
66 const char kItemsPerPageField[] = "openSearch$itemsPerPage.$t";
67 const char kLabelField[] = "label";
68 const char kLargestChangestampField[] = "docs$largestChangestamp.value";
69 const char kLastViewedField[] = "gd$lastViewed.$t";
70 const char kLinkField[] = "link";
71 const char kMD5Field[] = "docs$md5Checksum.$t";
72 const char kNameField[] = "name.$t";
73 const char kPublishedField[] = "published.$t";
74 const char kQuotaBytesTotalField[] = "gd$quotaBytesTotal.$t";
75 const char kQuotaBytesUsedField[] = "gd$quotaBytesUsed.$t";
76 const char kRelField[] = "rel";
77 const char kRemovedField[] = "docs$removed";
78 const char kResourceIdField[] = "gd$resourceId.$t";
79 const char kSchemeField[] = "scheme";
80 const char kSizeField[] = "docs$size.$t";
81 const char kSrcField[] = "src";
82 const char kStartIndexField[] = "openSearch$startIndex.$t";
83 const char kSuggestedFileNameField[] = "docs$suggestedFilename.$t";
84 const char kTField[] = "$t";
85 const char kTermField[] = "term";
86 const char kTitleField[] = "title";
87 const char kTitleTField[] = "title.$t";
88 const char kTypeField[] = "type";
89 const char kUpdatedField[] = "updated.$t";
90
91 // Link Prefixes
92 const char kOpenWithPrefix[] = "http://schemas.google.com/docs/2007#open-with-";
93 const size_t kOpenWithPrefixSize = arraysize(kOpenWithPrefix) - 1;
94
95 struct EntryKindMap {
96   DriveEntryKind kind;
97   const char* entry;
98   const char* extension;
99 };
100
101 const EntryKindMap kEntryKindMap[] = {
102     { ENTRY_KIND_UNKNOWN,      "unknown",      NULL},
103     { ENTRY_KIND_ITEM,         "item",         NULL},
104     { ENTRY_KIND_DOCUMENT,     "document",     ".gdoc"},
105     { ENTRY_KIND_SPREADSHEET,  "spreadsheet",  ".gsheet"},
106     { ENTRY_KIND_PRESENTATION, "presentation", ".gslides" },
107     { ENTRY_KIND_DRAWING,      "drawing",      ".gdraw"},
108     { ENTRY_KIND_TABLE,        "table",        ".gtable"},
109     { ENTRY_KIND_FORM,         "form",         ".gform"},
110     { ENTRY_KIND_EXTERNAL_APP, "externalapp",  ".glink"},
111     { ENTRY_KIND_SITE,         "site",         NULL},
112     { ENTRY_KIND_FOLDER,       "folder",       NULL},
113     { ENTRY_KIND_FILE,         "file",         NULL},
114     { ENTRY_KIND_PDF,          "pdf",          NULL},
115 };
116 COMPILE_ASSERT(arraysize(kEntryKindMap) == ENTRY_KIND_MAX_VALUE,
117                EntryKindMap_and_DriveEntryKind_are_not_in_sync);
118
119 struct LinkTypeMap {
120   Link::LinkType type;
121   const char* rel;
122 };
123
124 const LinkTypeMap kLinkTypeMap[] = {
125     { Link::LINK_SELF,
126       "self" },
127     { Link::LINK_NEXT,
128       "next" },
129     { Link::LINK_PARENT,
130       "http://schemas.google.com/docs/2007#parent" },
131     { Link::LINK_ALTERNATE,
132       "alternate"},
133     { Link::LINK_EDIT,
134       "edit" },
135     { Link::LINK_EDIT_MEDIA,
136       "edit-media" },
137     { Link::LINK_ALT_EDIT_MEDIA,
138       "http://schemas.google.com/docs/2007#alt-edit-media" },
139     { Link::LINK_ALT_POST,
140       "http://schemas.google.com/docs/2007#alt-post" },
141     { Link::LINK_FEED,
142       "http://schemas.google.com/g/2005#feed"},
143     { Link::LINK_POST,
144       "http://schemas.google.com/g/2005#post"},
145     { Link::LINK_BATCH,
146       "http://schemas.google.com/g/2005#batch"},
147     { Link::LINK_THUMBNAIL,
148       "http://schemas.google.com/docs/2007/thumbnail"},
149     { Link::LINK_RESUMABLE_EDIT_MEDIA,
150       "http://schemas.google.com/g/2005#resumable-edit-media"},
151     { Link::LINK_RESUMABLE_CREATE_MEDIA,
152       "http://schemas.google.com/g/2005#resumable-create-media"},
153     { Link::LINK_TABLES_FEED,
154       "http://schemas.google.com/spreadsheets/2006#tablesfeed"},
155     { Link::LINK_WORKSHEET_FEED,
156       "http://schemas.google.com/spreadsheets/2006#worksheetsfeed"},
157     { Link::LINK_EMBED,
158       "http://schemas.google.com/docs/2007#embed"},
159     { Link::LINK_PRODUCT,
160       "http://schemas.google.com/docs/2007#product"},
161     { Link::LINK_ICON,
162       "http://schemas.google.com/docs/2007#icon"},
163     { Link::LINK_SHARE,
164       "http://schemas.google.com/docs/2007#share"},
165 };
166
167 struct ResourceLinkTypeMap {
168   ResourceLink::ResourceLinkType type;
169   const char* rel;
170 };
171
172 const ResourceLinkTypeMap kFeedLinkTypeMap[] = {
173     { ResourceLink::FEED_LINK_ACL,
174       "http://schemas.google.com/acl/2007#accessControlList" },
175     { ResourceLink::FEED_LINK_REVISIONS,
176       "http://schemas.google.com/docs/2007/revisions" },
177 };
178
179 struct CategoryTypeMap {
180   Category::CategoryType type;
181   const char* scheme;
182 };
183
184 const CategoryTypeMap kCategoryTypeMap[] = {
185     { Category::CATEGORY_KIND, "http://schemas.google.com/g/2005#kind" },
186     { Category::CATEGORY_LABEL, "http://schemas.google.com/g/2005/labels" },
187 };
188
189 struct AppIconCategoryMap {
190   AppIcon::IconCategory category;
191   const char* category_name;
192 };
193
194 const AppIconCategoryMap kAppIconCategoryMap[] = {
195     { AppIcon::ICON_DOCUMENT, "document" },
196     { AppIcon::ICON_APPLICATION, "application" },
197     { AppIcon::ICON_SHARED_DOCUMENT, "documentShared" },
198 };
199
200 // Converts |url_string| to |result|.  Always returns true to be used
201 // for JSONValueConverter::RegisterCustomField method.
202 // TODO(mukai): make it return false in case of invalid |url_string|.
203 bool GetGURLFromString(const base::StringPiece& url_string, GURL* result) {
204   *result = GURL(url_string.as_string());
205   return true;
206 }
207
208 // Converts boolean string values like "true" into bool.
209 bool GetBoolFromString(const base::StringPiece& value, bool* result) {
210   *result = (value == "true");
211   return true;
212 }
213
214 bool SortBySize(const InstalledApp::IconList::value_type& a,
215                 const InstalledApp::IconList::value_type& b) {
216   return a.first < b.first;
217 }
218
219 }  // namespace
220
221 ////////////////////////////////////////////////////////////////////////////////
222 // Author implementation
223
224 Author::Author() {
225 }
226
227 // static
228 void Author::RegisterJSONConverter(
229     base::JSONValueConverter<Author>* converter) {
230   converter->RegisterStringField(kNameField, &Author::name_);
231   converter->RegisterStringField(kEmailField, &Author::email_);
232 }
233
234 ////////////////////////////////////////////////////////////////////////////////
235 // Link implementation
236
237 Link::Link() : type_(Link::LINK_UNKNOWN) {
238 }
239
240 Link::~Link() {
241 }
242
243 // static
244 bool Link::GetAppID(const base::StringPiece& rel, std::string* app_id) {
245   DCHECK(app_id);
246   // Fast return path if the link clearly isn't an OPEN_WITH link.
247   if (rel.size() < kOpenWithPrefixSize) {
248     app_id->clear();
249     return true;
250   }
251
252   const std::string kOpenWithPrefixStr(kOpenWithPrefix);
253   if (StartsWithASCII(rel.as_string(), kOpenWithPrefixStr, false)) {
254     *app_id = rel.as_string().substr(kOpenWithPrefixStr.size());
255     return true;
256   }
257
258   app_id->clear();
259   return true;
260 }
261
262 // static.
263 bool Link::GetLinkType(const base::StringPiece& rel, Link::LinkType* type) {
264   DCHECK(type);
265   for (size_t i = 0; i < arraysize(kLinkTypeMap); i++) {
266     if (rel == kLinkTypeMap[i].rel) {
267       *type = kLinkTypeMap[i].type;
268       return true;
269     }
270   }
271
272   // OPEN_WITH links have extra information at the end of the rel that is unique
273   // for each one, so we can't just check the usual map. This check is slightly
274   // redundant to provide a quick skip if it's obviously not an OPEN_WITH url.
275   if (rel.size() >= kOpenWithPrefixSize &&
276       StartsWithASCII(rel.as_string(), kOpenWithPrefix, false)) {
277     *type = LINK_OPEN_WITH;
278     return true;
279   }
280
281   // Let unknown link types through, just report it; if the link type is needed
282   // in the future, add it into LinkType and kLinkTypeMap.
283   DVLOG(1) << "Ignoring unknown link type for rel " << rel;
284   *type = LINK_UNKNOWN;
285   return true;
286 }
287
288 // static
289 void Link::RegisterJSONConverter(base::JSONValueConverter<Link>* converter) {
290   converter->RegisterCustomField<Link::LinkType>(kRelField,
291                                                  &Link::type_,
292                                                  &Link::GetLinkType);
293   // We have to register kRelField twice because we extract two different pieces
294   // of data from the same rel field.
295   converter->RegisterCustomField<std::string>(kRelField,
296                                               &Link::app_id_,
297                                               &Link::GetAppID);
298   converter->RegisterCustomField(kHrefField, &Link::href_, &GetGURLFromString);
299   converter->RegisterStringField(kTitleField, &Link::title_);
300   converter->RegisterStringField(kTypeField, &Link::mime_type_);
301 }
302
303 ////////////////////////////////////////////////////////////////////////////////
304 // ResourceLink implementation
305
306 ResourceLink::ResourceLink() : type_(ResourceLink::FEED_LINK_UNKNOWN) {
307 }
308
309 // static.
310 bool ResourceLink::GetFeedLinkType(
311     const base::StringPiece& rel, ResourceLink::ResourceLinkType* result) {
312   for (size_t i = 0; i < arraysize(kFeedLinkTypeMap); i++) {
313     if (rel == kFeedLinkTypeMap[i].rel) {
314       *result = kFeedLinkTypeMap[i].type;
315       return true;
316     }
317   }
318   DVLOG(1) << "Unknown feed link type for rel " << rel;
319   return false;
320 }
321
322 // static
323 void ResourceLink::RegisterJSONConverter(
324     base::JSONValueConverter<ResourceLink>* converter) {
325   converter->RegisterCustomField<ResourceLink::ResourceLinkType>(
326       kRelField, &ResourceLink::type_, &ResourceLink::GetFeedLinkType);
327   converter->RegisterCustomField(
328       kHrefField, &ResourceLink::href_, &GetGURLFromString);
329 }
330
331 ////////////////////////////////////////////////////////////////////////////////
332 // Category implementation
333
334 Category::Category() : type_(CATEGORY_UNKNOWN) {
335 }
336
337 // Converts category.scheme into CategoryType enum.
338 bool Category::GetCategoryTypeFromScheme(
339     const base::StringPiece& scheme, Category::CategoryType* result) {
340   for (size_t i = 0; i < arraysize(kCategoryTypeMap); i++) {
341     if (scheme == kCategoryTypeMap[i].scheme) {
342       *result = kCategoryTypeMap[i].type;
343       return true;
344     }
345   }
346   DVLOG(1) << "Unknown feed link type for scheme " << scheme;
347   return false;
348 }
349
350 // static
351 void Category::RegisterJSONConverter(
352     base::JSONValueConverter<Category>* converter) {
353   converter->RegisterStringField(kLabelField, &Category::label_);
354   converter->RegisterCustomField<Category::CategoryType>(
355       kSchemeField, &Category::type_, &Category::GetCategoryTypeFromScheme);
356   converter->RegisterStringField(kTermField, &Category::term_);
357 }
358
359 const Link* CommonMetadata::GetLinkByType(Link::LinkType type) const {
360   for (size_t i = 0; i < links_.size(); ++i) {
361     if (links_[i]->type() == type)
362       return links_[i];
363   }
364   return NULL;
365 }
366
367 ////////////////////////////////////////////////////////////////////////////////
368 // Content implementation
369
370 Content::Content() {
371 }
372
373 // static
374 void Content::RegisterJSONConverter(
375     base::JSONValueConverter<Content>* converter) {
376   converter->RegisterCustomField(kSrcField, &Content::url_, &GetGURLFromString);
377   converter->RegisterStringField(kTypeField, &Content::mime_type_);
378 }
379
380 ////////////////////////////////////////////////////////////////////////////////
381 // AppIcon implementation
382
383 AppIcon::AppIcon() : category_(AppIcon::ICON_UNKNOWN), icon_side_length_(0) {
384 }
385
386 AppIcon::~AppIcon() {
387 }
388
389 // static
390 void AppIcon::RegisterJSONConverter(
391     base::JSONValueConverter<AppIcon>* converter) {
392   converter->RegisterCustomField<AppIcon::IconCategory>(
393       kInstalledAppIconCategoryField,
394       &AppIcon::category_,
395       &AppIcon::GetIconCategory);
396   converter->RegisterCustomField<int>(kInstalledAppIconSizeField,
397                                       &AppIcon::icon_side_length_,
398                                       base::StringToInt);
399   converter->RegisterRepeatedMessage(kLinkField, &AppIcon::links_);
400 }
401
402 GURL AppIcon::GetIconURL() const {
403   for (size_t i = 0; i < links_.size(); ++i) {
404     if (links_[i]->type() == Link::LINK_ICON)
405       return links_[i]->href();
406   }
407   return GURL();
408 }
409
410 // static
411 bool AppIcon::GetIconCategory(const base::StringPiece& category,
412                               AppIcon::IconCategory* result) {
413   for (size_t i = 0; i < arraysize(kAppIconCategoryMap); i++) {
414     if (category == kAppIconCategoryMap[i].category_name) {
415       *result = kAppIconCategoryMap[i].category;
416       return true;
417     }
418   }
419   DVLOG(1) << "Unknown icon category " << category;
420   return false;
421 }
422
423 ////////////////////////////////////////////////////////////////////////////////
424 // CommonMetadata implementation
425
426 CommonMetadata::CommonMetadata() {
427 }
428
429 CommonMetadata::~CommonMetadata() {
430 }
431
432 // static
433 template<typename CommonMetadataDescendant>
434 void CommonMetadata::RegisterJSONConverter(
435     base::JSONValueConverter<CommonMetadataDescendant>* converter) {
436   converter->RegisterStringField(kETagField, &CommonMetadata::etag_);
437   converter->template RegisterRepeatedMessage<Author>(
438       kAuthorField, &CommonMetadata::authors_);
439   converter->template RegisterRepeatedMessage<Link>(
440       kLinkField, &CommonMetadata::links_);
441   converter->template RegisterRepeatedMessage<Category>(
442       kCategoryField, &CommonMetadata::categories_);
443   converter->template RegisterCustomField<base::Time>(
444       kUpdatedField, &CommonMetadata::updated_time_, &util::GetTimeFromString);
445 }
446
447 ////////////////////////////////////////////////////////////////////////////////
448 // ResourceEntry implementation
449
450 ResourceEntry::ResourceEntry()
451     : kind_(ENTRY_KIND_UNKNOWN),
452       file_size_(0),
453       deleted_(false),
454       removed_(false),
455       changestamp_(0),
456       image_width_(-1),
457       image_height_(-1),
458       image_rotation_(-1) {
459 }
460
461 ResourceEntry::~ResourceEntry() {
462 }
463
464 bool ResourceEntry::HasFieldPresent(const base::Value* value,
465                                     bool* result) {
466   *result = (value != NULL);
467   return true;
468 }
469
470 bool ResourceEntry::ParseChangestamp(const base::Value* value,
471                                      int64* result) {
472   DCHECK(result);
473   if (!value) {
474     *result = 0;
475     return true;
476   }
477
478   std::string string_value;
479   if (value->GetAsString(&string_value) &&
480       base::StringToInt64(string_value, result))
481     return true;
482
483   return false;
484 }
485
486 // static
487 void ResourceEntry::RegisterJSONConverter(
488     base::JSONValueConverter<ResourceEntry>* converter) {
489   // Inherit the parent registrations.
490   CommonMetadata::RegisterJSONConverter(converter);
491   converter->RegisterStringField(
492       kResourceIdField, &ResourceEntry::resource_id_);
493   converter->RegisterStringField(kIDField, &ResourceEntry::id_);
494   converter->RegisterStringField(kTitleTField, &ResourceEntry::title_);
495   converter->RegisterCustomField<base::Time>(
496       kPublishedField, &ResourceEntry::published_time_,
497       &util::GetTimeFromString);
498   converter->RegisterCustomField<base::Time>(
499       kLastViewedField, &ResourceEntry::last_viewed_time_,
500       &util::GetTimeFromString);
501   converter->RegisterRepeatedMessage(
502       kFeedLinkField, &ResourceEntry::resource_links_);
503   converter->RegisterNestedField(kContentField, &ResourceEntry::content_);
504
505   // File properties.  If the resource type is not a normal file, then
506   // that's no problem because those feed must not have these fields
507   // themselves, which does not report errors.
508   converter->RegisterStringField(kFileNameField, &ResourceEntry::filename_);
509   converter->RegisterStringField(kMD5Field, &ResourceEntry::file_md5_);
510   converter->RegisterCustomField<int64>(
511       kSizeField, &ResourceEntry::file_size_, &base::StringToInt64);
512   converter->RegisterStringField(
513       kSuggestedFileNameField, &ResourceEntry::suggested_filename_);
514   // Deleted are treated as 'trashed' items on web client side. Removed files
515   // are gone for good. We treat both cases as 'deleted' for this client.
516   converter->RegisterCustomValueField<bool>(
517       kDeletedField, &ResourceEntry::deleted_, &ResourceEntry::HasFieldPresent);
518   converter->RegisterCustomValueField<bool>(
519       kRemovedField, &ResourceEntry::removed_, &ResourceEntry::HasFieldPresent);
520   converter->RegisterCustomValueField<int64>(
521       kChangestampField, &ResourceEntry::changestamp_,
522       &ResourceEntry::ParseChangestamp);
523   // ImageMediaMetadata fields are not supported by WAPI.
524 }
525
526 std::string ResourceEntry::GetHostedDocumentExtension() const {
527   for (size_t i = 0; i < arraysize(kEntryKindMap); i++) {
528     if (kEntryKindMap[i].kind == kind_) {
529       if (kEntryKindMap[i].extension)
530         return std::string(kEntryKindMap[i].extension);
531       else
532         return std::string();
533     }
534   }
535   return std::string();
536 }
537
538 // static
539 int ResourceEntry::ClassifyEntryKindByFileExtension(
540     const base::FilePath& file_path) {
541 #if defined(OS_WIN)
542   std::string file_extension = WideToUTF8(file_path.Extension());
543 #else
544   std::string file_extension = file_path.Extension();
545 #endif
546   for (size_t i = 0; i < arraysize(kEntryKindMap); ++i) {
547     const char* document_extension = kEntryKindMap[i].extension;
548     if (document_extension && file_extension == document_extension)
549       return ClassifyEntryKind(kEntryKindMap[i].kind);
550   }
551   return 0;
552 }
553
554 // static
555 DriveEntryKind ResourceEntry::GetEntryKindFromTerm(
556     const std::string& term) {
557   if (!StartsWithASCII(term, kTermPrefix, false)) {
558     DVLOG(1) << "Unexpected term prefix term " << term;
559     return ENTRY_KIND_UNKNOWN;
560   }
561
562   std::string type = term.substr(strlen(kTermPrefix));
563   for (size_t i = 0; i < arraysize(kEntryKindMap); i++) {
564     if (type == kEntryKindMap[i].entry)
565       return kEntryKindMap[i].kind;
566   }
567   DVLOG(1) << "Unknown entry type for term " << term << ", type " << type;
568   return ENTRY_KIND_UNKNOWN;
569 }
570
571 // static
572 int ResourceEntry::ClassifyEntryKind(DriveEntryKind kind) {
573   int classes = 0;
574
575   // All DriveEntryKind members are listed here, so the compiler catches if a
576   // newly added member is missing here.
577   switch (kind) {
578     case ENTRY_KIND_UNKNOWN:
579     // Special entries.
580     case ENTRY_KIND_ITEM:
581     case ENTRY_KIND_SITE:
582       break;
583
584     // Hosted Google document.
585     case ENTRY_KIND_DOCUMENT:
586     case ENTRY_KIND_SPREADSHEET:
587     case ENTRY_KIND_PRESENTATION:
588     case ENTRY_KIND_DRAWING:
589     case ENTRY_KIND_TABLE:
590     case ENTRY_KIND_FORM:
591       classes = KIND_OF_GOOGLE_DOCUMENT | KIND_OF_HOSTED_DOCUMENT;
592       break;
593
594     // Hosted external application document.
595     case ENTRY_KIND_EXTERNAL_APP:
596       classes = KIND_OF_EXTERNAL_DOCUMENT | KIND_OF_HOSTED_DOCUMENT;
597       break;
598
599     // Folders, collections.
600     case ENTRY_KIND_FOLDER:
601       classes = KIND_OF_FOLDER;
602       break;
603
604     // Regular files.
605     case ENTRY_KIND_FILE:
606     case ENTRY_KIND_PDF:
607       classes = KIND_OF_FILE;
608       break;
609
610     case ENTRY_KIND_MAX_VALUE:
611       NOTREACHED();
612   }
613
614   return classes;
615 }
616
617 void ResourceEntry::FillRemainingFields() {
618   // Set |kind_| and |labels_| based on the |categories_| in the class.
619   // JSONValueConverter does not have the ability to catch an element in a list
620   // based on a predicate.  Thus we need to iterate over |categories_| and
621   // find the elements to set these fields as a post-process.
622   for (size_t i = 0; i < categories_.size(); ++i) {
623     const Category* category = categories_[i];
624     if (category->type() == Category::CATEGORY_KIND)
625       kind_ = GetEntryKindFromTerm(category->term());
626     else if (category->type() == Category::CATEGORY_LABEL)
627       labels_.push_back(category->label());
628   }
629 }
630
631 // static
632 scoped_ptr<ResourceEntry> ResourceEntry::ExtractAndParse(
633     const base::Value& value) {
634   const base::DictionaryValue* as_dict = NULL;
635   const base::DictionaryValue* entry_dict = NULL;
636   if (value.GetAsDictionary(&as_dict) &&
637       as_dict->GetDictionary(kEntryField, &entry_dict)) {
638     return ResourceEntry::CreateFrom(*entry_dict);
639   }
640   return scoped_ptr<ResourceEntry>();
641 }
642
643 // static
644 scoped_ptr<ResourceEntry> ResourceEntry::CreateFrom(const base::Value& value) {
645   base::JSONValueConverter<ResourceEntry> converter;
646   scoped_ptr<ResourceEntry> entry(new ResourceEntry());
647   if (!converter.Convert(value, entry.get())) {
648     DVLOG(1) << "Invalid resource entry!";
649     return scoped_ptr<ResourceEntry>();
650   }
651
652   entry->FillRemainingFields();
653   return entry.Pass();
654 }
655
656 // static
657 std::string ResourceEntry::GetEntryNodeName() {
658   return kEntryNode;
659 }
660
661 ////////////////////////////////////////////////////////////////////////////////
662 // ResourceList implementation
663
664 ResourceList::ResourceList()
665     : start_index_(0),
666       items_per_page_(0),
667       largest_changestamp_(0) {
668 }
669
670 ResourceList::~ResourceList() {
671 }
672
673 // static
674 void ResourceList::RegisterJSONConverter(
675     base::JSONValueConverter<ResourceList>* converter) {
676   // inheritance
677   CommonMetadata::RegisterJSONConverter(converter);
678   // TODO(zelidrag): Once we figure out where these will be used, we should
679   // check for valid start_index_ and items_per_page_ values.
680   converter->RegisterCustomField<int>(
681       kStartIndexField, &ResourceList::start_index_, &base::StringToInt);
682   converter->RegisterCustomField<int>(
683       kItemsPerPageField, &ResourceList::items_per_page_, &base::StringToInt);
684   converter->RegisterStringField(kTitleTField, &ResourceList::title_);
685   converter->RegisterRepeatedMessage(kEntryField, &ResourceList::entries_);
686   converter->RegisterCustomField<int64>(
687      kLargestChangestampField, &ResourceList::largest_changestamp_,
688      &base::StringToInt64);
689 }
690
691 bool ResourceList::Parse(const base::Value& value) {
692   base::JSONValueConverter<ResourceList> converter;
693   if (!converter.Convert(value, this)) {
694     DVLOG(1) << "Invalid resource list!";
695     return false;
696   }
697
698   ScopedVector<ResourceEntry>::iterator iter = entries_.begin();
699   while (iter != entries_.end()) {
700     ResourceEntry* entry = (*iter);
701     entry->FillRemainingFields();
702     ++iter;
703   }
704   return true;
705 }
706
707 // static
708 scoped_ptr<ResourceList> ResourceList::ExtractAndParse(
709     const base::Value& value) {
710   const base::DictionaryValue* as_dict = NULL;
711   const base::DictionaryValue* feed_dict = NULL;
712   if (value.GetAsDictionary(&as_dict) &&
713       as_dict->GetDictionary(kFeedField, &feed_dict)) {
714     return ResourceList::CreateFrom(*feed_dict);
715   }
716   return scoped_ptr<ResourceList>();
717 }
718
719 // static
720 scoped_ptr<ResourceList> ResourceList::CreateFrom(const base::Value& value) {
721   scoped_ptr<ResourceList> feed(new ResourceList());
722   if (!feed->Parse(value)) {
723     DVLOG(1) << "Invalid resource list!";
724     return scoped_ptr<ResourceList>();
725   }
726
727   return feed.Pass();
728 }
729
730 bool ResourceList::GetNextFeedURL(GURL* url) const {
731   DCHECK(url);
732   for (size_t i = 0; i < links_.size(); ++i) {
733     if (links_[i]->type() == Link::LINK_NEXT) {
734       *url = links_[i]->href();
735       return true;
736     }
737   }
738   return false;
739 }
740
741 void ResourceList::ReleaseEntries(std::vector<ResourceEntry*>* entries) {
742   entries_.release(entries);
743 }
744
745 ////////////////////////////////////////////////////////////////////////////////
746 // InstalledApp implementation
747
748 InstalledApp::InstalledApp() : supports_create_(false) {
749 }
750
751 InstalledApp::~InstalledApp() {
752 }
753
754 InstalledApp::IconList InstalledApp::GetIconsForCategory(
755     AppIcon::IconCategory category) const {
756   IconList result;
757
758   for (ScopedVector<AppIcon>::const_iterator icon_iter = app_icons_.begin();
759        icon_iter != app_icons_.end(); ++icon_iter) {
760     if ((*icon_iter)->category() != category)
761       continue;
762     GURL icon_url = (*icon_iter)->GetIconURL();
763     if (icon_url.is_empty())
764       continue;
765     result.push_back(std::make_pair((*icon_iter)->icon_side_length(),
766                                     icon_url));
767   }
768
769   // Return a sorted list, smallest to largest.
770   std::sort(result.begin(), result.end(), SortBySize);
771   return result;
772 }
773
774 GURL InstalledApp::GetProductUrl() const {
775   for (ScopedVector<Link>::const_iterator it = links_.begin();
776        it != links_.end(); ++it) {
777     const Link* link = *it;
778     if (link->type() == Link::LINK_PRODUCT)
779       return link->href();
780   }
781   return GURL();
782 }
783
784 // static
785 bool InstalledApp::GetValueString(const base::Value* value,
786                                   std::string* result) {
787   const base::DictionaryValue* dict = NULL;
788   if (!value->GetAsDictionary(&dict))
789     return false;
790
791   if (!dict->GetString(kTField, result))
792     return false;
793
794   return true;
795 }
796
797 // static
798 void InstalledApp::RegisterJSONConverter(
799     base::JSONValueConverter<InstalledApp>* converter) {
800   converter->RegisterRepeatedMessage(kInstalledAppIconField,
801                                      &InstalledApp::app_icons_);
802   converter->RegisterStringField(kInstalledAppIdField,
803                                  &InstalledApp::app_id_);
804   converter->RegisterStringField(kInstalledAppNameField,
805                                  &InstalledApp::app_name_);
806   converter->RegisterStringField(kInstalledAppObjectTypeField,
807                                  &InstalledApp::object_type_);
808   converter->RegisterCustomField<bool>(kInstalledAppSupportsCreateField,
809                                        &InstalledApp::supports_create_,
810                                        &GetBoolFromString);
811   converter->RegisterRepeatedCustomValue(kInstalledAppPrimaryMimeTypeField,
812                                          &InstalledApp::primary_mimetypes_,
813                                          &GetValueString);
814   converter->RegisterRepeatedCustomValue(kInstalledAppSecondaryMimeTypeField,
815                                          &InstalledApp::secondary_mimetypes_,
816                                          &GetValueString);
817   converter->RegisterRepeatedCustomValue(kInstalledAppPrimaryFileExtensionField,
818                                          &InstalledApp::primary_extensions_,
819                                          &GetValueString);
820   converter->RegisterRepeatedCustomValue(
821       kInstalledAppSecondaryFileExtensionField,
822       &InstalledApp::secondary_extensions_,
823       &GetValueString);
824   converter->RegisterRepeatedMessage(kLinkField, &InstalledApp::links_);
825 }
826
827 ////////////////////////////////////////////////////////////////////////////////
828 // AccountMetadata implementation
829
830 AccountMetadata::AccountMetadata()
831     : quota_bytes_total_(0),
832       quota_bytes_used_(0),
833       largest_changestamp_(0) {
834 }
835
836 AccountMetadata::~AccountMetadata() {
837 }
838
839 // static
840 void AccountMetadata::RegisterJSONConverter(
841     base::JSONValueConverter<AccountMetadata>* converter) {
842   converter->RegisterCustomField<int64>(
843       kQuotaBytesTotalField,
844       &AccountMetadata::quota_bytes_total_,
845       &base::StringToInt64);
846   converter->RegisterCustomField<int64>(
847       kQuotaBytesUsedField,
848       &AccountMetadata::quota_bytes_used_,
849       &base::StringToInt64);
850   converter->RegisterCustomField<int64>(
851       kLargestChangestampField,
852       &AccountMetadata::largest_changestamp_,
853       &base::StringToInt64);
854   converter->RegisterRepeatedMessage(kInstalledAppField,
855                                      &AccountMetadata::installed_apps_);
856 }
857
858 // static
859 scoped_ptr<AccountMetadata> AccountMetadata::CreateFrom(
860     const base::Value& value) {
861   scoped_ptr<AccountMetadata> metadata(new AccountMetadata());
862   const base::DictionaryValue* dictionary = NULL;
863   const base::Value* entry = NULL;
864   if (!value.GetAsDictionary(&dictionary) ||
865       !dictionary->Get(kEntryField, &entry) ||
866       !metadata->Parse(*entry)) {
867     LOG(ERROR) << "Unable to create: Invalid account metadata feed!";
868     return scoped_ptr<AccountMetadata>();
869   }
870
871   return metadata.Pass();
872 }
873
874 bool AccountMetadata::Parse(const base::Value& value) {
875   base::JSONValueConverter<AccountMetadata> converter;
876   if (!converter.Convert(value, this)) {
877     LOG(ERROR) << "Unable to parse: Invalid account metadata feed!";
878     return false;
879   }
880   return true;
881 }
882
883 }  // namespace google_apis