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