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