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.
5 #include "google_apis/drive/gdata_wapi_parser.h"
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"
22 using base::DictionaryValue
;
23 using base::ListValue
;
25 namespace google_apis
{
29 // Term values for kSchemeKind category:
30 const char kTermPrefix
[] = "http://schemas.google.com/docs/2007#";
33 const char kEntryNode
[] = "entry";
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";
72 const char kOpenWithPrefix
[] = "http://schemas.google.com/docs/2007#open-with-";
73 const size_t kOpenWithPrefixSize
= arraysize(kOpenWithPrefix
) - 1;
80 const LinkTypeMap kLinkTypeMap
[] = {
86 "http://schemas.google.com/docs/2007#parent" },
87 { Link::LINK_ALTERNATE
,
91 { Link::LINK_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" },
98 "http://schemas.google.com/g/2005#feed"},
100 "http://schemas.google.com/g/2005#post"},
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"},
114 "http://schemas.google.com/docs/2007#embed"},
115 { Link::LINK_PRODUCT
,
116 "http://schemas.google.com/docs/2007#product"},
118 "http://schemas.google.com/docs/2007#icon"},
120 "http://schemas.google.com/docs/2007#share"},
123 struct ResourceLinkTypeMap
{
124 ResourceLink::ResourceLinkType type
;
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" },
135 struct CategoryTypeMap
{
136 Category::CategoryType type
;
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" },
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());
155 ////////////////////////////////////////////////////////////////////////////////
156 // Author implementation
162 void Author::RegisterJSONConverter(
163 base::JSONValueConverter
<Author
>* converter
) {
164 converter
->RegisterStringField(kNameField
, &Author::name_
);
165 converter
->RegisterStringField(kEmailField
, &Author::email_
);
168 ////////////////////////////////////////////////////////////////////////////////
169 // Link implementation
171 Link::Link() : type_(Link::LINK_UNKNOWN
) {
178 bool Link::GetAppID(const base::StringPiece
& rel
, std::string
* app_id
) {
180 // Fast return path if the link clearly isn't an OPEN_WITH link.
181 if (rel
.size() < kOpenWithPrefixSize
) {
186 const std::string
kOpenWithPrefixStr(kOpenWithPrefix
);
187 if (StartsWithASCII(rel
.as_string(), kOpenWithPrefixStr
, false)) {
188 *app_id
= rel
.as_string().substr(kOpenWithPrefixStr
.size());
197 bool Link::GetLinkType(const base::StringPiece
& rel
, Link::LinkType
* type
) {
199 for (size_t i
= 0; i
< arraysize(kLinkTypeMap
); i
++) {
200 if (rel
== kLinkTypeMap
[i
].rel
) {
201 *type
= kLinkTypeMap
[i
].type
;
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
;
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
;
223 void Link::RegisterJSONConverter(base::JSONValueConverter
<Link
>* converter
) {
224 converter
->RegisterCustomField
<Link::LinkType
>(kRelField
,
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
,
232 converter
->RegisterCustomField(kHrefField
, &Link::href_
, &GetGURLFromString
);
233 converter
->RegisterStringField(kTitleField
, &Link::title_
);
234 converter
->RegisterStringField(kTypeField
, &Link::mime_type_
);
237 ////////////////////////////////////////////////////////////////////////////////
238 // ResourceLink implementation
240 ResourceLink::ResourceLink() : type_(ResourceLink::FEED_LINK_UNKNOWN
) {
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
;
252 DVLOG(1) << "Unknown feed link type for rel " << rel
;
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
);
265 ////////////////////////////////////////////////////////////////////////////////
266 // Category implementation
268 Category::Category() : type_(CATEGORY_UNKNOWN
) {
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
;
280 DVLOG(1) << "Unknown feed link type for scheme " << scheme
;
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_
);
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
)
301 ////////////////////////////////////////////////////////////////////////////////
302 // Content implementation
308 void Content::RegisterJSONConverter(
309 base::JSONValueConverter
<Content
>* converter
) {
310 converter
->RegisterCustomField(kSrcField
, &Content::url_
, &GetGURLFromString
);
311 converter
->RegisterStringField(kTypeField
, &Content::mime_type_
);
314 ////////////////////////////////////////////////////////////////////////////////
315 // CommonMetadata implementation
317 CommonMetadata::CommonMetadata() {
320 CommonMetadata::~CommonMetadata() {
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
);
338 ////////////////////////////////////////////////////////////////////////////////
339 // ResourceEntry implementation
341 ResourceEntry::ResourceEntry()
342 : kind_(ENTRY_KIND_UNKNOWN
),
349 image_rotation_(-1) {
352 ResourceEntry::~ResourceEntry() {
355 bool ResourceEntry::HasFieldPresent(const base::Value
* value
,
357 *result
= (value
!= NULL
);
361 bool ResourceEntry::ParseChangestamp(const base::Value
* value
,
369 std::string string_value
;
370 if (value
->GetAsString(&string_value
) &&
371 base::StringToInt64(string_value
, result
))
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_
);
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.
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
;
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
;
431 DVLOG(1) << "Unknown entry type for term " << term
<< ", type " << type
;
432 return ENTRY_KIND_UNKNOWN
;
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());
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
);
458 return scoped_ptr
<ResourceEntry
>();
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
>();
470 entry
->FillRemainingFields();
475 std::string
ResourceEntry::GetEntryNodeName() {
479 ////////////////////////////////////////////////////////////////////////////////
480 // ResourceList implementation
482 ResourceList::ResourceList()
485 largest_changestamp_(0) {
488 ResourceList::~ResourceList() {
492 void ResourceList::RegisterJSONConverter(
493 base::JSONValueConverter
<ResourceList
>* converter
) {
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
);
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!";
516 ScopedVector
<ResourceEntry
>::iterator iter
= entries_
.begin();
517 while (iter
!= entries_
.end()) {
518 ResourceEntry
* entry
= (*iter
);
519 entry
->FillRemainingFields();
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
);
534 return scoped_ptr
<ResourceList
>();
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
>();
548 bool ResourceList::GetNextFeedURL(GURL
* url
) const {
550 for (size_t i
= 0; i
< links_
.size(); ++i
) {
551 if (links_
[i
]->type() == Link::LINK_NEXT
) {
552 *url
= links_
[i
]->href();
559 void ResourceList::ReleaseEntries(std::vector
<ResourceEntry
*>* entries
) {
560 entries_
.release(entries
);
563 } // namespace google_apis