Home | History | Annotate | Download | only in drive
      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|.
    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 
    182 Author::Author() {
    183 }
    184 
    185 // static
    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 
    195 Link::Link() : type_(Link::LINK_UNKNOWN) {
    196 }
    197 
    198 Link::~Link() {
    199 }
    200 
    201 // static
    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.
    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
    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 
    264 ResourceLink::ResourceLink() : type_(ResourceLink::FEED_LINK_UNKNOWN) {
    265 }
    266 
    267 // static.
    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
    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 
    292 Category::Category() : type_(CATEGORY_UNKNOWN) {
    293 }
    294 
    295 // Converts category.scheme into CategoryType enum.
    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
    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 
    317 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 
    328 Content::Content() {
    329 }
    330 
    331 // static
    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 
    341 CommonMetadata::CommonMetadata() {
    342 }
    343 
    344 CommonMetadata::~CommonMetadata() {
    345 }
    346 
    347 // static
    348 template<typename CommonMetadataDescendant>
    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 
    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 
    376 ResourceEntry::~ResourceEntry() {
    377 }
    378 
    379 bool ResourceEntry::HasFieldPresent(const base::Value* value,
    380                                     bool* result) {
    381   *result = (value != NULL);
    382   return true;
    383 }
    384 
    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
    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
    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
    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
    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
    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
    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 
    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
    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
    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
    579 std::string ResourceEntry::GetEntryNodeName() {
    580   return kEntryNode;
    581 }
    582 
    583 ////////////////////////////////////////////////////////////////////////////////
    584 // ResourceList implementation
    585 
    586 ResourceList::ResourceList()
    587     : start_index_(0),
    588       items_per_page_(0),
    589       largest_changestamp_(0) {
    590 }
    591 
    592 ResourceList::~ResourceList() {
    593 }
    594 
    595 // static
    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 
    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
    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
    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 
    652 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 
    663 void ResourceList::ReleaseEntries(std::vector<ResourceEntry*>* entries) {
    664   entries_.release(entries);
    665 }
    666 
    667 }  // namespace google_apis
    668