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 "google_apis/drive/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 "google_apis/drive/time_util.h" 20 21 using base::Value; 22 using base::DictionaryValue; 23 using base::ListValue; 24 25 namespace google_apis { 26 27 namespace { 28 29 // Term values for kSchemeKind category: 30 const char kTermPrefix[] = "http://schemas.google.com/docs/2007#"; 31 32 // Node names. 33 const char kEntryNode[] = "entry"; 34 35 // Field names. 36 const char kAuthorField[] = "author"; 37 const char kCategoryField[] = "category"; 38 const char kChangestampField[] = "docs$changestamp.value"; 39 const char kContentField[] = "content"; 40 const char kDeletedField[] = "gd$deleted"; 41 const char kETagField[] = "gd$etag"; 42 const char kEmailField[] = "email.$t"; 43 const char kEntryField[] = "entry"; 44 const char kFeedField[] = "feed"; 45 const char kFeedLinkField[] = "gd$feedLink"; 46 const char kFileNameField[] = "docs$filename.$t"; 47 const char kHrefField[] = "href"; 48 const char kIDField[] = "id.$t"; 49 const char kItemsPerPageField[] = "openSearch$itemsPerPage.$t"; 50 const char kLabelField[] = "label"; 51 const char kLargestChangestampField[] = "docs$largestChangestamp.value"; 52 const char kLastViewedField[] = "gd$lastViewed.$t"; 53 const char kLinkField[] = "link"; 54 const char kMD5Field[] = "docs$md5Checksum.$t"; 55 const char kNameField[] = "name.$t"; 56 const char kPublishedField[] = "published.$t"; 57 const char kRelField[] = "rel"; 58 const char kRemovedField[] = "docs$removed"; 59 const char kResourceIdField[] = "gd$resourceId.$t"; 60 const char kSchemeField[] = "scheme"; 61 const char kSizeField[] = "docs$size.$t"; 62 const char kSrcField[] = "src"; 63 const char kStartIndexField[] = "openSearch$startIndex.$t"; 64 const char kSuggestedFileNameField[] = "docs$suggestedFilename.$t"; 65 const char kTermField[] = "term"; 66 const char kTitleField[] = "title"; 67 const char kTitleTField[] = "title.$t"; 68 const char kTypeField[] = "type"; 69 const char kUpdatedField[] = "updated.$t"; 70 71 // Link Prefixes 72 const char kOpenWithPrefix[] = "http://schemas.google.com/docs/2007#open-with-"; 73 const size_t kOpenWithPrefixSize = arraysize(kOpenWithPrefix) - 1; 74 75 struct EntryKindMap { 76 DriveEntryKind kind; 77 const char* entry; 78 const char* extension; 79 }; 80 81 const EntryKindMap kEntryKindMap[] = { 82 { ENTRY_KIND_UNKNOWN, "unknown", NULL}, 83 { ENTRY_KIND_ITEM, "item", NULL}, 84 { ENTRY_KIND_DOCUMENT, "document", ".gdoc"}, 85 { ENTRY_KIND_SPREADSHEET, "spreadsheet", ".gsheet"}, 86 { ENTRY_KIND_PRESENTATION, "presentation", ".gslides" }, 87 { ENTRY_KIND_DRAWING, "drawing", ".gdraw"}, 88 { ENTRY_KIND_TABLE, "table", ".gtable"}, 89 { ENTRY_KIND_FORM, "form", ".gform"}, 90 { ENTRY_KIND_EXTERNAL_APP, "externalapp", ".glink"}, 91 { ENTRY_KIND_SITE, "site", NULL}, 92 { ENTRY_KIND_FOLDER, "folder", NULL}, 93 { ENTRY_KIND_FILE, "file", NULL}, 94 { ENTRY_KIND_PDF, "pdf", NULL}, 95 }; 96 COMPILE_ASSERT(arraysize(kEntryKindMap) == ENTRY_KIND_MAX_VALUE, 97 EntryKindMap_and_DriveEntryKind_are_not_in_sync); 98 99 struct LinkTypeMap { 100 Link::LinkType type; 101 const char* rel; 102 }; 103 104 const LinkTypeMap kLinkTypeMap[] = { 105 { Link::LINK_SELF, 106 "self" }, 107 { Link::LINK_NEXT, 108 "next" }, 109 { Link::LINK_PARENT, 110 "http://schemas.google.com/docs/2007#parent" }, 111 { Link::LINK_ALTERNATE, 112 "alternate"}, 113 { Link::LINK_EDIT, 114 "edit" }, 115 { Link::LINK_EDIT_MEDIA, 116 "edit-media" }, 117 { Link::LINK_ALT_EDIT_MEDIA, 118 "http://schemas.google.com/docs/2007#alt-edit-media" }, 119 { Link::LINK_ALT_POST, 120 "http://schemas.google.com/docs/2007#alt-post" }, 121 { Link::LINK_FEED, 122 "http://schemas.google.com/g/2005#feed"}, 123 { Link::LINK_POST, 124 "http://schemas.google.com/g/2005#post"}, 125 { Link::LINK_BATCH, 126 "http://schemas.google.com/g/2005#batch"}, 127 { Link::LINK_THUMBNAIL, 128 "http://schemas.google.com/docs/2007/thumbnail"}, 129 { Link::LINK_RESUMABLE_EDIT_MEDIA, 130 "http://schemas.google.com/g/2005#resumable-edit-media"}, 131 { Link::LINK_RESUMABLE_CREATE_MEDIA, 132 "http://schemas.google.com/g/2005#resumable-create-media"}, 133 { Link::LINK_TABLES_FEED, 134 "http://schemas.google.com/spreadsheets/2006#tablesfeed"}, 135 { Link::LINK_WORKSHEET_FEED, 136 "http://schemas.google.com/spreadsheets/2006#worksheetsfeed"}, 137 { Link::LINK_EMBED, 138 "http://schemas.google.com/docs/2007#embed"}, 139 { Link::LINK_PRODUCT, 140 "http://schemas.google.com/docs/2007#product"}, 141 { Link::LINK_ICON, 142 "http://schemas.google.com/docs/2007#icon"}, 143 { Link::LINK_SHARE, 144 "http://schemas.google.com/docs/2007#share"}, 145 }; 146 147 struct ResourceLinkTypeMap { 148 ResourceLink::ResourceLinkType type; 149 const char* rel; 150 }; 151 152 const ResourceLinkTypeMap kFeedLinkTypeMap[] = { 153 { ResourceLink::FEED_LINK_ACL, 154 "http://schemas.google.com/acl/2007#accessControlList" }, 155 { ResourceLink::FEED_LINK_REVISIONS, 156 "http://schemas.google.com/docs/2007/revisions" }, 157 }; 158 159 struct CategoryTypeMap { 160 Category::CategoryType type; 161 const char* scheme; 162 }; 163 164 const CategoryTypeMap kCategoryTypeMap[] = { 165 { Category::CATEGORY_KIND, "http://schemas.google.com/g/2005#kind" }, 166 { Category::CATEGORY_LABEL, "http://schemas.google.com/g/2005/labels" }, 167 }; 168 169 // Converts |url_string| to |result|. Always returns true to be used 170 // for JSONValueConverter::RegisterCustomField method. 171 // TODO(mukai): make it return false in case of invalid |url_string|. 172 bool GetGURLFromString(const base::StringPiece& url_string, GURL* result) { 173 *result = GURL(url_string.as_string()); 174 return true; 175 } 176 177 } // namespace 178 179 //////////////////////////////////////////////////////////////////////////////// 180 // Author implementation 181 182 Author::Author() { 183 } 184 185 // static 186 void Author::RegisterJSONConverter( 187 base::JSONValueConverter<Author>* converter) { 188 converter->RegisterStringField(kNameField, &Author::name_); 189 converter->RegisterStringField(kEmailField, &Author::email_); 190 } 191 192 //////////////////////////////////////////////////////////////////////////////// 193 // Link implementation 194 195 Link::Link() : type_(Link::LINK_UNKNOWN) { 196 } 197 198 Link::~Link() { 199 } 200 201 // static 202 bool Link::GetAppID(const base::StringPiece& rel, std::string* app_id) { 203 DCHECK(app_id); 204 // Fast return path if the link clearly isn't an OPEN_WITH link. 205 if (rel.size() < kOpenWithPrefixSize) { 206 app_id->clear(); 207 return true; 208 } 209 210 const std::string kOpenWithPrefixStr(kOpenWithPrefix); 211 if (StartsWithASCII(rel.as_string(), kOpenWithPrefixStr, false)) { 212 *app_id = rel.as_string().substr(kOpenWithPrefixStr.size()); 213 return true; 214 } 215 216 app_id->clear(); 217 return true; 218 } 219 220 // static. 221 bool Link::GetLinkType(const base::StringPiece& rel, Link::LinkType* type) { 222 DCHECK(type); 223 for (size_t i = 0; i < arraysize(kLinkTypeMap); i++) { 224 if (rel == kLinkTypeMap[i].rel) { 225 *type = kLinkTypeMap[i].type; 226 return true; 227 } 228 } 229 230 // OPEN_WITH links have extra information at the end of the rel that is unique 231 // for each one, so we can't just check the usual map. This check is slightly 232 // redundant to provide a quick skip if it's obviously not an OPEN_WITH url. 233 if (rel.size() >= kOpenWithPrefixSize && 234 StartsWithASCII(rel.as_string(), kOpenWithPrefix, false)) { 235 *type = LINK_OPEN_WITH; 236 return true; 237 } 238 239 // Let unknown link types through, just report it; if the link type is needed 240 // in the future, add it into LinkType and kLinkTypeMap. 241 DVLOG(1) << "Ignoring unknown link type for rel " << rel; 242 *type = LINK_UNKNOWN; 243 return true; 244 } 245 246 // static 247 void Link::RegisterJSONConverter(base::JSONValueConverter<Link>* converter) { 248 converter->RegisterCustomField<Link::LinkType>(kRelField, 249 &Link::type_, 250 &Link::GetLinkType); 251 // We have to register kRelField twice because we extract two different pieces 252 // of data from the same rel field. 253 converter->RegisterCustomField<std::string>(kRelField, 254 &Link::app_id_, 255 &Link::GetAppID); 256 converter->RegisterCustomField(kHrefField, &Link::href_, &GetGURLFromString); 257 converter->RegisterStringField(kTitleField, &Link::title_); 258 converter->RegisterStringField(kTypeField, &Link::mime_type_); 259 } 260 261 //////////////////////////////////////////////////////////////////////////////// 262 // ResourceLink implementation 263 264 ResourceLink::ResourceLink() : type_(ResourceLink::FEED_LINK_UNKNOWN) { 265 } 266 267 // static. 268 bool ResourceLink::GetFeedLinkType( 269 const base::StringPiece& rel, ResourceLink::ResourceLinkType* result) { 270 for (size_t i = 0; i < arraysize(kFeedLinkTypeMap); i++) { 271 if (rel == kFeedLinkTypeMap[i].rel) { 272 *result = kFeedLinkTypeMap[i].type; 273 return true; 274 } 275 } 276 DVLOG(1) << "Unknown feed link type for rel " << rel; 277 return false; 278 } 279 280 // static 281 void ResourceLink::RegisterJSONConverter( 282 base::JSONValueConverter<ResourceLink>* converter) { 283 converter->RegisterCustomField<ResourceLink::ResourceLinkType>( 284 kRelField, &ResourceLink::type_, &ResourceLink::GetFeedLinkType); 285 converter->RegisterCustomField( 286 kHrefField, &ResourceLink::href_, &GetGURLFromString); 287 } 288 289 //////////////////////////////////////////////////////////////////////////////// 290 // Category implementation 291 292 Category::Category() : type_(CATEGORY_UNKNOWN) { 293 } 294 295 // Converts category.scheme into CategoryType enum. 296 bool Category::GetCategoryTypeFromScheme( 297 const base::StringPiece& scheme, Category::CategoryType* result) { 298 for (size_t i = 0; i < arraysize(kCategoryTypeMap); i++) { 299 if (scheme == kCategoryTypeMap[i].scheme) { 300 *result = kCategoryTypeMap[i].type; 301 return true; 302 } 303 } 304 DVLOG(1) << "Unknown feed link type for scheme " << scheme; 305 return false; 306 } 307 308 // static 309 void Category::RegisterJSONConverter( 310 base::JSONValueConverter<Category>* converter) { 311 converter->RegisterStringField(kLabelField, &Category::label_); 312 converter->RegisterCustomField<Category::CategoryType>( 313 kSchemeField, &Category::type_, &Category::GetCategoryTypeFromScheme); 314 converter->RegisterStringField(kTermField, &Category::term_); 315 } 316 317 const Link* CommonMetadata::GetLinkByType(Link::LinkType type) const { 318 for (size_t i = 0; i < links_.size(); ++i) { 319 if (links_[i]->type() == type) 320 return links_[i]; 321 } 322 return NULL; 323 } 324 325 //////////////////////////////////////////////////////////////////////////////// 326 // Content implementation 327 328 Content::Content() { 329 } 330 331 // static 332 void Content::RegisterJSONConverter( 333 base::JSONValueConverter<Content>* converter) { 334 converter->RegisterCustomField(kSrcField, &Content::url_, &GetGURLFromString); 335 converter->RegisterStringField(kTypeField, &Content::mime_type_); 336 } 337 338 //////////////////////////////////////////////////////////////////////////////// 339 // CommonMetadata implementation 340 341 CommonMetadata::CommonMetadata() { 342 } 343 344 CommonMetadata::~CommonMetadata() { 345 } 346 347 // static 348 template<typename CommonMetadataDescendant> 349 void CommonMetadata::RegisterJSONConverter( 350 base::JSONValueConverter<CommonMetadataDescendant>* converter) { 351 converter->RegisterStringField(kETagField, &CommonMetadata::etag_); 352 converter->template RegisterRepeatedMessage<Author>( 353 kAuthorField, &CommonMetadata::authors_); 354 converter->template RegisterRepeatedMessage<Link>( 355 kLinkField, &CommonMetadata::links_); 356 converter->template RegisterRepeatedMessage<Category>( 357 kCategoryField, &CommonMetadata::categories_); 358 converter->template RegisterCustomField<base::Time>( 359 kUpdatedField, &CommonMetadata::updated_time_, &util::GetTimeFromString); 360 } 361 362 //////////////////////////////////////////////////////////////////////////////// 363 // ResourceEntry implementation 364 365 ResourceEntry::ResourceEntry() 366 : kind_(ENTRY_KIND_UNKNOWN), 367 file_size_(0), 368 deleted_(false), 369 removed_(false), 370 changestamp_(0), 371 image_width_(-1), 372 image_height_(-1), 373 image_rotation_(-1) { 374 } 375 376 ResourceEntry::~ResourceEntry() { 377 } 378 379 bool ResourceEntry::HasFieldPresent(const base::Value* value, 380 bool* result) { 381 *result = (value != NULL); 382 return true; 383 } 384 385 bool ResourceEntry::ParseChangestamp(const base::Value* value, 386 int64* result) { 387 DCHECK(result); 388 if (!value) { 389 *result = 0; 390 return true; 391 } 392 393 std::string string_value; 394 if (value->GetAsString(&string_value) && 395 base::StringToInt64(string_value, result)) 396 return true; 397 398 return false; 399 } 400 401 // static 402 void ResourceEntry::RegisterJSONConverter( 403 base::JSONValueConverter<ResourceEntry>* converter) { 404 // Inherit the parent registrations. 405 CommonMetadata::RegisterJSONConverter(converter); 406 converter->RegisterStringField( 407 kResourceIdField, &ResourceEntry::resource_id_); 408 converter->RegisterStringField(kIDField, &ResourceEntry::id_); 409 converter->RegisterStringField(kTitleTField, &ResourceEntry::title_); 410 converter->RegisterCustomField<base::Time>( 411 kPublishedField, &ResourceEntry::published_time_, 412 &util::GetTimeFromString); 413 converter->RegisterCustomField<base::Time>( 414 kLastViewedField, &ResourceEntry::last_viewed_time_, 415 &util::GetTimeFromString); 416 converter->RegisterRepeatedMessage( 417 kFeedLinkField, &ResourceEntry::resource_links_); 418 converter->RegisterNestedField(kContentField, &ResourceEntry::content_); 419 420 // File properties. If the resource type is not a normal file, then 421 // that's no problem because those feed must not have these fields 422 // themselves, which does not report errors. 423 converter->RegisterStringField(kFileNameField, &ResourceEntry::filename_); 424 converter->RegisterStringField(kMD5Field, &ResourceEntry::file_md5_); 425 converter->RegisterCustomField<int64>( 426 kSizeField, &ResourceEntry::file_size_, &base::StringToInt64); 427 converter->RegisterStringField( 428 kSuggestedFileNameField, &ResourceEntry::suggested_filename_); 429 // Deleted are treated as 'trashed' items on web client side. Removed files 430 // are gone for good. We treat both cases as 'deleted' for this client. 431 converter->RegisterCustomValueField<bool>( 432 kDeletedField, &ResourceEntry::deleted_, &ResourceEntry::HasFieldPresent); 433 converter->RegisterCustomValueField<bool>( 434 kRemovedField, &ResourceEntry::removed_, &ResourceEntry::HasFieldPresent); 435 converter->RegisterCustomValueField<int64>( 436 kChangestampField, &ResourceEntry::changestamp_, 437 &ResourceEntry::ParseChangestamp); 438 // ImageMediaMetadata fields are not supported by WAPI. 439 } 440 441 // static 442 std::string ResourceEntry::GetHostedDocumentExtension(DriveEntryKind kind) { 443 for (size_t i = 0; i < arraysize(kEntryKindMap); i++) { 444 if (kEntryKindMap[i].kind == kind) { 445 if (kEntryKindMap[i].extension) 446 return std::string(kEntryKindMap[i].extension); 447 else 448 return std::string(); 449 } 450 } 451 return std::string(); 452 } 453 454 // static 455 DriveEntryKind ResourceEntry::GetEntryKindFromExtension( 456 const std::string& extension) { 457 for (size_t i = 0; i < arraysize(kEntryKindMap); ++i) { 458 const char* document_extension = kEntryKindMap[i].extension; 459 if (document_extension && extension == document_extension) 460 return kEntryKindMap[i].kind; 461 } 462 return ENTRY_KIND_UNKNOWN; 463 } 464 465 // static 466 int ResourceEntry::ClassifyEntryKindByFileExtension( 467 const base::FilePath& file_path) { 468 #if defined(OS_WIN) 469 std::string file_extension = base::WideToUTF8(file_path.Extension()); 470 #else 471 std::string file_extension = file_path.Extension(); 472 #endif 473 return ClassifyEntryKind(GetEntryKindFromExtension(file_extension)); 474 } 475 476 // static 477 DriveEntryKind ResourceEntry::GetEntryKindFromTerm( 478 const std::string& term) { 479 if (!StartsWithASCII(term, kTermPrefix, false)) { 480 DVLOG(1) << "Unexpected term prefix term " << term; 481 return ENTRY_KIND_UNKNOWN; 482 } 483 484 std::string type = term.substr(strlen(kTermPrefix)); 485 for (size_t i = 0; i < arraysize(kEntryKindMap); i++) { 486 if (type == kEntryKindMap[i].entry) 487 return kEntryKindMap[i].kind; 488 } 489 DVLOG(1) << "Unknown entry type for term " << term << ", type " << type; 490 return ENTRY_KIND_UNKNOWN; 491 } 492 493 // static 494 int ResourceEntry::ClassifyEntryKind(DriveEntryKind kind) { 495 int classes = 0; 496 497 // All DriveEntryKind members are listed here, so the compiler catches if a 498 // newly added member is missing here. 499 switch (kind) { 500 case ENTRY_KIND_UNKNOWN: 501 // Special entries. 502 case ENTRY_KIND_ITEM: 503 case ENTRY_KIND_SITE: 504 break; 505 506 // Hosted Google document. 507 case ENTRY_KIND_DOCUMENT: 508 case ENTRY_KIND_SPREADSHEET: 509 case ENTRY_KIND_PRESENTATION: 510 case ENTRY_KIND_DRAWING: 511 case ENTRY_KIND_TABLE: 512 case ENTRY_KIND_FORM: 513 classes = KIND_OF_GOOGLE_DOCUMENT | KIND_OF_HOSTED_DOCUMENT; 514 break; 515 516 // Hosted external application document. 517 case ENTRY_KIND_EXTERNAL_APP: 518 classes = KIND_OF_EXTERNAL_DOCUMENT | KIND_OF_HOSTED_DOCUMENT; 519 break; 520 521 // Folders, collections. 522 case ENTRY_KIND_FOLDER: 523 classes = KIND_OF_FOLDER; 524 break; 525 526 // Regular files. 527 case ENTRY_KIND_FILE: 528 case ENTRY_KIND_PDF: 529 classes = KIND_OF_FILE; 530 break; 531 532 case ENTRY_KIND_MAX_VALUE: 533 NOTREACHED(); 534 } 535 536 return classes; 537 } 538 539 void ResourceEntry::FillRemainingFields() { 540 // Set |kind_| and |labels_| based on the |categories_| in the class. 541 // JSONValueConverter does not have the ability to catch an element in a list 542 // based on a predicate. Thus we need to iterate over |categories_| and 543 // find the elements to set these fields as a post-process. 544 for (size_t i = 0; i < categories_.size(); ++i) { 545 const Category* category = categories_[i]; 546 if (category->type() == Category::CATEGORY_KIND) 547 kind_ = GetEntryKindFromTerm(category->term()); 548 else if (category->type() == Category::CATEGORY_LABEL) 549 labels_.push_back(category->label()); 550 } 551 } 552 553 // static 554 scoped_ptr<ResourceEntry> ResourceEntry::ExtractAndParse( 555 const base::Value& value) { 556 const base::DictionaryValue* as_dict = NULL; 557 const base::DictionaryValue* entry_dict = NULL; 558 if (value.GetAsDictionary(&as_dict) && 559 as_dict->GetDictionary(kEntryField, &entry_dict)) { 560 return ResourceEntry::CreateFrom(*entry_dict); 561 } 562 return scoped_ptr<ResourceEntry>(); 563 } 564 565 // static 566 scoped_ptr<ResourceEntry> ResourceEntry::CreateFrom(const base::Value& value) { 567 base::JSONValueConverter<ResourceEntry> converter; 568 scoped_ptr<ResourceEntry> entry(new ResourceEntry()); 569 if (!converter.Convert(value, entry.get())) { 570 DVLOG(1) << "Invalid resource entry!"; 571 return scoped_ptr<ResourceEntry>(); 572 } 573 574 entry->FillRemainingFields(); 575 return entry.Pass(); 576 } 577 578 // static 579 std::string ResourceEntry::GetEntryNodeName() { 580 return kEntryNode; 581 } 582 583 //////////////////////////////////////////////////////////////////////////////// 584 // ResourceList implementation 585 586 ResourceList::ResourceList() 587 : start_index_(0), 588 items_per_page_(0), 589 largest_changestamp_(0) { 590 } 591 592 ResourceList::~ResourceList() { 593 } 594 595 // static 596 void ResourceList::RegisterJSONConverter( 597 base::JSONValueConverter<ResourceList>* converter) { 598 // inheritance 599 CommonMetadata::RegisterJSONConverter(converter); 600 // TODO(zelidrag): Once we figure out where these will be used, we should 601 // check for valid start_index_ and items_per_page_ values. 602 converter->RegisterCustomField<int>( 603 kStartIndexField, &ResourceList::start_index_, &base::StringToInt); 604 converter->RegisterCustomField<int>( 605 kItemsPerPageField, &ResourceList::items_per_page_, &base::StringToInt); 606 converter->RegisterStringField(kTitleTField, &ResourceList::title_); 607 converter->RegisterRepeatedMessage(kEntryField, &ResourceList::entries_); 608 converter->RegisterCustomField<int64>( 609 kLargestChangestampField, &ResourceList::largest_changestamp_, 610 &base::StringToInt64); 611 } 612 613 bool ResourceList::Parse(const base::Value& value) { 614 base::JSONValueConverter<ResourceList> converter; 615 if (!converter.Convert(value, this)) { 616 DVLOG(1) << "Invalid resource list!"; 617 return false; 618 } 619 620 ScopedVector<ResourceEntry>::iterator iter = entries_.begin(); 621 while (iter != entries_.end()) { 622 ResourceEntry* entry = (*iter); 623 entry->FillRemainingFields(); 624 ++iter; 625 } 626 return true; 627 } 628 629 // static 630 scoped_ptr<ResourceList> ResourceList::ExtractAndParse( 631 const base::Value& value) { 632 const base::DictionaryValue* as_dict = NULL; 633 const base::DictionaryValue* feed_dict = NULL; 634 if (value.GetAsDictionary(&as_dict) && 635 as_dict->GetDictionary(kFeedField, &feed_dict)) { 636 return ResourceList::CreateFrom(*feed_dict); 637 } 638 return scoped_ptr<ResourceList>(); 639 } 640 641 // static 642 scoped_ptr<ResourceList> ResourceList::CreateFrom(const base::Value& value) { 643 scoped_ptr<ResourceList> feed(new ResourceList()); 644 if (!feed->Parse(value)) { 645 DVLOG(1) << "Invalid resource list!"; 646 return scoped_ptr<ResourceList>(); 647 } 648 649 return feed.Pass(); 650 } 651 652 bool ResourceList::GetNextFeedURL(GURL* url) const { 653 DCHECK(url); 654 for (size_t i = 0; i < links_.size(); ++i) { 655 if (links_[i]->type() == Link::LINK_NEXT) { 656 *url = links_[i]->href(); 657 return true; 658 } 659 } 660 return false; 661 } 662 663 void ResourceList::ReleaseEntries(std::vector<ResourceEntry*>* entries) { 664 entries_.release(entries); 665 } 666 667 } // namespace google_apis 668