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;
78 const char* extension;
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},
96 COMPILE_ASSERT(arraysize(kEntryKindMap) == ENTRY_KIND_MAX_VALUE,
97 EntryKindMap_and_DriveEntryKind_are_not_in_sync);
104 const LinkTypeMap kLinkTypeMap[] = {
110 "http://schemas.google.com/docs/2007#parent" },
111 { Link::LINK_ALTERNATE,
115 { Link::LINK_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" },
122 "http://schemas.google.com/g/2005#feed"},
124 "http://schemas.google.com/g/2005#post"},
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"},
138 "http://schemas.google.com/docs/2007#embed"},
139 { Link::LINK_PRODUCT,
140 "http://schemas.google.com/docs/2007#product"},
142 "http://schemas.google.com/docs/2007#icon"},
144 "http://schemas.google.com/docs/2007#share"},
147 struct ResourceLinkTypeMap {
148 ResourceLink::ResourceLinkType type;
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" },
159 struct CategoryTypeMap {
160 Category::CategoryType type;
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" },
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());
179 ////////////////////////////////////////////////////////////////////////////////
180 // Author implementation
186 void Author::RegisterJSONConverter(
187 base::JSONValueConverter<Author>* converter) {
188 converter->RegisterStringField(kNameField, &Author::name_);
189 converter->RegisterStringField(kEmailField, &Author::email_);
192 ////////////////////////////////////////////////////////////////////////////////
193 // Link implementation
195 Link::Link() : type_(Link::LINK_UNKNOWN) {
202 bool Link::GetAppID(const base::StringPiece& rel, std::string* app_id) {
204 // Fast return path if the link clearly isn't an OPEN_WITH link.
205 if (rel.size() < kOpenWithPrefixSize) {
210 const std::string kOpenWithPrefixStr(kOpenWithPrefix);
211 if (StartsWithASCII(rel.as_string(), kOpenWithPrefixStr, false)) {
212 *app_id = rel.as_string().substr(kOpenWithPrefixStr.size());
221 bool Link::GetLinkType(const base::StringPiece& rel, Link::LinkType* type) {
223 for (size_t i = 0; i < arraysize(kLinkTypeMap); i++) {
224 if (rel == kLinkTypeMap[i].rel) {
225 *type = kLinkTypeMap[i].type;
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;
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;
247 void Link::RegisterJSONConverter(base::JSONValueConverter<Link>* converter) {
248 converter->RegisterCustomField<Link::LinkType>(kRelField,
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,
256 converter->RegisterCustomField(kHrefField, &Link::href_, &GetGURLFromString);
257 converter->RegisterStringField(kTitleField, &Link::title_);
258 converter->RegisterStringField(kTypeField, &Link::mime_type_);
261 ////////////////////////////////////////////////////////////////////////////////
262 // ResourceLink implementation
264 ResourceLink::ResourceLink() : type_(ResourceLink::FEED_LINK_UNKNOWN) {
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;
276 DVLOG(1) << "Unknown feed link type for rel " << rel;
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);
289 ////////////////////////////////////////////////////////////////////////////////
290 // Category implementation
292 Category::Category() : type_(CATEGORY_UNKNOWN) {
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;
304 DVLOG(1) << "Unknown feed link type for scheme " << scheme;
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_);
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)
325 ////////////////////////////////////////////////////////////////////////////////
326 // Content implementation
332 void Content::RegisterJSONConverter(
333 base::JSONValueConverter<Content>* converter) {
334 converter->RegisterCustomField(kSrcField, &Content::url_, &GetGURLFromString);
335 converter->RegisterStringField(kTypeField, &Content::mime_type_);
338 ////////////////////////////////////////////////////////////////////////////////
339 // CommonMetadata implementation
341 CommonMetadata::CommonMetadata() {
344 CommonMetadata::~CommonMetadata() {
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);
362 ////////////////////////////////////////////////////////////////////////////////
363 // ResourceEntry implementation
365 ResourceEntry::ResourceEntry()
366 : kind_(ENTRY_KIND_UNKNOWN),
373 image_rotation_(-1) {
376 ResourceEntry::~ResourceEntry() {
379 bool ResourceEntry::HasFieldPresent(const base::Value* value,
381 *result = (value != NULL);
385 bool ResourceEntry::ParseChangestamp(const base::Value* value,
393 std::string string_value;
394 if (value->GetAsString(&string_value) &&
395 base::StringToInt64(string_value, result))
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_);
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.
441 std::string ResourceEntry::GetHostedDocumentExtension() const {
442 for (size_t i = 0; i < arraysize(kEntryKindMap); i++) {
443 if (kEntryKindMap[i].kind == kind_) {
444 if (kEntryKindMap[i].extension)
445 return std::string(kEntryKindMap[i].extension);
447 return std::string();
450 return std::string();
454 DriveEntryKind ResourceEntry::GetEntryKindFromExtension(
455 const std::string& extension) {
456 for (size_t i = 0; i < arraysize(kEntryKindMap); ++i) {
457 const char* document_extension = kEntryKindMap[i].extension;
458 if (document_extension && extension == document_extension)
459 return kEntryKindMap[i].kind;
461 return ENTRY_KIND_UNKNOWN;
465 int ResourceEntry::ClassifyEntryKindByFileExtension(
466 const base::FilePath& file_path) {
468 std::string file_extension = base::WideToUTF8(file_path.Extension());
470 std::string file_extension = file_path.Extension();
472 return ClassifyEntryKind(GetEntryKindFromExtension(file_extension));
476 DriveEntryKind ResourceEntry::GetEntryKindFromTerm(
477 const std::string& term) {
478 if (!StartsWithASCII(term, kTermPrefix, false)) {
479 DVLOG(1) << "Unexpected term prefix term " << term;
480 return ENTRY_KIND_UNKNOWN;
483 std::string type = term.substr(strlen(kTermPrefix));
484 for (size_t i = 0; i < arraysize(kEntryKindMap); i++) {
485 if (type == kEntryKindMap[i].entry)
486 return kEntryKindMap[i].kind;
488 DVLOG(1) << "Unknown entry type for term " << term << ", type " << type;
489 return ENTRY_KIND_UNKNOWN;
493 int ResourceEntry::ClassifyEntryKind(DriveEntryKind kind) {
496 // All DriveEntryKind members are listed here, so the compiler catches if a
497 // newly added member is missing here.
499 case ENTRY_KIND_UNKNOWN:
501 case ENTRY_KIND_ITEM:
502 case ENTRY_KIND_SITE:
505 // Hosted Google document.
506 case ENTRY_KIND_DOCUMENT:
507 case ENTRY_KIND_SPREADSHEET:
508 case ENTRY_KIND_PRESENTATION:
509 case ENTRY_KIND_DRAWING:
510 case ENTRY_KIND_TABLE:
511 case ENTRY_KIND_FORM:
512 classes = KIND_OF_GOOGLE_DOCUMENT | KIND_OF_HOSTED_DOCUMENT;
515 // Hosted external application document.
516 case ENTRY_KIND_EXTERNAL_APP:
517 classes = KIND_OF_EXTERNAL_DOCUMENT | KIND_OF_HOSTED_DOCUMENT;
520 // Folders, collections.
521 case ENTRY_KIND_FOLDER:
522 classes = KIND_OF_FOLDER;
526 case ENTRY_KIND_FILE:
528 classes = KIND_OF_FILE;
531 case ENTRY_KIND_MAX_VALUE:
538 void ResourceEntry::FillRemainingFields() {
539 // Set |kind_| and |labels_| based on the |categories_| in the class.
540 // JSONValueConverter does not have the ability to catch an element in a list
541 // based on a predicate. Thus we need to iterate over |categories_| and
542 // find the elements to set these fields as a post-process.
543 for (size_t i = 0; i < categories_.size(); ++i) {
544 const Category* category = categories_[i];
545 if (category->type() == Category::CATEGORY_KIND)
546 kind_ = GetEntryKindFromTerm(category->term());
547 else if (category->type() == Category::CATEGORY_LABEL)
548 labels_.push_back(category->label());
553 scoped_ptr<ResourceEntry> ResourceEntry::ExtractAndParse(
554 const base::Value& value) {
555 const base::DictionaryValue* as_dict = NULL;
556 const base::DictionaryValue* entry_dict = NULL;
557 if (value.GetAsDictionary(&as_dict) &&
558 as_dict->GetDictionary(kEntryField, &entry_dict)) {
559 return ResourceEntry::CreateFrom(*entry_dict);
561 return scoped_ptr<ResourceEntry>();
565 scoped_ptr<ResourceEntry> ResourceEntry::CreateFrom(const base::Value& value) {
566 base::JSONValueConverter<ResourceEntry> converter;
567 scoped_ptr<ResourceEntry> entry(new ResourceEntry());
568 if (!converter.Convert(value, entry.get())) {
569 DVLOG(1) << "Invalid resource entry!";
570 return scoped_ptr<ResourceEntry>();
573 entry->FillRemainingFields();
578 std::string ResourceEntry::GetEntryNodeName() {
582 ////////////////////////////////////////////////////////////////////////////////
583 // ResourceList implementation
585 ResourceList::ResourceList()
588 largest_changestamp_(0) {
591 ResourceList::~ResourceList() {
595 void ResourceList::RegisterJSONConverter(
596 base::JSONValueConverter<ResourceList>* converter) {
598 CommonMetadata::RegisterJSONConverter(converter);
599 // TODO(zelidrag): Once we figure out where these will be used, we should
600 // check for valid start_index_ and items_per_page_ values.
601 converter->RegisterCustomField<int>(
602 kStartIndexField, &ResourceList::start_index_, &base::StringToInt);
603 converter->RegisterCustomField<int>(
604 kItemsPerPageField, &ResourceList::items_per_page_, &base::StringToInt);
605 converter->RegisterStringField(kTitleTField, &ResourceList::title_);
606 converter->RegisterRepeatedMessage(kEntryField, &ResourceList::entries_);
607 converter->RegisterCustomField<int64>(
608 kLargestChangestampField, &ResourceList::largest_changestamp_,
609 &base::StringToInt64);
612 bool ResourceList::Parse(const base::Value& value) {
613 base::JSONValueConverter<ResourceList> converter;
614 if (!converter.Convert(value, this)) {
615 DVLOG(1) << "Invalid resource list!";
619 ScopedVector<ResourceEntry>::iterator iter = entries_.begin();
620 while (iter != entries_.end()) {
621 ResourceEntry* entry = (*iter);
622 entry->FillRemainingFields();
629 scoped_ptr<ResourceList> ResourceList::ExtractAndParse(
630 const base::Value& value) {
631 const base::DictionaryValue* as_dict = NULL;
632 const base::DictionaryValue* feed_dict = NULL;
633 if (value.GetAsDictionary(&as_dict) &&
634 as_dict->GetDictionary(kFeedField, &feed_dict)) {
635 return ResourceList::CreateFrom(*feed_dict);
637 return scoped_ptr<ResourceList>();
641 scoped_ptr<ResourceList> ResourceList::CreateFrom(const base::Value& value) {
642 scoped_ptr<ResourceList> feed(new ResourceList());
643 if (!feed->Parse(value)) {
644 DVLOG(1) << "Invalid resource list!";
645 return scoped_ptr<ResourceList>();
651 bool ResourceList::GetNextFeedURL(GURL* url) const {
653 for (size_t i = 0; i < links_.size(); ++i) {
654 if (links_[i]->type() == Link::LINK_NEXT) {
655 *url = links_[i]->href();
662 void ResourceList::ReleaseEntries(std::vector<ResourceEntry*>* entries) {
663 entries_.release(entries);
666 } // namespace google_apis