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