OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "google_apis/drive/gdata_wapi_parser.h" | 5 #include "google_apis/drive/gdata_wapi_parser.h" |
6 | 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 { | 7 namespace google_apis { |
26 | 8 |
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 LinkTypeMap { | |
76 Link::LinkType type; | |
77 const char* rel; | |
78 }; | |
79 | |
80 const LinkTypeMap kLinkTypeMap[] = { | |
81 { Link::LINK_SELF, | |
82 "self" }, | |
83 { Link::LINK_NEXT, | |
84 "next" }, | |
85 { Link::LINK_PARENT, | |
86 "http://schemas.google.com/docs/2007#parent" }, | |
87 { Link::LINK_ALTERNATE, | |
88 "alternate"}, | |
89 { Link::LINK_EDIT, | |
90 "edit" }, | |
91 { Link::LINK_EDIT_MEDIA, | |
92 "edit-media" }, | |
93 { Link::LINK_ALT_EDIT_MEDIA, | |
94 "http://schemas.google.com/docs/2007#alt-edit-media" }, | |
95 { Link::LINK_ALT_POST, | |
96 "http://schemas.google.com/docs/2007#alt-post" }, | |
97 { Link::LINK_FEED, | |
98 "http://schemas.google.com/g/2005#feed"}, | |
99 { Link::LINK_POST, | |
100 "http://schemas.google.com/g/2005#post"}, | |
101 { Link::LINK_BATCH, | |
102 "http://schemas.google.com/g/2005#batch"}, | |
103 { Link::LINK_THUMBNAIL, | |
104 "http://schemas.google.com/docs/2007/thumbnail"}, | |
105 { Link::LINK_RESUMABLE_EDIT_MEDIA, | |
106 "http://schemas.google.com/g/2005#resumable-edit-media"}, | |
107 { Link::LINK_RESUMABLE_CREATE_MEDIA, | |
108 "http://schemas.google.com/g/2005#resumable-create-media"}, | |
109 { Link::LINK_TABLES_FEED, | |
110 "http://schemas.google.com/spreadsheets/2006#tablesfeed"}, | |
111 { Link::LINK_WORKSHEET_FEED, | |
112 "http://schemas.google.com/spreadsheets/2006#worksheetsfeed"}, | |
113 { Link::LINK_EMBED, | |
114 "http://schemas.google.com/docs/2007#embed"}, | |
115 { Link::LINK_PRODUCT, | |
116 "http://schemas.google.com/docs/2007#product"}, | |
117 { Link::LINK_ICON, | |
118 "http://schemas.google.com/docs/2007#icon"}, | |
119 { Link::LINK_SHARE, | |
120 "http://schemas.google.com/docs/2007#share"}, | |
121 }; | |
122 | |
123 struct ResourceLinkTypeMap { | |
124 ResourceLink::ResourceLinkType type; | |
125 const char* rel; | |
126 }; | |
127 | |
128 const ResourceLinkTypeMap kFeedLinkTypeMap[] = { | |
129 { ResourceLink::FEED_LINK_ACL, | |
130 "http://schemas.google.com/acl/2007#accessControlList" }, | |
131 { ResourceLink::FEED_LINK_REVISIONS, | |
132 "http://schemas.google.com/docs/2007/revisions" }, | |
133 }; | |
134 | |
135 struct CategoryTypeMap { | |
136 Category::CategoryType type; | |
137 const char* scheme; | |
138 }; | |
139 | |
140 const CategoryTypeMap kCategoryTypeMap[] = { | |
141 { Category::CATEGORY_KIND, "http://schemas.google.com/g/2005#kind" }, | |
142 { Category::CATEGORY_LABEL, "http://schemas.google.com/g/2005/labels" }, | |
143 }; | |
144 | |
145 // Converts |url_string| to |result|. Always returns true to be used | |
146 // for JSONValueConverter::RegisterCustomField method. | |
147 // TODO(mukai): make it return false in case of invalid |url_string|. | |
148 bool GetGURLFromString(const base::StringPiece& url_string, GURL* result) { | |
149 *result = GURL(url_string.as_string()); | |
150 return true; | |
151 } | |
152 | |
153 } // namespace | |
154 | |
155 //////////////////////////////////////////////////////////////////////////////// | |
156 // Author implementation | |
157 | |
158 Author::Author() { | |
159 } | |
160 | |
161 // static | |
162 void Author::RegisterJSONConverter( | |
163 base::JSONValueConverter<Author>* converter) { | |
164 converter->RegisterStringField(kNameField, &Author::name_); | |
165 converter->RegisterStringField(kEmailField, &Author::email_); | |
166 } | |
167 | |
168 //////////////////////////////////////////////////////////////////////////////// | |
169 // Link implementation | |
170 | |
171 Link::Link() : type_(Link::LINK_UNKNOWN) { | |
172 } | |
173 | |
174 Link::~Link() { | |
175 } | |
176 | |
177 // static | |
178 bool Link::GetAppID(const base::StringPiece& rel, std::string* app_id) { | |
179 DCHECK(app_id); | |
180 // Fast return path if the link clearly isn't an OPEN_WITH link. | |
181 if (rel.size() < kOpenWithPrefixSize) { | |
182 app_id->clear(); | |
183 return true; | |
184 } | |
185 | |
186 const std::string kOpenWithPrefixStr(kOpenWithPrefix); | |
187 if (StartsWithASCII(rel.as_string(), kOpenWithPrefixStr, false)) { | |
188 *app_id = rel.as_string().substr(kOpenWithPrefixStr.size()); | |
189 return true; | |
190 } | |
191 | |
192 app_id->clear(); | |
193 return true; | |
194 } | |
195 | |
196 // static. | |
197 bool Link::GetLinkType(const base::StringPiece& rel, Link::LinkType* type) { | |
198 DCHECK(type); | |
199 for (size_t i = 0; i < arraysize(kLinkTypeMap); i++) { | |
200 if (rel == kLinkTypeMap[i].rel) { | |
201 *type = kLinkTypeMap[i].type; | |
202 return true; | |
203 } | |
204 } | |
205 | |
206 // OPEN_WITH links have extra information at the end of the rel that is unique | |
207 // for each one, so we can't just check the usual map. This check is slightly | |
208 // redundant to provide a quick skip if it's obviously not an OPEN_WITH url. | |
209 if (rel.size() >= kOpenWithPrefixSize && | |
210 StartsWithASCII(rel.as_string(), kOpenWithPrefix, false)) { | |
211 *type = LINK_OPEN_WITH; | |
212 return true; | |
213 } | |
214 | |
215 // Let unknown link types through, just report it; if the link type is needed | |
216 // in the future, add it into LinkType and kLinkTypeMap. | |
217 DVLOG(1) << "Ignoring unknown link type for rel " << rel; | |
218 *type = LINK_UNKNOWN; | |
219 return true; | |
220 } | |
221 | |
222 // static | |
223 void Link::RegisterJSONConverter(base::JSONValueConverter<Link>* converter) { | |
224 converter->RegisterCustomField<Link::LinkType>(kRelField, | |
225 &Link::type_, | |
226 &Link::GetLinkType); | |
227 // We have to register kRelField twice because we extract two different pieces | |
228 // of data from the same rel field. | |
229 converter->RegisterCustomField<std::string>(kRelField, | |
230 &Link::app_id_, | |
231 &Link::GetAppID); | |
232 converter->RegisterCustomField(kHrefField, &Link::href_, &GetGURLFromString); | |
233 converter->RegisterStringField(kTitleField, &Link::title_); | |
234 converter->RegisterStringField(kTypeField, &Link::mime_type_); | |
235 } | |
236 | |
237 //////////////////////////////////////////////////////////////////////////////// | |
238 // ResourceLink implementation | |
239 | |
240 ResourceLink::ResourceLink() : type_(ResourceLink::FEED_LINK_UNKNOWN) { | |
241 } | |
242 | |
243 // static. | |
244 bool ResourceLink::GetFeedLinkType( | |
245 const base::StringPiece& rel, ResourceLink::ResourceLinkType* result) { | |
246 for (size_t i = 0; i < arraysize(kFeedLinkTypeMap); i++) { | |
247 if (rel == kFeedLinkTypeMap[i].rel) { | |
248 *result = kFeedLinkTypeMap[i].type; | |
249 return true; | |
250 } | |
251 } | |
252 DVLOG(1) << "Unknown feed link type for rel " << rel; | |
253 return false; | |
254 } | |
255 | |
256 // static | |
257 void ResourceLink::RegisterJSONConverter( | |
258 base::JSONValueConverter<ResourceLink>* converter) { | |
259 converter->RegisterCustomField<ResourceLink::ResourceLinkType>( | |
260 kRelField, &ResourceLink::type_, &ResourceLink::GetFeedLinkType); | |
261 converter->RegisterCustomField( | |
262 kHrefField, &ResourceLink::href_, &GetGURLFromString); | |
263 } | |
264 | |
265 //////////////////////////////////////////////////////////////////////////////// | |
266 // Category implementation | |
267 | |
268 Category::Category() : type_(CATEGORY_UNKNOWN) { | |
269 } | |
270 | |
271 // Converts category.scheme into CategoryType enum. | |
272 bool Category::GetCategoryTypeFromScheme( | |
273 const base::StringPiece& scheme, Category::CategoryType* result) { | |
274 for (size_t i = 0; i < arraysize(kCategoryTypeMap); i++) { | |
275 if (scheme == kCategoryTypeMap[i].scheme) { | |
276 *result = kCategoryTypeMap[i].type; | |
277 return true; | |
278 } | |
279 } | |
280 DVLOG(1) << "Unknown feed link type for scheme " << scheme; | |
281 return false; | |
282 } | |
283 | |
284 // static | |
285 void Category::RegisterJSONConverter( | |
286 base::JSONValueConverter<Category>* converter) { | |
287 converter->RegisterStringField(kLabelField, &Category::label_); | |
288 converter->RegisterCustomField<Category::CategoryType>( | |
289 kSchemeField, &Category::type_, &Category::GetCategoryTypeFromScheme); | |
290 converter->RegisterStringField(kTermField, &Category::term_); | |
291 } | |
292 | |
293 const Link* CommonMetadata::GetLinkByType(Link::LinkType type) const { | |
294 for (size_t i = 0; i < links_.size(); ++i) { | |
295 if (links_[i]->type() == type) | |
296 return links_[i]; | |
297 } | |
298 return NULL; | |
299 } | |
300 | |
301 //////////////////////////////////////////////////////////////////////////////// | |
302 // Content implementation | |
303 | |
304 Content::Content() { | |
305 } | |
306 | |
307 // static | |
308 void Content::RegisterJSONConverter( | |
309 base::JSONValueConverter<Content>* converter) { | |
310 converter->RegisterCustomField(kSrcField, &Content::url_, &GetGURLFromString); | |
311 converter->RegisterStringField(kTypeField, &Content::mime_type_); | |
312 } | |
313 | |
314 //////////////////////////////////////////////////////////////////////////////// | |
315 // CommonMetadata implementation | |
316 | |
317 CommonMetadata::CommonMetadata() { | |
318 } | |
319 | |
320 CommonMetadata::~CommonMetadata() { | |
321 } | |
322 | |
323 // static | |
324 template<typename CommonMetadataDescendant> | |
325 void CommonMetadata::RegisterJSONConverter( | |
326 base::JSONValueConverter<CommonMetadataDescendant>* converter) { | |
327 converter->RegisterStringField(kETagField, &CommonMetadata::etag_); | |
328 converter->template RegisterRepeatedMessage<Author>( | |
329 kAuthorField, &CommonMetadata::authors_); | |
330 converter->template RegisterRepeatedMessage<Link>( | |
331 kLinkField, &CommonMetadata::links_); | |
332 converter->template RegisterRepeatedMessage<Category>( | |
333 kCategoryField, &CommonMetadata::categories_); | |
334 converter->template RegisterCustomField<base::Time>( | |
335 kUpdatedField, &CommonMetadata::updated_time_, &util::GetTimeFromString); | |
336 } | |
337 | |
338 //////////////////////////////////////////////////////////////////////////////// | 9 //////////////////////////////////////////////////////////////////////////////// |
339 // ResourceEntry implementation | 10 // ResourceEntry implementation |
340 | 11 |
341 ResourceEntry::ResourceEntry() | 12 ResourceEntry::ResourceEntry() |
342 : kind_(ENTRY_KIND_UNKNOWN), | 13 : kind_(ENTRY_KIND_UNKNOWN), |
343 file_size_(0), | 14 deleted_(false) { |
344 deleted_(false), | |
345 removed_(false), | |
346 changestamp_(0), | |
347 image_width_(-1), | |
348 image_height_(-1), | |
349 image_rotation_(-1) { | |
350 } | 15 } |
351 | 16 |
352 ResourceEntry::~ResourceEntry() { | 17 ResourceEntry::~ResourceEntry() { |
353 } | 18 } |
354 | 19 |
355 bool ResourceEntry::HasFieldPresent(const base::Value* value, | |
356 bool* result) { | |
357 *result = (value != NULL); | |
358 return true; | |
359 } | |
360 | |
361 bool ResourceEntry::ParseChangestamp(const base::Value* value, | |
362 int64* result) { | |
363 DCHECK(result); | |
364 if (!value) { | |
365 *result = 0; | |
366 return true; | |
367 } | |
368 | |
369 std::string string_value; | |
370 if (value->GetAsString(&string_value) && | |
371 base::StringToInt64(string_value, result)) | |
372 return true; | |
373 | |
374 return false; | |
375 } | |
376 | |
377 // static | |
378 void ResourceEntry::RegisterJSONConverter( | |
379 base::JSONValueConverter<ResourceEntry>* converter) { | |
380 // Inherit the parent registrations. | |
381 CommonMetadata::RegisterJSONConverter(converter); | |
382 converter->RegisterStringField( | |
383 kResourceIdField, &ResourceEntry::resource_id_); | |
384 converter->RegisterStringField(kIDField, &ResourceEntry::id_); | |
385 converter->RegisterStringField(kTitleTField, &ResourceEntry::title_); | |
386 converter->RegisterCustomField<base::Time>( | |
387 kPublishedField, &ResourceEntry::published_time_, | |
388 &util::GetTimeFromString); | |
389 converter->RegisterCustomField<base::Time>( | |
390 kLastViewedField, &ResourceEntry::last_viewed_time_, | |
391 &util::GetTimeFromString); | |
392 converter->RegisterRepeatedMessage( | |
393 kFeedLinkField, &ResourceEntry::resource_links_); | |
394 converter->RegisterNestedField(kContentField, &ResourceEntry::content_); | |
395 | |
396 // File properties. If the resource type is not a normal file, then | |
397 // that's no problem because those feed must not have these fields | |
398 // themselves, which does not report errors. | |
399 converter->RegisterStringField(kFileNameField, &ResourceEntry::filename_); | |
400 converter->RegisterStringField(kMD5Field, &ResourceEntry::file_md5_); | |
401 converter->RegisterCustomField<int64>( | |
402 kSizeField, &ResourceEntry::file_size_, &base::StringToInt64); | |
403 converter->RegisterStringField( | |
404 kSuggestedFileNameField, &ResourceEntry::suggested_filename_); | |
405 // Deleted are treated as 'trashed' items on web client side. Removed files | |
406 // are gone for good. We treat both cases as 'deleted' for this client. | |
407 converter->RegisterCustomValueField<bool>( | |
408 kDeletedField, &ResourceEntry::deleted_, &ResourceEntry::HasFieldPresent); | |
409 converter->RegisterCustomValueField<bool>( | |
410 kRemovedField, &ResourceEntry::removed_, &ResourceEntry::HasFieldPresent); | |
411 converter->RegisterCustomValueField<int64>( | |
412 kChangestampField, &ResourceEntry::changestamp_, | |
413 &ResourceEntry::ParseChangestamp); | |
414 // ImageMediaMetadata fields are not supported by WAPI. | |
415 } | |
416 | |
417 // static | |
418 ResourceEntry::ResourceEntryKind ResourceEntry::GetEntryKindFromTerm( | |
419 const std::string& term) { | |
420 if (!StartsWithASCII(term, kTermPrefix, false)) { | |
421 DVLOG(1) << "Unexpected term prefix term " << term; | |
422 return ENTRY_KIND_UNKNOWN; | |
423 } | |
424 | |
425 std::string type = term.substr(strlen(kTermPrefix)); | |
426 if (type == "folder") | |
427 return ENTRY_KIND_FOLDER; | |
428 if (type == "file" || type == "pdf") | |
429 return ENTRY_KIND_FILE; | |
430 | |
431 DVLOG(1) << "Unknown entry type for term " << term << ", type " << type; | |
432 return ENTRY_KIND_UNKNOWN; | |
433 } | |
434 | |
435 void ResourceEntry::FillRemainingFields() { | |
436 // Set |kind_| and |labels_| based on the |categories_| in the class. | |
437 // JSONValueConverter does not have the ability to catch an element in a list | |
438 // based on a predicate. Thus we need to iterate over |categories_| and | |
439 // find the elements to set these fields as a post-process. | |
440 for (size_t i = 0; i < categories_.size(); ++i) { | |
441 const Category* category = categories_[i]; | |
442 if (category->type() == Category::CATEGORY_KIND) | |
443 kind_ = GetEntryKindFromTerm(category->term()); | |
444 else if (category->type() == Category::CATEGORY_LABEL) | |
445 labels_.push_back(category->label()); | |
446 } | |
447 } | |
448 | |
449 // static | |
450 scoped_ptr<ResourceEntry> ResourceEntry::ExtractAndParse( | |
451 const base::Value& value) { | |
452 const base::DictionaryValue* as_dict = NULL; | |
453 const base::DictionaryValue* entry_dict = NULL; | |
454 if (value.GetAsDictionary(&as_dict) && | |
455 as_dict->GetDictionary(kEntryField, &entry_dict)) { | |
456 return ResourceEntry::CreateFrom(*entry_dict); | |
457 } | |
458 return scoped_ptr<ResourceEntry>(); | |
459 } | |
460 | |
461 // static | |
462 scoped_ptr<ResourceEntry> ResourceEntry::CreateFrom(const base::Value& value) { | |
463 base::JSONValueConverter<ResourceEntry> converter; | |
464 scoped_ptr<ResourceEntry> entry(new ResourceEntry()); | |
465 if (!converter.Convert(value, entry.get())) { | |
466 DVLOG(1) << "Invalid resource entry!"; | |
467 return scoped_ptr<ResourceEntry>(); | |
468 } | |
469 | |
470 entry->FillRemainingFields(); | |
471 return entry.Pass(); | |
472 } | |
473 | |
474 // static | |
475 std::string ResourceEntry::GetEntryNodeName() { | |
476 return kEntryNode; | |
477 } | |
478 | |
479 //////////////////////////////////////////////////////////////////////////////// | |
480 // ResourceList implementation | |
481 | |
482 ResourceList::ResourceList() | |
483 : start_index_(0), | |
484 items_per_page_(0), | |
485 largest_changestamp_(0) { | |
486 } | |
487 | |
488 ResourceList::~ResourceList() { | |
489 } | |
490 | |
491 // static | |
492 void ResourceList::RegisterJSONConverter( | |
493 base::JSONValueConverter<ResourceList>* converter) { | |
494 // inheritance | |
495 CommonMetadata::RegisterJSONConverter(converter); | |
496 // TODO(zelidrag): Once we figure out where these will be used, we should | |
497 // check for valid start_index_ and items_per_page_ values. | |
498 converter->RegisterCustomField<int>( | |
499 kStartIndexField, &ResourceList::start_index_, &base::StringToInt); | |
500 converter->RegisterCustomField<int>( | |
501 kItemsPerPageField, &ResourceList::items_per_page_, &base::StringToInt); | |
502 converter->RegisterStringField(kTitleTField, &ResourceList::title_); | |
503 converter->RegisterRepeatedMessage(kEntryField, &ResourceList::entries_); | |
504 converter->RegisterCustomField<int64>( | |
505 kLargestChangestampField, &ResourceList::largest_changestamp_, | |
506 &base::StringToInt64); | |
507 } | |
508 | |
509 bool ResourceList::Parse(const base::Value& value) { | |
510 base::JSONValueConverter<ResourceList> converter; | |
511 if (!converter.Convert(value, this)) { | |
512 DVLOG(1) << "Invalid resource list!"; | |
513 return false; | |
514 } | |
515 | |
516 ScopedVector<ResourceEntry>::iterator iter = entries_.begin(); | |
517 while (iter != entries_.end()) { | |
518 ResourceEntry* entry = (*iter); | |
519 entry->FillRemainingFields(); | |
520 ++iter; | |
521 } | |
522 return true; | |
523 } | |
524 | |
525 // static | |
526 scoped_ptr<ResourceList> ResourceList::ExtractAndParse( | |
527 const base::Value& value) { | |
528 const base::DictionaryValue* as_dict = NULL; | |
529 const base::DictionaryValue* feed_dict = NULL; | |
530 if (value.GetAsDictionary(&as_dict) && | |
531 as_dict->GetDictionary(kFeedField, &feed_dict)) { | |
532 return ResourceList::CreateFrom(*feed_dict); | |
533 } | |
534 return scoped_ptr<ResourceList>(); | |
535 } | |
536 | |
537 // static | |
538 scoped_ptr<ResourceList> ResourceList::CreateFrom(const base::Value& value) { | |
539 scoped_ptr<ResourceList> feed(new ResourceList()); | |
540 if (!feed->Parse(value)) { | |
541 DVLOG(1) << "Invalid resource list!"; | |
542 return scoped_ptr<ResourceList>(); | |
543 } | |
544 | |
545 return feed.Pass(); | |
546 } | |
547 | |
548 bool ResourceList::GetNextFeedURL(GURL* url) const { | |
549 DCHECK(url); | |
550 for (size_t i = 0; i < links_.size(); ++i) { | |
551 if (links_[i]->type() == Link::LINK_NEXT) { | |
552 *url = links_[i]->href(); | |
553 return true; | |
554 } | |
555 } | |
556 return false; | |
557 } | |
558 | |
559 void ResourceList::ReleaseEntries(std::vector<ResourceEntry*>* entries) { | |
560 entries_.release(entries); | |
561 } | |
562 | |
563 } // namespace google_apis | 20 } // namespace google_apis |
OLD | NEW |