text: // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "google_apis/drive/gdata_wapi_parser.h"
  
  #include <algorithm>
  #include <string>
  
  #include "base/basictypes.h"
  #include "base/files/file_path.h"
  #include "base/json/json_value_converter.h"
  #include "base/memory/scoped_ptr.h"
  #include "base/strings/string_number_conversions.h"
  #include "base/strings/string_piece.h"
  #include "base/strings/string_util.h"
  #include "base/strings/utf_string_conversions.h"
  #include "base/values.h"
  #include "google_apis/drive/time_util.h"
  
  using base::Value;
  using base::DictionaryValue;
  using base::ListValue;
  
  namespace google_apis {
  
  namespace {
  
  // Term values for kSchemeKind category:
  const char kTermPrefix[] = "http://schemas.google.com/docs/2007#";
  
  // Node names.
  const char kEntryNode[] = "entry";
  
  // Field names.
  const char kAuthorField[] = "author";
  const char kCategoryField[] = "category";
  const char kChangestampField[] = "docs$changestamp.value";
  const char kContentField[] = "content";
  const char kDeletedField[] = "gd$deleted";
  const char kETagField[] = "gd$etag";
  const char kEmailField[] = "email.$t";
  const char kEntryField[] = "entry";
  const char kFeedField[] = "feed";
  const char kFeedLinkField[] = "gd$feedLink";
  const char kFileNameField[] = "docs$filename.$t";
  const char kHrefField[] = "href";
  const char kIDField[] = "id.$t";
  const char kInstalledAppField[] = "docs$installedApp";
  const char kInstalledAppNameField[] = "docs$installedAppName";
  const char kInstalledAppIdField[] = "docs$installedAppId";
  const char kInstalledAppIconField[] = "docs$installedAppIcon";
  const char kInstalledAppIconCategoryField[] = "docs$installedAppIconCategory";
  const char kInstalledAppIconSizeField[] = "docs$installedAppIconSize";
  const char kInstalledAppObjectTypeField[] = "docs$installedAppObjectType";
  const char kInstalledAppPrimaryFileExtensionField[] =
      "docs$installedAppPrimaryFileExtension";
  const char kInstalledAppPrimaryMimeTypeField[] =
      "docs$installedAppPrimaryMimeType";
  const char kInstalledAppSecondaryFileExtensionField[] =
      "docs$installedAppSecondaryFileExtension";
  const char kInstalledAppSecondaryMimeTypeField[] =
      "docs$installedAppSecondaryMimeType";
  const char kInstalledAppSupportsCreateField[] =
      "docs$installedAppSupportsCreate";
  const char kItemsPerPageField[] = "openSearch$itemsPerPage.$t";
  const char kLabelField[] = "label";
  const char kLargestChangestampField[] = "docs$largestChangestamp.value";
  const char kLastViewedField[] = "gd$lastViewed.$t";
  const char kLinkField[] = "link";
  const char kMD5Field[] = "docs$md5Checksum.$t";
  const char kNameField[] = "name.$t";
  const char kPublishedField[] = "published.$t";
  const char kQuotaBytesTotalField[] = "gd$quotaBytesTotal.$t";
  const char kQuotaBytesUsedField[] = "gd$quotaBytesUsed.$t";
  const char kRelField[] = "rel";
  const char kRemovedField[] = "docs$removed";
  const char kResourceIdField[] = "gd$resourceId.$t";
  const char kSchemeField[] = "scheme";
  const char kSizeField[] = "docs$size.$t";
  const char kSrcField[] = "src";
  const char kStartIndexField[] = "openSearch$startIndex.$t";
  const char kSuggestedFileNameField[] = "docs$suggestedFilename.$t";
  const char kTField[] = "$t";
  const char kTermField[] = "term";
  const char kTitleField[] = "title";
  const char kTitleTField[] = "title.$t";
  const char kTypeField[] = "type";
  const char kUpdatedField[] = "updated.$t";
  
  // Link Prefixes
  const char kOpenWithPrefix[] = "http://schemas.google.com/docs/2007#open-with-";
  const size_t kOpenWithPrefixSize = arraysize(kOpenWithPrefix) - 1;
  
  struct EntryKindMap {
    DriveEntryKind kind;
    const char* entry;
    const char* extension;
  };
  
  const EntryKindMap kEntryKindMap[] = {
      { ENTRY_KIND_UNKNOWN,      "unknown",      NULL},
      { ENTRY_KIND_ITEM,         "item",         NULL},
      { ENTRY_KIND_DOCUMENT,     "document",     ".gdoc"},
      { ENTRY_KIND_SPREADSHEET,  "spreadsheet",  ".gsheet"},
      { ENTRY_KIND_PRESENTATION, "presentation", ".gslides" },
      { ENTRY_KIND_DRAWING,      "drawing",      ".gdraw"},
      { ENTRY_KIND_TABLE,        "table",        ".gtable"},
      { ENTRY_KIND_FORM,         "form",         ".gform"},
      { ENTRY_KIND_EXTERNAL_APP, "externalapp",  ".glink"},
      { ENTRY_KIND_SITE,         "site",         NULL},
      { ENTRY_KIND_FOLDER,       "folder",       NULL},
      { ENTRY_KIND_FILE,         "file",         NULL},
      { ENTRY_KIND_PDF,          "pdf",          NULL},
  };
  COMPILE_ASSERT(arraysize(kEntryKindMap) == ENTRY_KIND_MAX_VALUE,
                 EntryKindMap_and_DriveEntryKind_are_not_in_sync);
  
  struct LinkTypeMap {
    Link::LinkType type;
    const char* rel;
  };
  
  const LinkTypeMap kLinkTypeMap[] = {
      { Link::LINK_SELF,
        "self" },
      { Link::LINK_NEXT,
        "next" },
      { Link::LINK_PARENT,
        "http://schemas.google.com/docs/2007#parent" },
      { Link::LINK_ALTERNATE,
        "alternate"},
      { Link::LINK_EDIT,
        "edit" },
      { Link::LINK_EDIT_MEDIA,
        "edit-media" },
      { Link::LINK_ALT_EDIT_MEDIA,
        "http://schemas.google.com/docs/2007#alt-edit-media" },
      { Link::LINK_ALT_POST,
        "http://schemas.google.com/docs/2007#alt-post" },
      { Link::LINK_FEED,
        "http://schemas.google.com/g/2005#feed"},
      { Link::LINK_POST,
        "http://schemas.google.com/g/2005#post"},
      { Link::LINK_BATCH,
        "http://schemas.google.com/g/2005#batch"},
      { Link::LINK_THUMBNAIL,
        "http://schemas.google.com/docs/2007/thumbnail"},
      { Link::LINK_RESUMABLE_EDIT_MEDIA,
        "http://schemas.google.com/g/2005#resumable-edit-media"},
      { Link::LINK_RESUMABLE_CREATE_MEDIA,
        "http://schemas.google.com/g/2005#resumable-create-media"},
      { Link::LINK_TABLES_FEED,
        "http://schemas.google.com/spreadsheets/2006#tablesfeed"},
      { Link::LINK_WORKSHEET_FEED,
        "http://schemas.google.com/spreadsheets/2006#worksheetsfeed"},
      { Link::LINK_EMBED,
        "http://schemas.google.com/docs/2007#embed"},
      { Link::LINK_PRODUCT,
        "http://schemas.google.com/docs/2007#product"},
      { Link::LINK_ICON,
        "http://schemas.google.com/docs/2007#icon"},
      { Link::LINK_SHARE,
        "http://schemas.google.com/docs/2007#share"},
  };
  
  struct ResourceLinkTypeMap {
    ResourceLink::ResourceLinkType type;
    const char* rel;
  };
  
  const ResourceLinkTypeMap kFeedLinkTypeMap[] = {
      { ResourceLink::FEED_LINK_ACL,
        "http://schemas.google.com/acl/2007#accessControlList" },
      { ResourceLink::FEED_LINK_REVISIONS,
        "http://schemas.google.com/docs/2007/revisions" },
  };
  
  struct CategoryTypeMap {
    Category::CategoryType type;
    const char* scheme;
  };
  
  const CategoryTypeMap kCategoryTypeMap[] = {
      { Category::CATEGORY_KIND, "http://schemas.google.com/g/2005#kind" },
      { Category::CATEGORY_LABEL, "http://schemas.google.com/g/2005/labels" },
  };
  
  struct AppIconCategoryMap {
    AppIcon::IconCategory category;
    const char* category_name;
  };
  
  const AppIconCategoryMap kAppIconCategoryMap[] = {
      { AppIcon::ICON_DOCUMENT, "document" },
      { AppIcon::ICON_APPLICATION, "application" },
      { AppIcon::ICON_SHARED_DOCUMENT, "documentShared" },
  };
  
  // Converts |url_string| to |result|.  Always returns true to be used
  // for JSONValueConverter::RegisterCustomField method.
  // TODO(mukai): make it return false in case of invalid |url_string|.
  bool GetGURLFromString(const base::StringPiece& url_string, GURL* result) {
    *result = GURL(url_string.as_string());
    return true;
  }
  
  // Converts boolean string values like "true" into bool.
  bool GetBoolFromString(const base::StringPiece& value, bool* result) {
    *result = (value == "true");
    return true;
  }
  
  bool SortBySize(const InstalledApp::IconList::value_type& a,
                  const InstalledApp::IconList::value_type& b) {
    return a.first < b.first;
  }
  
  }  // namespace
  
  ////////////////////////////////////////////////////////////////////////////////
  // Author implementation
  
  Author::Author() {
  }
  
  // static
  void Author::RegisterJSONConverter(
      base::JSONValueConverter<Author>* converter) {
    converter->RegisterStringField(kNameField, &Author::name_);
    converter->RegisterStringField(kEmailField, &Author::email_);
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Link implementation
  
  Link::Link() : type_(Link::LINK_UNKNOWN) {
  }
  
  Link::~Link() {
  }
  
  // static
  bool Link::GetAppID(const base::StringPiece& rel, std::string* app_id) {
    DCHECK(app_id);
    // Fast return path if the link clearly isn't an OPEN_WITH link.
    if (rel.size() < kOpenWithPrefixSize) {
      app_id->clear();
      return true;
    }
  
    const std::string kOpenWithPrefixStr(kOpenWithPrefix);
    if (StartsWithASCII(rel.as_string(), kOpenWithPrefixStr, false)) {
      *app_id = rel.as_string().substr(kOpenWithPrefixStr.size());
      return true;
    }
  
    app_id->clear();
    return true;
  }
  
  // static.
  bool Link::GetLinkType(const base::StringPiece& rel, Link::LinkType* type) {
    DCHECK(type);
    for (size_t i = 0; i < arraysize(kLinkTypeMap); i++) {
      if (rel == kLinkTypeMap[i].rel) {
        *type = kLinkTypeMap[i].type;
        return true;
      }
    }
  
    // OPEN_WITH links have extra information at the end of the rel that is unique
    // for each one, so we can't just check the usual map. This check is slightly
    // redundant to provide a quick skip if it's obviously not an OPEN_WITH url.
    if (rel.size() >= kOpenWithPrefixSize &&
        StartsWithASCII(rel.as_string(), kOpenWithPrefix, false)) {
      *type = LINK_OPEN_WITH;
      return true;
    }
  
    // Let unknown link types through, just report it; if the link type is needed
    // in the future, add it into LinkType and kLinkTypeMap.
    DVLOG(1) << "Ignoring unknown link type for rel " << rel;
    *type = LINK_UNKNOWN;
    return true;
  }
  
  // static
  void Link::RegisterJSONConverter(base::JSONValueConverter<Link>* converter) {
    converter->RegisterCustomField<Link::LinkType>(kRelField,
                                                   &Link::type_,
                                                   &Link::GetLinkType);
    // We have to register kRelField twice because we extract two different pieces
    // of data from the same rel field.
    converter->RegisterCustomField<std::string>(kRelField,
                                                &Link::app_id_,
                                                &Link::GetAppID);
    converter->RegisterCustomField(kHrefField, &Link::href_, &GetGURLFromString);
    converter->RegisterStringField(kTitleField, &Link::title_);
    converter->RegisterStringField(kTypeField, &Link::mime_type_);
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // ResourceLink implementation
  
  ResourceLink::ResourceLink() : type_(ResourceLink::FEED_LINK_UNKNOWN) {
  }
  
  // static.
  bool ResourceLink::GetFeedLinkType(
      const base::StringPiece& rel, ResourceLink::ResourceLinkType* result) {
    for (size_t i = 0; i < arraysize(kFeedLinkTypeMap); i++) {
      if (rel == kFeedLinkTypeMap[i].rel) {
        *result = kFeedLinkTypeMap[i].type;
        return true;
      }
    }
    DVLOG(1) << "Unknown feed link type for rel " << rel;
    return false;
  }
  
  // static
  void ResourceLink::RegisterJSONConverter(
      base::JSONValueConverter<ResourceLink>* converter) {
    converter->RegisterCustomField<ResourceLink::ResourceLinkType>(
        kRelField, &ResourceLink::type_, &ResourceLink::GetFeedLinkType);
    converter->RegisterCustomField(
        kHrefField, &ResourceLink::href_, &GetGURLFromString);
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Category implementation
  
  Category::Category() : type_(CATEGORY_UNKNOWN) {
  }
  
  // Converts category.scheme into CategoryType enum.
  bool Category::GetCategoryTypeFromScheme(
      const base::StringPiece& scheme, Category::CategoryType* result) {
    for (size_t i = 0; i < arraysize(kCategoryTypeMap); i++) {
      if (scheme == kCategoryTypeMap[i].scheme) {
        *result = kCategoryTypeMap[i].type;
        return true;
      }
    }
    DVLOG(1) << "Unknown feed link type for scheme " << scheme;
    return false;
  }
  
  // static
  void Category::RegisterJSONConverter(
      base::JSONValueConverter<Category>* converter) {
    converter->RegisterStringField(kLabelField, &Category::label_);
    converter->RegisterCustomField<Category::CategoryType>(
        kSchemeField, &Category::type_, &Category::GetCategoryTypeFromScheme);
    converter->RegisterStringField(kTermField, &Category::term_);
  }
  
  const Link* CommonMetadata::GetLinkByType(Link::LinkType type) const {
    for (size_t i = 0; i < links_.size(); ++i) {
      if (links_[i]->type() == type)
        return links_[i];
    }
    return NULL;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // Content implementation
  
  Content::Content() {
  }
  
  // static
  void Content::RegisterJSONConverter(
      base::JSONValueConverter<Content>* converter) {
    converter->RegisterCustomField(kSrcField, &Content::url_, &GetGURLFromString);
    converter->RegisterStringField(kTypeField, &Content::mime_type_);
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // AppIcon implementation
  
  AppIcon::AppIcon() : category_(AppIcon::ICON_UNKNOWN), icon_side_length_(0) {
  }
  
  AppIcon::~AppIcon() {
  }
  
  // static
  void AppIcon::RegisterJSONConverter(
      base::JSONValueConverter<AppIcon>* converter) {
    converter->RegisterCustomField<AppIcon::IconCategory>(
        kInstalledAppIconCategoryField,
        &AppIcon::category_,
        &AppIcon::GetIconCategory);
    converter->RegisterCustomField<int>(kInstalledAppIconSizeField,
                                        &AppIcon::icon_side_length_,
                                        base::StringToInt);
    converter->RegisterRepeatedMessage(kLinkField, &AppIcon::links_);
  }
  
  GURL AppIcon::GetIconURL() const {
    for (size_t i = 0; i < links_.size(); ++i) {
      if (links_[i]->type() == Link::LINK_ICON)
        return links_[i]->href();
    }
    return GURL();
  }
  
  // static
  bool AppIcon::GetIconCategory(const base::StringPiece& category,
                                AppIcon::IconCategory* result) {
    for (size_t i = 0; i < arraysize(kAppIconCategoryMap); i++) {
      if (category == kAppIconCategoryMap[i].category_name) {
        *result = kAppIconCategoryMap[i].category;
        return true;
      }
    }
    DVLOG(1) << "Unknown icon category " << category;
    return false;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // CommonMetadata implementation
  
  CommonMetadata::CommonMetadata() {
  }
  
  CommonMetadata::~CommonMetadata() {
  }
  
  // static
  template<typename CommonMetadataDescendant>
  void CommonMetadata::RegisterJSONConverter(
      base::JSONValueConverter<CommonMetadataDescendant>* converter) {
    converter->RegisterStringField(kETagField, &CommonMetadata::etag_);
    converter->template RegisterRepeatedMessage<Author>(
        kAuthorField, &CommonMetadata::authors_);
    converter->template RegisterRepeatedMessage<Link>(
        kLinkField, &CommonMetadata::links_);
    converter->template RegisterRepeatedMessage<Category>(
        kCategoryField, &CommonMetadata::categories_);
    converter->template RegisterCustomField<base::Time>(
        kUpdatedField, &CommonMetadata::updated_time_, &util::GetTimeFromString);
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // ResourceEntry implementation
  
  ResourceEntry::ResourceEntry()
      : kind_(ENTRY_KIND_UNKNOWN),
        file_size_(0),
        deleted_(false),
        removed_(false),
        changestamp_(0),
        image_width_(-1),
        image_height_(-1),
        image_rotation_(-1) {
  }
  
  ResourceEntry::~ResourceEntry() {
  }
  
  bool ResourceEntry::HasFieldPresent(const base::Value* value,
                                      bool* result) {
    *result = (value != NULL);
    return true;
  }
  
  bool ResourceEntry::ParseChangestamp(const base::Value* value,
                                       int64* result) {
    DCHECK(result);
    if (!value) {
      *result = 0;
      return true;
    }
  
    std::string string_value;
    if (value->GetAsString(&string_value) &&
        base::StringToInt64(string_value, result))
      return true;
  
    return false;
  }
  
  // static
  void ResourceEntry::RegisterJSONConverter(
      base::JSONValueConverter<ResourceEntry>* converter) {
    // Inherit the parent registrations.
    CommonMetadata::RegisterJSONConverter(converter);
    converter->RegisterStringField(
        kResourceIdField, &ResourceEntry::resource_id_);
    converter->RegisterStringField(kIDField, &ResourceEntry::id_);
    converter->RegisterStringField(kTitleTField, &ResourceEntry::title_);
    converter->RegisterCustomField<base::Time>(
        kPublishedField, &ResourceEntry::published_time_,
        &util::GetTimeFromString);
    converter->RegisterCustomField<base::Time>(
        kLastViewedField, &ResourceEntry::last_viewed_time_,
        &util::GetTimeFromString);
    converter->RegisterRepeatedMessage(
        kFeedLinkField, &ResourceEntry::resource_links_);
    converter->RegisterNestedField(kContentField, &ResourceEntry::content_);
  
    // File properties.  If the resource type is not a normal file, then
    // that's no problem because those feed must not have these fields
    // themselves, which does not report errors.
    converter->RegisterStringField(kFileNameField, &ResourceEntry::filename_);
    converter->RegisterStringField(kMD5Field, &ResourceEntry::file_md5_);
    converter->RegisterCustomField<int64>(
        kSizeField, &ResourceEntry::file_size_, &base::StringToInt64);
    converter->RegisterStringField(
        kSuggestedFileNameField, &ResourceEntry::suggested_filename_);
    // Deleted are treated as 'trashed' items on web client side. Removed files
    // are gone for good. We treat both cases as 'deleted' for this client.
    converter->RegisterCustomValueField<bool>(
        kDeletedField, &ResourceEntry::deleted_, &ResourceEntry::HasFieldPresent);
    converter->RegisterCustomValueField<bool>(
        kRemovedField, &ResourceEntry::removed_, &ResourceEntry::HasFieldPresent);
    converter->RegisterCustomValueField<int64>(
        kChangestampField, &ResourceEntry::changestamp_,
        &ResourceEntry::ParseChangestamp);
    // ImageMediaMetadata fields are not supported by WAPI.
  }
  
  std::string ResourceEntry::GetHostedDocumentExtension() const {
    for (size_t i = 0; i < arraysize(kEntryKindMap); i++) {
      if (kEntryKindMap[i].kind == kind_) {
        if (kEntryKindMap[i].extension)
          return std::string(kEntryKindMap[i].extension);
        else
          return std::string();
      }
    }
    return std::string();
  }
  
  // static
  int ResourceEntry::ClassifyEntryKindByFileExtension(
      const base::FilePath& file_path) {
  #if defined(OS_WIN)
    std::string file_extension = WideToUTF8(file_path.Extension());
  #else
    std::string file_extension = file_path.Extension();
  #endif
    for (size_t i = 0; i < arraysize(kEntryKindMap); ++i) {
      const char* document_extension = kEntryKindMap[i].extension;
      if (document_extension && file_extension == document_extension)
        return ClassifyEntryKind(kEntryKindMap[i].kind);
    }
    return 0;
  }
  
  // static
  DriveEntryKind ResourceEntry::GetEntryKindFromTerm(
      const std::string& term) {
    if (!StartsWithASCII(term, kTermPrefix, false)) {
      DVLOG(1) << "Unexpected term prefix term " << term;
      return ENTRY_KIND_UNKNOWN;
    }
  
    std::string type = term.substr(strlen(kTermPrefix));
    for (size_t i = 0; i < arraysize(kEntryKindMap); i++) {
      if (type == kEntryKindMap[i].entry)
        return kEntryKindMap[i].kind;
    }
    DVLOG(1) << "Unknown entry type for term " << term << ", type " << type;
    return ENTRY_KIND_UNKNOWN;
  }
  
  // static
  int ResourceEntry::ClassifyEntryKind(DriveEntryKind kind) {
    int classes = 0;
  
    // All DriveEntryKind members are listed here, so the compiler catches if a
    // newly added member is missing here.
    switch (kind) {
      case ENTRY_KIND_UNKNOWN:
      // Special entries.
      case ENTRY_KIND_ITEM:
      case ENTRY_KIND_SITE:
        break;
  
      // Hosted Google document.
      case ENTRY_KIND_DOCUMENT:
      case ENTRY_KIND_SPREADSHEET:
      case ENTRY_KIND_PRESENTATION:
      case ENTRY_KIND_DRAWING:
      case ENTRY_KIND_TABLE:
      case ENTRY_KIND_FORM:
        classes = KIND_OF_GOOGLE_DOCUMENT | KIND_OF_HOSTED_DOCUMENT;
        break;
  
      // Hosted external application document.
      case ENTRY_KIND_EXTERNAL_APP:
        classes = KIND_OF_EXTERNAL_DOCUMENT | KIND_OF_HOSTED_DOCUMENT;
        break;
  
      // Folders, collections.
      case ENTRY_KIND_FOLDER:
        classes = KIND_OF_FOLDER;
        break;
  
      // Regular files.
      case ENTRY_KIND_FILE:
      case ENTRY_KIND_PDF:
        classes = KIND_OF_FILE;
        break;
  
      case ENTRY_KIND_MAX_VALUE:
        NOTREACHED();
    }
  
    return classes;
  }
  
  void ResourceEntry::FillRemainingFields() {
    // Set |kind_| and |labels_| based on the |categories_| in the class.
    // JSONValueConverter does not have the ability to catch an element in a list
    // based on a predicate.  Thus we need to iterate over |categories_| and
    // find the elements to set these fields as a post-process.
    for (size_t i = 0; i < categories_.size(); ++i) {
      const Category* category = categories_[i];
      if (category->type() == Category::CATEGORY_KIND)
        kind_ = GetEntryKindFromTerm(category->term());
      else if (category->type() == Category::CATEGORY_LABEL)
        labels_.push_back(category->label());
    }
  }
  
  // static
  scoped_ptr<ResourceEntry> ResourceEntry::ExtractAndParse(
      const base::Value& value) {
    const base::DictionaryValue* as_dict = NULL;
    const base::DictionaryValue* entry_dict = NULL;
    if (value.GetAsDictionary(&as_dict) &&
        as_dict->GetDictionary(kEntryField, &entry_dict)) {
      return ResourceEntry::CreateFrom(*entry_dict);
    }
    return scoped_ptr<ResourceEntry>();
  }
  
  // static
  scoped_ptr<ResourceEntry> ResourceEntry::CreateFrom(const base::Value& value) {
    base::JSONValueConverter<ResourceEntry> converter;
    scoped_ptr<ResourceEntry> entry(new ResourceEntry());
    if (!converter.Convert(value, entry.get())) {
      DVLOG(1) << "Invalid resource entry!";
      return scoped_ptr<ResourceEntry>();
    }
  
    entry->FillRemainingFields();
    return entry.Pass();
  }
  
  // static
  std::string ResourceEntry::GetEntryNodeName() {
    return kEntryNode;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // ResourceList implementation
  
  ResourceList::ResourceList()
      : start_index_(0),
        items_per_page_(0),
        largest_changestamp_(0) {
  }
  
  ResourceList::~ResourceList() {
  }
  
  // static
  void ResourceList::RegisterJSONConverter(
      base::JSONValueConverter<ResourceList>* converter) {
    // inheritance
    CommonMetadata::RegisterJSONConverter(converter);
    // TODO(zelidrag): Once we figure out where these will be used, we should
    // check for valid start_index_ and items_per_page_ values.
    converter->RegisterCustomField<int>(
        kStartIndexField, &ResourceList::start_index_, &base::StringToInt);
    converter->RegisterCustomField<int>(
        kItemsPerPageField, &ResourceList::items_per_page_, &base::StringToInt);
    converter->RegisterStringField(kTitleTField, &ResourceList::title_);
    converter->RegisterRepeatedMessage(kEntryField, &ResourceList::entries_);
    converter->RegisterCustomField<int64>(
       kLargestChangestampField, &ResourceList::largest_changestamp_,
       &base::StringToInt64);
  }
  
  bool ResourceList::Parse(const base::Value& value) {
    base::JSONValueConverter<ResourceList> converter;
    if (!converter.Convert(value, this)) {
      DVLOG(1) << "Invalid resource list!";
      return false;
    }
  
    ScopedVector<ResourceEntry>::iterator iter = entries_.begin();
    while (iter != entries_.end()) {
      ResourceEntry* entry = (*iter);
      entry->FillRemainingFields();
      ++iter;
    }
    return true;
  }
  
  // static
  scoped_ptr<ResourceList> ResourceList::ExtractAndParse(
      const base::Value& value) {
    const base::DictionaryValue* as_dict = NULL;
    const base::DictionaryValue* feed_dict = NULL;
    if (value.GetAsDictionary(&as_dict) &&
        as_dict->GetDictionary(kFeedField, &feed_dict)) {
      return ResourceList::CreateFrom(*feed_dict);
    }
    return scoped_ptr<ResourceList>();
  }
  
  // static
  scoped_ptr<ResourceList> ResourceList::CreateFrom(const base::Value& value) {
    scoped_ptr<ResourceList> feed(new ResourceList());
    if (!feed->Parse(value)) {
      DVLOG(1) << "Invalid resource list!";
      return scoped_ptr<ResourceList>();
    }
  
    return feed.Pass();
  }
  
  bool ResourceList::GetNextFeedURL(GURL* url) const {
    DCHECK(url);
    for (size_t i = 0; i < links_.size(); ++i) {
      if (links_[i]->type() == Link::LINK_NEXT) {
        *url = links_[i]->href();
        return true;
      }
    }
    return false;
  }
  
  void ResourceList::ReleaseEntries(std::vector<ResourceEntry*>* entries) {
    entries_.release(entries);
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // InstalledApp implementation
  
  InstalledApp::InstalledApp() : supports_create_(false) {
  }
  
  InstalledApp::~InstalledApp() {
  }
  
  InstalledApp::IconList InstalledApp::GetIconsForCategory(
      AppIcon::IconCategory category) const {
    IconList result;
  
    for (ScopedVector<AppIcon>::const_iterator icon_iter = app_icons_.begin();
         icon_iter != app_icons_.end(); ++icon_iter) {
      if ((*icon_iter)->category() != category)
        continue;
      GURL icon_url = (*icon_iter)->GetIconURL();
      if (icon_url.is_empty())
        continue;
      result.push_back(std::make_pair((*icon_iter)->icon_side_length(),
                                      icon_url));
    }
  
    // Return a sorted list, smallest to largest.
    std::sort(result.begin(), result.end(), SortBySize);
    return result;
  }
  
  GURL InstalledApp::GetProductUrl() const {
    for (ScopedVector<Link>::const_iterator it = links_.begin();
         it != links_.end(); ++it) {
      const Link* link = *it;
      if (link->type() == Link::LINK_PRODUCT)
        return link->href();
    }
    return GURL();
  }
  
  // static
  bool InstalledApp::GetValueString(const base::Value* value,
                                    std::string* result) {
    const base::DictionaryValue* dict = NULL;
    if (!value->GetAsDictionary(&dict))
      return false;
  
    if (!dict->GetString(kTField, result))
      return false;
  
    return true;
  }
  
  // static
  void InstalledApp::RegisterJSONConverter(
      base::JSONValueConverter<InstalledApp>* converter) {
    converter->RegisterRepeatedMessage(kInstalledAppIconField,
                                       &InstalledApp::app_icons_);
    converter->RegisterStringField(kInstalledAppIdField,
                                   &InstalledApp::app_id_);
    converter->RegisterStringField(kInstalledAppNameField,
                                   &InstalledApp::app_name_);
    converter->RegisterStringField(kInstalledAppObjectTypeField,
                                   &InstalledApp::object_type_);
    converter->RegisterCustomField<bool>(kInstalledAppSupportsCreateField,
                                         &InstalledApp::supports_create_,
                                         &GetBoolFromString);
    converter->RegisterRepeatedCustomValue(kInstalledAppPrimaryMimeTypeField,
                                           &InstalledApp::primary_mimetypes_,
                                           &GetValueString);
    converter->RegisterRepeatedCustomValue(kInstalledAppSecondaryMimeTypeField,
                                           &InstalledApp::secondary_mimetypes_,
                                           &GetValueString);
    converter->RegisterRepeatedCustomValue(kInstalledAppPrimaryFileExtensionField,
                                           &InstalledApp::primary_extensions_,
                                           &GetValueString);
    converter->RegisterRepeatedCustomValue(
        kInstalledAppSecondaryFileExtensionField,
        &InstalledApp::secondary_extensions_,
        &GetValueString);
    converter->RegisterRepeatedMessage(kLinkField, &InstalledApp::links_);
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  // AccountMetadata implementation
  
  AccountMetadata::AccountMetadata()
      : quota_bytes_total_(0),
        quota_bytes_used_(0),
        largest_changestamp_(0) {
  }
  
  AccountMetadata::~AccountMetadata() {
  }
  
  // static
  void AccountMetadata::RegisterJSONConverter(
      base::JSONValueConverter<AccountMetadata>* converter) {
    converter->RegisterCustomField<int64>(
        kQuotaBytesTotalField,
        &AccountMetadata::quota_bytes_total_,
        &base::StringToInt64);
    converter->RegisterCustomField<int64>(
        kQuotaBytesUsedField,
        &AccountMetadata::quota_bytes_used_,
        &base::StringToInt64);
    converter->RegisterCustomField<int64>(
        kLargestChangestampField,
        &AccountMetadata::largest_changestamp_,
        &base::StringToInt64);
    converter->RegisterRepeatedMessage(kInstalledAppField,
                                       &AccountMetadata::installed_apps_);
  }
  
  // static
  scoped_ptr<AccountMetadata> AccountMetadata::CreateFrom(
      const base::Value& value) {
    scoped_ptr<AccountMetadata> metadata(new AccountMetadata());
    const base::DictionaryValue* dictionary = NULL;
    const base::Value* entry = NULL;
    if (!value.GetAsDictionary(&dictionary) ||
        !dictionary->Get(kEntryField, &entry) ||
        !metadata->Parse(*entry)) {
      LOG(ERROR) << "Unable to create: Invalid account metadata feed!";
      return scoped_ptr<AccountMetadata>();
    }
  
    return metadata.Pass();
  }
  
  bool AccountMetadata::Parse(const base::Value& value) {
    base::JSONValueConverter<AccountMetadata> converter;
    if (!converter.Convert(value, this)) {
      LOG(ERROR) << "Unable to parse: Invalid account metadata feed!";
      return false;
    }
    return true;
  }
  
  }  // namespace google_apis
  