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
22using base::Value;
23using base::DictionaryValue;
24using base::ListValue;
25
26namespace google_apis {
27
28namespace {
29
30// Term values for kSchemeKind category:
31const char kSchemeKind[] = "http://schemas.google.com/g/2005#kind";
32const char kTermPrefix[] = "http://schemas.google.com/docs/2007#";
33const char kFileTerm[] = "file";
34const char kFolderTerm[] = "folder";
35const char kItemTerm[] = "item";
36const char kPdfTerm[] = "pdf";
37const char kDocumentTerm[] = "document";
38const char kSpreadSheetTerm[] = "spreadsheet";
39const char kPresentationTerm[] = "presentation";
40
41const char kSchemeLabels[] = "http://schemas.google.com/g/2005/labels";
42
43// Node names.
44const char kAuthorNode[] = "author";
45const char kCategoryNode[] = "category";
46const char kContentNode[] = "content";
47const char kEditedNode[] = "edited";
48const char kEmailNode[] = "email";
49const char kEntryNode[] = "entry";
50const char kFeedLinkNode[] = "feedLink";
51const char kFilenameNode[] = "filename";
52const char kIDNode[] = "id";
53const char kLastModifiedByNode[] = "lastModifiedBy";
54const char kLastViewedNode[] = "lastViewed";
55const char kLinkNode[] = "link";
56const char kMd5ChecksumNode[] = "md5Checksum";
57const char kModifiedByMeDateNode[] = "modifiedByMeDate";
58const char kNameNode[] = "name";
59const char kPublishedNode[] = "published";
60const char kQuotaBytesUsedNode[] = "quotaBytesUsed";
61const char kResourceIdNode[] = "resourceId";
62const char kSizeNode[] = "size";
63const char kSuggestedFilenameNode[] = "suggestedFilename";
64const char kTitleNode[] = "title";
65const char kUpdatedNode[] = "updated";
66const char kWritersCanInviteNode[] = "writersCanInvite";
67
68// Field names.
69const char kAuthorField[] = "author";
70const char kCategoryField[] = "category";
71const char kChangestampField[] = "docs$changestamp.value";
72const char kContentField[] = "content";
73const char kDeletedField[] = "gd$deleted";
74const char kETagField[] = "gd$etag";
75const char kEmailField[] = "email.$t";
76const char kEntryField[] = "entry";
77const char kFeedField[] = "feed";
78const char kFeedLinkField[] = "gd$feedLink";
79const char kFileNameField[] = "docs$filename.$t";
80const char kHrefField[] = "href";
81const char kIDField[] = "id.$t";
82const char kInstalledAppField[] = "docs$installedApp";
83const char kInstalledAppNameField[] = "docs$installedAppName";
84const char kInstalledAppIdField[] = "docs$installedAppId";
85const char kInstalledAppIconField[] = "docs$installedAppIcon";
86const char kInstalledAppIconCategoryField[] = "docs$installedAppIconCategory";
87const char kInstalledAppIconSizeField[] = "docs$installedAppIconSize";
88const char kInstalledAppObjectTypeField[] = "docs$installedAppObjectType";
89const char kInstalledAppPrimaryFileExtensionField[] =
90    "docs$installedAppPrimaryFileExtension";
91const char kInstalledAppPrimaryMimeTypeField[] =
92    "docs$installedAppPrimaryMimeType";
93const char kInstalledAppSecondaryFileExtensionField[] =
94    "docs$installedAppSecondaryFileExtension";
95const char kInstalledAppSecondaryMimeTypeField[] =
96    "docs$installedAppSecondaryMimeType";
97const char kInstalledAppSupportsCreateField[] =
98    "docs$installedAppSupportsCreate";
99const char kItemsPerPageField[] = "openSearch$itemsPerPage.$t";
100const char kLabelField[] = "label";
101const char kLargestChangestampField[] = "docs$largestChangestamp.value";
102const char kLastViewedField[] = "gd$lastViewed.$t";
103const char kLinkField[] = "link";
104const char kMD5Field[] = "docs$md5Checksum.$t";
105const char kNameField[] = "name.$t";
106const char kPublishedField[] = "published.$t";
107const char kQuotaBytesTotalField[] = "gd$quotaBytesTotal.$t";
108const char kQuotaBytesUsedField[] = "gd$quotaBytesUsed.$t";
109const char kRelField[] = "rel";
110const char kRemovedField[] = "docs$removed";
111const char kResourceIdField[] = "gd$resourceId.$t";
112const char kSchemeField[] = "scheme";
113const char kSizeField[] = "docs$size.$t";
114const char kSrcField[] = "src";
115const char kStartIndexField[] = "openSearch$startIndex.$t";
116const char kSuggestedFileNameField[] = "docs$suggestedFilename.$t";
117const char kTField[] = "$t";
118const char kTermField[] = "term";
119const char kTitleField[] = "title";
120const char kTitleTField[] = "title.$t";
121const char kTypeField[] = "type";
122const char kUpdatedField[] = "updated.$t";
123
124// Attribute names.
125// Attributes are not namespace-blind as node names in XmlReader.
126const char kETagAttr[] = "gd:etag";
127const char kEmailAttr[] = "email";
128const char kHrefAttr[] = "href";
129const char kLabelAttr[] = "label";
130const char kNameAttr[] = "name";
131const char kRelAttr[] = "rel";
132const char kSchemeAttr[] = "scheme";
133const char kSrcAttr[] = "src";
134const char kTermAttr[] = "term";
135const char kTypeAttr[] = "type";
136const char kValueAttr[] = "value";
137
138// Link Prefixes
139const char kOpenWithPrefix[] = "http://schemas.google.com/docs/2007#open-with-";
140const size_t kOpenWithPrefixSize = arraysize(kOpenWithPrefix) - 1;
141
142struct EntryKindMap {
143  DriveEntryKind kind;
144  const char* entry;
145  const char* extension;
146};
147
148const 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};
162COMPILE_ASSERT(arraysize(kEntryKindMap) == ENTRY_KIND_MAX_VALUE,
163               EntryKindMap_and_DriveEntryKind_are_not_in_sync);
164
165struct LinkTypeMap {
166  Link::LinkType type;
167  const char* rel;
168};
169
170const 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
213struct ResourceLinkTypeMap {
214  ResourceLink::ResourceLinkType type;
215  const char* rel;
216};
217
218const 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
225struct CategoryTypeMap {
226  Category::CategoryType type;
227  const char* scheme;
228};
229
230const 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
235struct AppIconCategoryMap {
236  AppIcon::IconCategory category;
237  const char* category_name;
238};
239
240const 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|.
249bool 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.
255bool GetBoolFromString(const base::StringPiece& value, bool* result) {
256  *result = (value == "true");
257  return true;
258}
259
260bool 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
270Author::Author() {
271}
272
273// static
274void 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
283Link::Link() : type_(Link::LINK_UNKNOWN) {
284}
285
286Link::~Link() {
287}
288
289// static
290bool 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.
309bool 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
335void 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
352ResourceLink::ResourceLink() : type_(ResourceLink::FEED_LINK_UNKNOWN) {
353}
354
355// static.
356bool 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
369void 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
380Category::Category() : type_(CATEGORY_UNKNOWN) {
381}
382
383// Converts category.scheme into CategoryType enum.
384bool 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
397void 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
405const 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
416Content::Content() {
417}
418
419// static
420void 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
429AppIcon::AppIcon() : category_(AppIcon::ICON_UNKNOWN), icon_side_length_(0) {
430}
431
432AppIcon::~AppIcon() {
433}
434
435// static
436void 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
448GURL 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
457bool 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
472CommonMetadata::CommonMetadata() {
473}
474
475CommonMetadata::~CommonMetadata() {
476}
477
478// static
479template<typename CommonMetadataDescendant>
480void 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
496ResourceEntry::ResourceEntry()
497    : kind_(ENTRY_KIND_UNKNOWN),
498      file_size_(0),
499      deleted_(false),
500      removed_(false),
501      changestamp_(0) {
502}
503
504ResourceEntry::~ResourceEntry() {
505}
506
507bool ResourceEntry::HasFieldPresent(const base::Value* value,
508                                    bool* result) {
509  *result = (value != NULL);
510  return true;
511}
512
513bool 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
530void 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
568std::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
581int 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
597DriveEntryKind 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
614int 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
658void 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
673scoped_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
685scoped_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
698scoped_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
772scoped_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
789std::string ResourceEntry::GetEntryNodeName() {
790  return kEntryNode;
791}
792
793////////////////////////////////////////////////////////////////////////////////
794// ResourceList implementation
795
796ResourceList::ResourceList()
797    : start_index_(0),
798      items_per_page_(0),
799      largest_changestamp_(0) {
800}
801
802ResourceList::~ResourceList() {
803}
804
805// static
806void 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
823bool 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
840scoped_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
852scoped_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
863scoped_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
889scoped_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
908bool 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
919void ResourceList::ReleaseEntries(std::vector<ResourceEntry*>* entries) {
920  entries_.release(entries);
921}
922
923////////////////////////////////////////////////////////////////////////////////
924// InstalledApp implementation
925
926InstalledApp::InstalledApp() : supports_create_(false) {
927}
928
929InstalledApp::~InstalledApp() {
930}
931
932InstalledApp::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
952GURL 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
963bool 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
976void 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
1008AccountMetadata::AccountMetadata()
1009    : quota_bytes_total_(0),
1010      quota_bytes_used_(0),
1011      largest_changestamp_(0) {
1012}
1013
1014AccountMetadata::~AccountMetadata() {
1015}
1016
1017// static
1018void 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
1037scoped_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
1052bool 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