root/google_apis/drive/gdata_wapi_parser.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. GetGURLFromString
  2. GetBoolFromString
  3. SortBySize
  4. RegisterJSONConverter
  5. GetAppID
  6. GetLinkType
  7. RegisterJSONConverter
  8. GetFeedLinkType
  9. RegisterJSONConverter
  10. GetCategoryTypeFromScheme
  11. RegisterJSONConverter
  12. GetLinkByType
  13. RegisterJSONConverter
  14. icon_side_length_
  15. RegisterJSONConverter
  16. GetIconURL
  17. GetIconCategory
  18. RegisterJSONConverter
  19. image_rotation_
  20. HasFieldPresent
  21. ParseChangestamp
  22. RegisterJSONConverter
  23. GetHostedDocumentExtension
  24. GetEntryKindFromExtension
  25. ClassifyEntryKindByFileExtension
  26. GetEntryKindFromTerm
  27. ClassifyEntryKind
  28. FillRemainingFields
  29. ExtractAndParse
  30. CreateFrom
  31. GetEntryNodeName
  32. largest_changestamp_
  33. RegisterJSONConverter
  34. Parse
  35. ExtractAndParse
  36. CreateFrom
  37. GetNextFeedURL
  38. ReleaseEntries
  39. GetIconsForCategory
  40. GetProductUrl
  41. GetValueString
  42. RegisterJSONConverter
  43. largest_changestamp_
  44. RegisterJSONConverter
  45. CreateFrom
  46. Parse

// 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
DriveEntryKind ResourceEntry::GetEntryKindFromExtension(
    const std::string& extension) {
  for (size_t i = 0; i < arraysize(kEntryKindMap); ++i) {
    const char* document_extension = kEntryKindMap[i].extension;
    if (document_extension && extension == document_extension)
      return kEntryKindMap[i].kind;
  }
  return ENTRY_KIND_UNKNOWN;
}

// static
int ResourceEntry::ClassifyEntryKindByFileExtension(
    const base::FilePath& file_path) {
#if defined(OS_WIN)
  std::string file_extension = base::WideToUTF8(file_path.Extension());
#else
  std::string file_extension = file_path.Extension();
#endif
  return ClassifyEntryKind(GetEntryKindFromExtension(file_extension));
}

// 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

/* [<][>][^][v][top][bottom][index][help] */