• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "google_apis/drive/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 "google_apis/drive/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 kItemsPerPageField[] = "openSearch$itemsPerPage.$t";
50 const char kLabelField[] = "label";
51 const char kLargestChangestampField[] = "docs$largestChangestamp.value";
52 const char kLastViewedField[] = "gd$lastViewed.$t";
53 const char kLinkField[] = "link";
54 const char kMD5Field[] = "docs$md5Checksum.$t";
55 const char kNameField[] = "name.$t";
56 const char kPublishedField[] = "published.$t";
57 const char kRelField[] = "rel";
58 const char kRemovedField[] = "docs$removed";
59 const char kResourceIdField[] = "gd$resourceId.$t";
60 const char kSchemeField[] = "scheme";
61 const char kSizeField[] = "docs$size.$t";
62 const char kSrcField[] = "src";
63 const char kStartIndexField[] = "openSearch$startIndex.$t";
64 const char kSuggestedFileNameField[] = "docs$suggestedFilename.$t";
65 const char kTermField[] = "term";
66 const char kTitleField[] = "title";
67 const char kTitleTField[] = "title.$t";
68 const char kTypeField[] = "type";
69 const char kUpdatedField[] = "updated.$t";
70 
71 // Link Prefixes
72 const char kOpenWithPrefix[] = "http://schemas.google.com/docs/2007#open-with-";
73 const size_t kOpenWithPrefixSize = arraysize(kOpenWithPrefix) - 1;
74 
75 struct EntryKindMap {
76   DriveEntryKind kind;
77   const char* entry;
78   const char* extension;
79 };
80 
81 const EntryKindMap kEntryKindMap[] = {
82     { ENTRY_KIND_UNKNOWN,      "unknown",      NULL},
83     { ENTRY_KIND_ITEM,         "item",         NULL},
84     { ENTRY_KIND_DOCUMENT,     "document",     ".gdoc"},
85     { ENTRY_KIND_SPREADSHEET,  "spreadsheet",  ".gsheet"},
86     { ENTRY_KIND_PRESENTATION, "presentation", ".gslides" },
87     { ENTRY_KIND_DRAWING,      "drawing",      ".gdraw"},
88     { ENTRY_KIND_TABLE,        "table",        ".gtable"},
89     { ENTRY_KIND_FORM,         "form",         ".gform"},
90     { ENTRY_KIND_EXTERNAL_APP, "externalapp",  ".glink"},
91     { ENTRY_KIND_SITE,         "site",         NULL},
92     { ENTRY_KIND_FOLDER,       "folder",       NULL},
93     { ENTRY_KIND_FILE,         "file",         NULL},
94     { ENTRY_KIND_PDF,          "pdf",          NULL},
95 };
96 COMPILE_ASSERT(arraysize(kEntryKindMap) == ENTRY_KIND_MAX_VALUE,
97                EntryKindMap_and_DriveEntryKind_are_not_in_sync);
98 
99 struct LinkTypeMap {
100   Link::LinkType type;
101   const char* rel;
102 };
103 
104 const LinkTypeMap kLinkTypeMap[] = {
105     { Link::LINK_SELF,
106       "self" },
107     { Link::LINK_NEXT,
108       "next" },
109     { Link::LINK_PARENT,
110       "http://schemas.google.com/docs/2007#parent" },
111     { Link::LINK_ALTERNATE,
112       "alternate"},
113     { Link::LINK_EDIT,
114       "edit" },
115     { Link::LINK_EDIT_MEDIA,
116       "edit-media" },
117     { Link::LINK_ALT_EDIT_MEDIA,
118       "http://schemas.google.com/docs/2007#alt-edit-media" },
119     { Link::LINK_ALT_POST,
120       "http://schemas.google.com/docs/2007#alt-post" },
121     { Link::LINK_FEED,
122       "http://schemas.google.com/g/2005#feed"},
123     { Link::LINK_POST,
124       "http://schemas.google.com/g/2005#post"},
125     { Link::LINK_BATCH,
126       "http://schemas.google.com/g/2005#batch"},
127     { Link::LINK_THUMBNAIL,
128       "http://schemas.google.com/docs/2007/thumbnail"},
129     { Link::LINK_RESUMABLE_EDIT_MEDIA,
130       "http://schemas.google.com/g/2005#resumable-edit-media"},
131     { Link::LINK_RESUMABLE_CREATE_MEDIA,
132       "http://schemas.google.com/g/2005#resumable-create-media"},
133     { Link::LINK_TABLES_FEED,
134       "http://schemas.google.com/spreadsheets/2006#tablesfeed"},
135     { Link::LINK_WORKSHEET_FEED,
136       "http://schemas.google.com/spreadsheets/2006#worksheetsfeed"},
137     { Link::LINK_EMBED,
138       "http://schemas.google.com/docs/2007#embed"},
139     { Link::LINK_PRODUCT,
140       "http://schemas.google.com/docs/2007#product"},
141     { Link::LINK_ICON,
142       "http://schemas.google.com/docs/2007#icon"},
143     { Link::LINK_SHARE,
144       "http://schemas.google.com/docs/2007#share"},
145 };
146 
147 struct ResourceLinkTypeMap {
148   ResourceLink::ResourceLinkType type;
149   const char* rel;
150 };
151 
152 const ResourceLinkTypeMap kFeedLinkTypeMap[] = {
153     { ResourceLink::FEED_LINK_ACL,
154       "http://schemas.google.com/acl/2007#accessControlList" },
155     { ResourceLink::FEED_LINK_REVISIONS,
156       "http://schemas.google.com/docs/2007/revisions" },
157 };
158 
159 struct CategoryTypeMap {
160   Category::CategoryType type;
161   const char* scheme;
162 };
163 
164 const CategoryTypeMap kCategoryTypeMap[] = {
165     { Category::CATEGORY_KIND, "http://schemas.google.com/g/2005#kind" },
166     { Category::CATEGORY_LABEL, "http://schemas.google.com/g/2005/labels" },
167 };
168 
169 // Converts |url_string| to |result|.  Always returns true to be used
170 // for JSONValueConverter::RegisterCustomField method.
171 // TODO(mukai): make it return false in case of invalid |url_string|.
GetGURLFromString(const base::StringPiece & url_string,GURL * result)172 bool GetGURLFromString(const base::StringPiece& url_string, GURL* result) {
173   *result = GURL(url_string.as_string());
174   return true;
175 }
176 
177 }  // namespace
178 
179 ////////////////////////////////////////////////////////////////////////////////
180 // Author implementation
181 
Author()182 Author::Author() {
183 }
184 
185 // static
RegisterJSONConverter(base::JSONValueConverter<Author> * converter)186 void Author::RegisterJSONConverter(
187     base::JSONValueConverter<Author>* converter) {
188   converter->RegisterStringField(kNameField, &Author::name_);
189   converter->RegisterStringField(kEmailField, &Author::email_);
190 }
191 
192 ////////////////////////////////////////////////////////////////////////////////
193 // Link implementation
194 
Link()195 Link::Link() : type_(Link::LINK_UNKNOWN) {
196 }
197 
~Link()198 Link::~Link() {
199 }
200 
201 // static
GetAppID(const base::StringPiece & rel,std::string * app_id)202 bool Link::GetAppID(const base::StringPiece& rel, std::string* app_id) {
203   DCHECK(app_id);
204   // Fast return path if the link clearly isn't an OPEN_WITH link.
205   if (rel.size() < kOpenWithPrefixSize) {
206     app_id->clear();
207     return true;
208   }
209 
210   const std::string kOpenWithPrefixStr(kOpenWithPrefix);
211   if (StartsWithASCII(rel.as_string(), kOpenWithPrefixStr, false)) {
212     *app_id = rel.as_string().substr(kOpenWithPrefixStr.size());
213     return true;
214   }
215 
216   app_id->clear();
217   return true;
218 }
219 
220 // static.
GetLinkType(const base::StringPiece & rel,Link::LinkType * type)221 bool Link::GetLinkType(const base::StringPiece& rel, Link::LinkType* type) {
222   DCHECK(type);
223   for (size_t i = 0; i < arraysize(kLinkTypeMap); i++) {
224     if (rel == kLinkTypeMap[i].rel) {
225       *type = kLinkTypeMap[i].type;
226       return true;
227     }
228   }
229 
230   // OPEN_WITH links have extra information at the end of the rel that is unique
231   // for each one, so we can't just check the usual map. This check is slightly
232   // redundant to provide a quick skip if it's obviously not an OPEN_WITH url.
233   if (rel.size() >= kOpenWithPrefixSize &&
234       StartsWithASCII(rel.as_string(), kOpenWithPrefix, false)) {
235     *type = LINK_OPEN_WITH;
236     return true;
237   }
238 
239   // Let unknown link types through, just report it; if the link type is needed
240   // in the future, add it into LinkType and kLinkTypeMap.
241   DVLOG(1) << "Ignoring unknown link type for rel " << rel;
242   *type = LINK_UNKNOWN;
243   return true;
244 }
245 
246 // static
RegisterJSONConverter(base::JSONValueConverter<Link> * converter)247 void Link::RegisterJSONConverter(base::JSONValueConverter<Link>* converter) {
248   converter->RegisterCustomField<Link::LinkType>(kRelField,
249                                                  &Link::type_,
250                                                  &Link::GetLinkType);
251   // We have to register kRelField twice because we extract two different pieces
252   // of data from the same rel field.
253   converter->RegisterCustomField<std::string>(kRelField,
254                                               &Link::app_id_,
255                                               &Link::GetAppID);
256   converter->RegisterCustomField(kHrefField, &Link::href_, &GetGURLFromString);
257   converter->RegisterStringField(kTitleField, &Link::title_);
258   converter->RegisterStringField(kTypeField, &Link::mime_type_);
259 }
260 
261 ////////////////////////////////////////////////////////////////////////////////
262 // ResourceLink implementation
263 
ResourceLink()264 ResourceLink::ResourceLink() : type_(ResourceLink::FEED_LINK_UNKNOWN) {
265 }
266 
267 // static.
GetFeedLinkType(const base::StringPiece & rel,ResourceLink::ResourceLinkType * result)268 bool ResourceLink::GetFeedLinkType(
269     const base::StringPiece& rel, ResourceLink::ResourceLinkType* result) {
270   for (size_t i = 0; i < arraysize(kFeedLinkTypeMap); i++) {
271     if (rel == kFeedLinkTypeMap[i].rel) {
272       *result = kFeedLinkTypeMap[i].type;
273       return true;
274     }
275   }
276   DVLOG(1) << "Unknown feed link type for rel " << rel;
277   return false;
278 }
279 
280 // static
RegisterJSONConverter(base::JSONValueConverter<ResourceLink> * converter)281 void ResourceLink::RegisterJSONConverter(
282     base::JSONValueConverter<ResourceLink>* converter) {
283   converter->RegisterCustomField<ResourceLink::ResourceLinkType>(
284       kRelField, &ResourceLink::type_, &ResourceLink::GetFeedLinkType);
285   converter->RegisterCustomField(
286       kHrefField, &ResourceLink::href_, &GetGURLFromString);
287 }
288 
289 ////////////////////////////////////////////////////////////////////////////////
290 // Category implementation
291 
Category()292 Category::Category() : type_(CATEGORY_UNKNOWN) {
293 }
294 
295 // Converts category.scheme into CategoryType enum.
GetCategoryTypeFromScheme(const base::StringPiece & scheme,Category::CategoryType * result)296 bool Category::GetCategoryTypeFromScheme(
297     const base::StringPiece& scheme, Category::CategoryType* result) {
298   for (size_t i = 0; i < arraysize(kCategoryTypeMap); i++) {
299     if (scheme == kCategoryTypeMap[i].scheme) {
300       *result = kCategoryTypeMap[i].type;
301       return true;
302     }
303   }
304   DVLOG(1) << "Unknown feed link type for scheme " << scheme;
305   return false;
306 }
307 
308 // static
RegisterJSONConverter(base::JSONValueConverter<Category> * converter)309 void Category::RegisterJSONConverter(
310     base::JSONValueConverter<Category>* converter) {
311   converter->RegisterStringField(kLabelField, &Category::label_);
312   converter->RegisterCustomField<Category::CategoryType>(
313       kSchemeField, &Category::type_, &Category::GetCategoryTypeFromScheme);
314   converter->RegisterStringField(kTermField, &Category::term_);
315 }
316 
GetLinkByType(Link::LinkType type) const317 const Link* CommonMetadata::GetLinkByType(Link::LinkType type) const {
318   for (size_t i = 0; i < links_.size(); ++i) {
319     if (links_[i]->type() == type)
320       return links_[i];
321   }
322   return NULL;
323 }
324 
325 ////////////////////////////////////////////////////////////////////////////////
326 // Content implementation
327 
Content()328 Content::Content() {
329 }
330 
331 // static
RegisterJSONConverter(base::JSONValueConverter<Content> * converter)332 void Content::RegisterJSONConverter(
333     base::JSONValueConverter<Content>* converter) {
334   converter->RegisterCustomField(kSrcField, &Content::url_, &GetGURLFromString);
335   converter->RegisterStringField(kTypeField, &Content::mime_type_);
336 }
337 
338 ////////////////////////////////////////////////////////////////////////////////
339 // CommonMetadata implementation
340 
CommonMetadata()341 CommonMetadata::CommonMetadata() {
342 }
343 
~CommonMetadata()344 CommonMetadata::~CommonMetadata() {
345 }
346 
347 // static
348 template<typename CommonMetadataDescendant>
RegisterJSONConverter(base::JSONValueConverter<CommonMetadataDescendant> * converter)349 void CommonMetadata::RegisterJSONConverter(
350     base::JSONValueConverter<CommonMetadataDescendant>* converter) {
351   converter->RegisterStringField(kETagField, &CommonMetadata::etag_);
352   converter->template RegisterRepeatedMessage<Author>(
353       kAuthorField, &CommonMetadata::authors_);
354   converter->template RegisterRepeatedMessage<Link>(
355       kLinkField, &CommonMetadata::links_);
356   converter->template RegisterRepeatedMessage<Category>(
357       kCategoryField, &CommonMetadata::categories_);
358   converter->template RegisterCustomField<base::Time>(
359       kUpdatedField, &CommonMetadata::updated_time_, &util::GetTimeFromString);
360 }
361 
362 ////////////////////////////////////////////////////////////////////////////////
363 // ResourceEntry implementation
364 
ResourceEntry()365 ResourceEntry::ResourceEntry()
366     : kind_(ENTRY_KIND_UNKNOWN),
367       file_size_(0),
368       deleted_(false),
369       removed_(false),
370       changestamp_(0),
371       image_width_(-1),
372       image_height_(-1),
373       image_rotation_(-1) {
374 }
375 
~ResourceEntry()376 ResourceEntry::~ResourceEntry() {
377 }
378 
HasFieldPresent(const base::Value * value,bool * result)379 bool ResourceEntry::HasFieldPresent(const base::Value* value,
380                                     bool* result) {
381   *result = (value != NULL);
382   return true;
383 }
384 
ParseChangestamp(const base::Value * value,int64 * result)385 bool ResourceEntry::ParseChangestamp(const base::Value* value,
386                                      int64* result) {
387   DCHECK(result);
388   if (!value) {
389     *result = 0;
390     return true;
391   }
392 
393   std::string string_value;
394   if (value->GetAsString(&string_value) &&
395       base::StringToInt64(string_value, result))
396     return true;
397 
398   return false;
399 }
400 
401 // static
RegisterJSONConverter(base::JSONValueConverter<ResourceEntry> * converter)402 void ResourceEntry::RegisterJSONConverter(
403     base::JSONValueConverter<ResourceEntry>* converter) {
404   // Inherit the parent registrations.
405   CommonMetadata::RegisterJSONConverter(converter);
406   converter->RegisterStringField(
407       kResourceIdField, &ResourceEntry::resource_id_);
408   converter->RegisterStringField(kIDField, &ResourceEntry::id_);
409   converter->RegisterStringField(kTitleTField, &ResourceEntry::title_);
410   converter->RegisterCustomField<base::Time>(
411       kPublishedField, &ResourceEntry::published_time_,
412       &util::GetTimeFromString);
413   converter->RegisterCustomField<base::Time>(
414       kLastViewedField, &ResourceEntry::last_viewed_time_,
415       &util::GetTimeFromString);
416   converter->RegisterRepeatedMessage(
417       kFeedLinkField, &ResourceEntry::resource_links_);
418   converter->RegisterNestedField(kContentField, &ResourceEntry::content_);
419 
420   // File properties.  If the resource type is not a normal file, then
421   // that's no problem because those feed must not have these fields
422   // themselves, which does not report errors.
423   converter->RegisterStringField(kFileNameField, &ResourceEntry::filename_);
424   converter->RegisterStringField(kMD5Field, &ResourceEntry::file_md5_);
425   converter->RegisterCustomField<int64>(
426       kSizeField, &ResourceEntry::file_size_, &base::StringToInt64);
427   converter->RegisterStringField(
428       kSuggestedFileNameField, &ResourceEntry::suggested_filename_);
429   // Deleted are treated as 'trashed' items on web client side. Removed files
430   // are gone for good. We treat both cases as 'deleted' for this client.
431   converter->RegisterCustomValueField<bool>(
432       kDeletedField, &ResourceEntry::deleted_, &ResourceEntry::HasFieldPresent);
433   converter->RegisterCustomValueField<bool>(
434       kRemovedField, &ResourceEntry::removed_, &ResourceEntry::HasFieldPresent);
435   converter->RegisterCustomValueField<int64>(
436       kChangestampField, &ResourceEntry::changestamp_,
437       &ResourceEntry::ParseChangestamp);
438   // ImageMediaMetadata fields are not supported by WAPI.
439 }
440 
441 // static
GetHostedDocumentExtension(DriveEntryKind kind)442 std::string ResourceEntry::GetHostedDocumentExtension(DriveEntryKind kind) {
443   for (size_t i = 0; i < arraysize(kEntryKindMap); i++) {
444     if (kEntryKindMap[i].kind == kind) {
445       if (kEntryKindMap[i].extension)
446         return std::string(kEntryKindMap[i].extension);
447       else
448         return std::string();
449     }
450   }
451   return std::string();
452 }
453 
454 // static
GetEntryKindFromExtension(const std::string & extension)455 DriveEntryKind ResourceEntry::GetEntryKindFromExtension(
456     const std::string& extension) {
457   for (size_t i = 0; i < arraysize(kEntryKindMap); ++i) {
458     const char* document_extension = kEntryKindMap[i].extension;
459     if (document_extension && extension == document_extension)
460       return kEntryKindMap[i].kind;
461   }
462   return ENTRY_KIND_UNKNOWN;
463 }
464 
465 // static
ClassifyEntryKindByFileExtension(const base::FilePath & file_path)466 int ResourceEntry::ClassifyEntryKindByFileExtension(
467     const base::FilePath& file_path) {
468 #if defined(OS_WIN)
469   std::string file_extension = base::WideToUTF8(file_path.Extension());
470 #else
471   std::string file_extension = file_path.Extension();
472 #endif
473   return ClassifyEntryKind(GetEntryKindFromExtension(file_extension));
474 }
475 
476 // static
GetEntryKindFromTerm(const std::string & term)477 DriveEntryKind ResourceEntry::GetEntryKindFromTerm(
478     const std::string& term) {
479   if (!StartsWithASCII(term, kTermPrefix, false)) {
480     DVLOG(1) << "Unexpected term prefix term " << term;
481     return ENTRY_KIND_UNKNOWN;
482   }
483 
484   std::string type = term.substr(strlen(kTermPrefix));
485   for (size_t i = 0; i < arraysize(kEntryKindMap); i++) {
486     if (type == kEntryKindMap[i].entry)
487       return kEntryKindMap[i].kind;
488   }
489   DVLOG(1) << "Unknown entry type for term " << term << ", type " << type;
490   return ENTRY_KIND_UNKNOWN;
491 }
492 
493 // static
ClassifyEntryKind(DriveEntryKind kind)494 int ResourceEntry::ClassifyEntryKind(DriveEntryKind kind) {
495   int classes = 0;
496 
497   // All DriveEntryKind members are listed here, so the compiler catches if a
498   // newly added member is missing here.
499   switch (kind) {
500     case ENTRY_KIND_UNKNOWN:
501     // Special entries.
502     case ENTRY_KIND_ITEM:
503     case ENTRY_KIND_SITE:
504       break;
505 
506     // Hosted Google document.
507     case ENTRY_KIND_DOCUMENT:
508     case ENTRY_KIND_SPREADSHEET:
509     case ENTRY_KIND_PRESENTATION:
510     case ENTRY_KIND_DRAWING:
511     case ENTRY_KIND_TABLE:
512     case ENTRY_KIND_FORM:
513       classes = KIND_OF_GOOGLE_DOCUMENT | KIND_OF_HOSTED_DOCUMENT;
514       break;
515 
516     // Hosted external application document.
517     case ENTRY_KIND_EXTERNAL_APP:
518       classes = KIND_OF_EXTERNAL_DOCUMENT | KIND_OF_HOSTED_DOCUMENT;
519       break;
520 
521     // Folders, collections.
522     case ENTRY_KIND_FOLDER:
523       classes = KIND_OF_FOLDER;
524       break;
525 
526     // Regular files.
527     case ENTRY_KIND_FILE:
528     case ENTRY_KIND_PDF:
529       classes = KIND_OF_FILE;
530       break;
531 
532     case ENTRY_KIND_MAX_VALUE:
533       NOTREACHED();
534   }
535 
536   return classes;
537 }
538 
FillRemainingFields()539 void ResourceEntry::FillRemainingFields() {
540   // Set |kind_| and |labels_| based on the |categories_| in the class.
541   // JSONValueConverter does not have the ability to catch an element in a list
542   // based on a predicate.  Thus we need to iterate over |categories_| and
543   // find the elements to set these fields as a post-process.
544   for (size_t i = 0; i < categories_.size(); ++i) {
545     const Category* category = categories_[i];
546     if (category->type() == Category::CATEGORY_KIND)
547       kind_ = GetEntryKindFromTerm(category->term());
548     else if (category->type() == Category::CATEGORY_LABEL)
549       labels_.push_back(category->label());
550   }
551 }
552 
553 // static
ExtractAndParse(const base::Value & value)554 scoped_ptr<ResourceEntry> ResourceEntry::ExtractAndParse(
555     const base::Value& value) {
556   const base::DictionaryValue* as_dict = NULL;
557   const base::DictionaryValue* entry_dict = NULL;
558   if (value.GetAsDictionary(&as_dict) &&
559       as_dict->GetDictionary(kEntryField, &entry_dict)) {
560     return ResourceEntry::CreateFrom(*entry_dict);
561   }
562   return scoped_ptr<ResourceEntry>();
563 }
564 
565 // static
CreateFrom(const base::Value & value)566 scoped_ptr<ResourceEntry> ResourceEntry::CreateFrom(const base::Value& value) {
567   base::JSONValueConverter<ResourceEntry> converter;
568   scoped_ptr<ResourceEntry> entry(new ResourceEntry());
569   if (!converter.Convert(value, entry.get())) {
570     DVLOG(1) << "Invalid resource entry!";
571     return scoped_ptr<ResourceEntry>();
572   }
573 
574   entry->FillRemainingFields();
575   return entry.Pass();
576 }
577 
578 // static
GetEntryNodeName()579 std::string ResourceEntry::GetEntryNodeName() {
580   return kEntryNode;
581 }
582 
583 ////////////////////////////////////////////////////////////////////////////////
584 // ResourceList implementation
585 
ResourceList()586 ResourceList::ResourceList()
587     : start_index_(0),
588       items_per_page_(0),
589       largest_changestamp_(0) {
590 }
591 
~ResourceList()592 ResourceList::~ResourceList() {
593 }
594 
595 // static
RegisterJSONConverter(base::JSONValueConverter<ResourceList> * converter)596 void ResourceList::RegisterJSONConverter(
597     base::JSONValueConverter<ResourceList>* converter) {
598   // inheritance
599   CommonMetadata::RegisterJSONConverter(converter);
600   // TODO(zelidrag): Once we figure out where these will be used, we should
601   // check for valid start_index_ and items_per_page_ values.
602   converter->RegisterCustomField<int>(
603       kStartIndexField, &ResourceList::start_index_, &base::StringToInt);
604   converter->RegisterCustomField<int>(
605       kItemsPerPageField, &ResourceList::items_per_page_, &base::StringToInt);
606   converter->RegisterStringField(kTitleTField, &ResourceList::title_);
607   converter->RegisterRepeatedMessage(kEntryField, &ResourceList::entries_);
608   converter->RegisterCustomField<int64>(
609      kLargestChangestampField, &ResourceList::largest_changestamp_,
610      &base::StringToInt64);
611 }
612 
Parse(const base::Value & value)613 bool ResourceList::Parse(const base::Value& value) {
614   base::JSONValueConverter<ResourceList> converter;
615   if (!converter.Convert(value, this)) {
616     DVLOG(1) << "Invalid resource list!";
617     return false;
618   }
619 
620   ScopedVector<ResourceEntry>::iterator iter = entries_.begin();
621   while (iter != entries_.end()) {
622     ResourceEntry* entry = (*iter);
623     entry->FillRemainingFields();
624     ++iter;
625   }
626   return true;
627 }
628 
629 // static
ExtractAndParse(const base::Value & value)630 scoped_ptr<ResourceList> ResourceList::ExtractAndParse(
631     const base::Value& value) {
632   const base::DictionaryValue* as_dict = NULL;
633   const base::DictionaryValue* feed_dict = NULL;
634   if (value.GetAsDictionary(&as_dict) &&
635       as_dict->GetDictionary(kFeedField, &feed_dict)) {
636     return ResourceList::CreateFrom(*feed_dict);
637   }
638   return scoped_ptr<ResourceList>();
639 }
640 
641 // static
CreateFrom(const base::Value & value)642 scoped_ptr<ResourceList> ResourceList::CreateFrom(const base::Value& value) {
643   scoped_ptr<ResourceList> feed(new ResourceList());
644   if (!feed->Parse(value)) {
645     DVLOG(1) << "Invalid resource list!";
646     return scoped_ptr<ResourceList>();
647   }
648 
649   return feed.Pass();
650 }
651 
GetNextFeedURL(GURL * url) const652 bool ResourceList::GetNextFeedURL(GURL* url) const {
653   DCHECK(url);
654   for (size_t i = 0; i < links_.size(); ++i) {
655     if (links_[i]->type() == Link::LINK_NEXT) {
656       *url = links_[i]->href();
657       return true;
658     }
659   }
660   return false;
661 }
662 
ReleaseEntries(std::vector<ResourceEntry * > * entries)663 void ResourceList::ReleaseEntries(std::vector<ResourceEntry*>* entries) {
664   entries_.release(entries);
665 }
666 
667 }  // namespace google_apis
668