| 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 "chrome/browser/chromeos/gdata/gdata_parser.h" | 5 #include "chrome/browser/chromeos/gdata/gdata_parser.h" |
| 6 | 6 |
| 7 #include "base/basictypes.h" | 7 #include "base/basictypes.h" |
| 8 #include "base/scoped_ptr.h" | 8 #include "base/json/json_value_converter.h" |
| 9 #include "base/memory/scoped_ptr.h" |
| 9 #include "base/string_number_conversions.h" | 10 #include "base/string_number_conversions.h" |
| 11 #include "base/string_piece.h" |
| 10 #include "base/string_util.h" | 12 #include "base/string_util.h" |
| 11 #include "base/values.h" | 13 #include "base/values.h" |
| 12 | 14 |
| 13 using base::Value; | 15 using base::Value; |
| 14 using base::DictionaryValue; | 16 using base::DictionaryValue; |
| 15 using base::ListValue; | 17 using base::ListValue; |
| 16 | 18 |
| 17 namespace gdata { | 19 namespace gdata { |
| 18 | 20 |
| 19 namespace { | 21 namespace { |
| 20 | 22 |
| 21 // Term values for kSchemeKind category: | 23 // Term values for kSchemeKind category: |
| 22 const char kSchemeKind[] = "http://schemas.google.com/g/2005#kind"; | 24 const char kSchemeKind[] = "http://schemas.google.com/g/2005#kind"; |
| 23 const char kTermPrefix[] = "http://schemas.google.com/docs/2007#"; | 25 const char kTermPrefix[] = "http://schemas.google.com/docs/2007#"; |
| 24 const char kFileTerm[] = "file"; | 26 const char kFileTerm[] = "file"; |
| 25 const char kFolderTerm[] = "folder"; | 27 const char kFolderTerm[] = "folder"; |
| 26 const char kItemTerm[] = "item"; | 28 const char kItemTerm[] = "item"; |
| 27 const char kPdfTerm[] = "pdf"; | 29 const char kPdfTerm[] = "pdf"; |
| 28 const char kDocumentTerm[] = "document"; | 30 const char kDocumentTerm[] = "document"; |
| 29 const char kSpreadSheetTerm[] = "spreadsheet"; | 31 const char kSpreadSheetTerm[] = "spreadsheet"; |
| 30 const char kPresentationTerm[] = "presentation"; | 32 const char kPresentationTerm[] = "presentation"; |
| 31 | 33 |
| 32 const char kSchemeLabels[] = "http://schemas.google.com/g/2005/labels"; | 34 const char kSchemeLabels[] = "http://schemas.google.com/g/2005/labels"; |
| 33 | 35 |
| 34 const struct { | 36 struct EntryKindMap { |
| 35 DocumentEntry::EntryKind kind; | 37 DocumentEntry::EntryKind kind; |
| 36 const char* entry; | 38 const char* entry; |
| 37 } kEntryKindMap[] = { | 39 }; |
| 40 |
| 41 const EntryKindMap kEntryKindMap[] = { |
| 38 { DocumentEntry::ITEM, "item" }, | 42 { DocumentEntry::ITEM, "item" }, |
| 39 { DocumentEntry::DOCUMENT, "document"}, | 43 { DocumentEntry::DOCUMENT, "document"}, |
| 40 { DocumentEntry::SPREADSHEET, "spreadsheet" }, | 44 { DocumentEntry::SPREADSHEET, "spreadsheet" }, |
| 41 { DocumentEntry::PRESENTATION, "presentation" }, | 45 { DocumentEntry::PRESENTATION, "presentation" }, |
| 42 { DocumentEntry::FOLDER, "folder"}, | 46 { DocumentEntry::FOLDER, "folder"}, |
| 43 { DocumentEntry::FILE, "file"}, | 47 { DocumentEntry::FILE, "file"}, |
| 44 { DocumentEntry::PDF, "pdf"}, | 48 { DocumentEntry::PDF, "pdf"}, |
| 45 { DocumentEntry::UNKNOWN, NULL} | |
| 46 }; | 49 }; |
| 47 | 50 |
| 48 const struct { | 51 struct LinkTypeMap { |
| 49 Link::LinkType type; | 52 Link::LinkType type; |
| 50 const char* rel; | 53 const char* rel; |
| 51 } kLinkTypeMap[] = { | 54 }; |
| 55 |
| 56 const LinkTypeMap kLinkTypeMap[] = { |
| 52 { Link::SELF, | 57 { Link::SELF, |
| 53 "self" }, | 58 "self" }, |
| 54 { Link::NEXT, | 59 { Link::NEXT, |
| 55 "next" }, | 60 "next" }, |
| 56 { Link::PARENT, | 61 { Link::PARENT, |
| 57 "http://schemas.google.com/docs/2007#parent" }, | 62 "http://schemas.google.com/docs/2007#parent" }, |
| 58 { Link::ALTERNATE, | 63 { Link::ALTERNATE, |
| 59 "alternate"}, | 64 "alternate"}, |
| 60 { Link::EDIT, | 65 { Link::EDIT, |
| 61 "edit" }, | 66 "edit" }, |
| 62 { Link::EDIT_MEDIA, | 67 { Link::EDIT_MEDIA, |
| 63 "edit-media" }, | 68 "edit-media" }, |
| 64 { Link::FEED, | 69 { Link::FEED, |
| 65 "http://schemas.google.com/g/2005#feed"}, | 70 "http://schemas.google.com/g/2005#feed"}, |
| 66 { Link::POST, | 71 { Link::POST, |
| 67 "http://schemas.google.com/g/2005#post"}, | 72 "http://schemas.google.com/g/2005#post"}, |
| 68 { Link::BATCH, | 73 { Link::BATCH, |
| 69 "http://schemas.google.com/g/2005#batch"}, | 74 "http://schemas.google.com/g/2005#batch"}, |
| 70 { Link::THUMBNAIL, | 75 { Link::THUMBNAIL, |
| 71 "http://schemas.google.com/docs/2007/thumbnail"}, | 76 "http://schemas.google.com/docs/2007/thumbnail"}, |
| 72 { Link::RESUMABLE_EDIT_MEDIA, | 77 { Link::RESUMABLE_EDIT_MEDIA, |
| 73 "http://schemas.google.com/g/2005#resumable-edit-media"}, | 78 "http://schemas.google.com/g/2005#resumable-edit-media"}, |
| 74 { Link::RESUMABLE_CREATE_MEDIA, | 79 { Link::RESUMABLE_CREATE_MEDIA, |
| 75 "http://schemas.google.com/g/2005#resumable-create-media"}, | 80 "http://schemas.google.com/g/2005#resumable-create-media"}, |
| 76 { Link::TABLES_FEED, | 81 { Link::TABLES_FEED, |
| 77 "http://schemas.google.com/spreadsheets/2006#tablesfeed"}, | 82 "http://schemas.google.com/spreadsheets/2006#tablesfeed"}, |
| 78 { Link::WORKSHEET_FEED, | 83 { Link::WORKSHEET_FEED, |
| 79 "http://schemas.google.com/spreadsheets/2006#worksheetsfeed"}, | 84 "http://schemas.google.com/spreadsheets/2006#worksheetsfeed"}, |
| 80 { Link::UNKNOWN, | |
| 81 NULL} | |
| 82 }; | 85 }; |
| 83 | 86 |
| 84 const struct { | 87 struct FeedLinkTypeMap { |
| 85 FeedLink::FeedLinkType type; | 88 FeedLink::FeedLinkType type; |
| 86 const char* rel; | 89 const char* rel; |
| 87 } kFeedLinkTypeMap[] = { | 90 }; |
| 91 |
| 92 const FeedLinkTypeMap kFeedLinkTypeMap[] = { |
| 88 { FeedLink::ACL, | 93 { FeedLink::ACL, |
| 89 "http://schemas.google.com/acl/2007#accessControlList" }, | 94 "http://schemas.google.com/acl/2007#accessControlList" }, |
| 90 { FeedLink::REVISIONS, | 95 { FeedLink::REVISIONS, |
| 91 "http://schemas.google.com/docs/2007/revisions" }, | 96 "http://schemas.google.com/docs/2007/revisions" }, |
| 92 { FeedLink::UNKNOWN, | |
| 93 NULL} | |
| 94 }; | 97 }; |
| 95 | 98 |
| 96 const struct { | 99 struct CategoryTypeMap { |
| 97 Category::CategoryType type; | 100 Category::CategoryType type; |
| 98 const char* scheme; | 101 const char* scheme; |
| 99 } kCategoryTypeMap[] = { | 102 }; |
| 103 |
| 104 const CategoryTypeMap kCategoryTypeMap[] = { |
| 100 { Category::KIND, | 105 { Category::KIND, |
| 101 "http://schemas.google.com/g/2005#kind" }, | 106 "http://schemas.google.com/g/2005#kind" }, |
| 102 { Category::LABEL, | 107 { Category::LABEL, |
| 103 "http://schemas.google.com/g/2005/labels" }, | 108 "http://schemas.google.com/g/2005/labels" }, |
| 104 { Category::UNKNOWN, | |
| 105 NULL} | |
| 106 }; | 109 }; |
| 107 | 110 |
| 111 // Converts |url_string| to |result|. Always returns true to be used |
| 112 // for JSONValueConverter::RegisterCustomField method. |
| 113 // TODO(mukai): make it return false in case of invalid |url_string|. |
| 114 bool GetGURLFromString(const base::StringPiece& url_string, GURL* result) { |
| 115 *result = GURL(url_string.as_string()); |
| 116 return true; |
| 117 } |
| 118 |
| 108 } // namespace | 119 } // namespace |
| 109 | 120 |
| 110 const char Author::kNameField[] = "name.$t"; | 121 const char Author::kNameField[] = "name.$t"; |
| 111 const char Author::kEmailField[] = "email.$t"; | 122 const char Author::kEmailField[] = "email.$t"; |
| 112 | 123 |
| 113 Author::Author() { | 124 Author::Author() { |
| 114 } | 125 } |
| 115 | 126 |
| 116 bool Author::Parse(const DictionaryValue* dictionary) { | 127 // static |
| 117 if (dictionary->GetString(kNameField, &name_) && | 128 void Author::RegisterJSONConverter( |
| 118 dictionary->GetString(kEmailField, &email_)) { | 129 base::JSONValueConverter<Author>* converter) { |
| 119 return true; | 130 converter->RegisterStringField(kNameField, &Author::name_); |
| 120 } | 131 converter->RegisterStringField(kEmailField, &Author::email_); |
| 121 return false; | |
| 122 } | 132 } |
| 123 | 133 |
| 124 const char Link::kHrefField[] = "href"; | 134 const char Link::kHrefField[] = "href"; |
| 125 const char Link::kRelField[] = "rel"; | 135 const char Link::kRelField[] = "rel"; |
| 126 const char Link::kTitleField[] = "title"; | 136 const char Link::kTitleField[] = "title"; |
| 127 const char Link::kTypeField[] = "type"; | 137 const char Link::kTypeField[] = "type"; |
| 128 | 138 |
| 129 Link::Link() : type_(Link::UNKNOWN) { | 139 Link::Link() : type_(Link::UNKNOWN) { |
| 130 } | 140 } |
| 131 | 141 |
| 132 bool Link::Parse(const DictionaryValue* dictionary) { | 142 // static. |
| 133 std::string rel; | 143 bool Link::GetLinkType(const base::StringPiece& rel, Link::LinkType* result) { |
| 134 std::string href; | 144 for (size_t i = 0; i < arraysize(kLinkTypeMap); i++) { |
| 135 if (dictionary->GetString(kRelField, &rel) && | 145 if (rel == kLinkTypeMap[i].rel) { |
| 136 dictionary->GetString(kHrefField, &href) && | 146 *result = kLinkTypeMap[i].type; |
| 137 dictionary->GetString(kTypeField, &mime_type_)) { | |
| 138 href_ = GURL(href); | |
| 139 type_ = GetLinkType(rel); | |
| 140 if (type_ == Link::PARENT) | |
| 141 dictionary->GetString(kTitleField, &title_); | |
| 142 | |
| 143 if (type_ != Link::UNKNOWN) | |
| 144 return true; | 147 return true; |
| 148 } |
| 145 } | 149 } |
| 150 DVLOG(1) << "Unknown link type for rel " << rel; |
| 146 return false; | 151 return false; |
| 147 } | 152 } |
| 148 | 153 |
| 149 // static. | 154 // static |
| 150 Link::LinkType Link::GetLinkType(const std::string& rel) { | 155 void Link::RegisterJSONConverter(base::JSONValueConverter<Link>* converter) { |
| 151 for (size_t i = 0; kLinkTypeMap[i].rel; i++) { | 156 converter->RegisterCustomField<Link::LinkType>( |
| 152 if (rel == kLinkTypeMap[i].rel) | 157 kRelField, &Link::type_, &Link::GetLinkType); |
| 153 return kLinkTypeMap[i].type; | 158 converter->RegisterCustomField(kHrefField, &Link::href_, &GetGURLFromString); |
| 154 } | 159 converter->RegisterStringField(kTitleField, &Link::title_); |
| 155 DVLOG(1) << "Unknown link type for rel " << rel; | 160 converter->RegisterStringField(kTypeField, &Link::mime_type_); |
| 156 return Link::UNKNOWN; | |
| 157 } | 161 } |
| 158 | 162 |
| 159 const char FeedLink::kHrefField[] = "href"; | 163 const char FeedLink::kHrefField[] = "href"; |
| 160 const char FeedLink::kRelField[] = "rel"; | 164 const char FeedLink::kRelField[] = "rel"; |
| 161 | 165 |
| 162 FeedLink::FeedLink() : type_(FeedLink::UNKNOWN) { | 166 FeedLink::FeedLink() : type_(FeedLink::UNKNOWN) { |
| 163 } | 167 } |
| 164 | 168 |
| 165 bool FeedLink::Parse(const DictionaryValue* dictionary) { | 169 // static. |
| 166 std::string rel; | 170 bool FeedLink::GetFeedLinkType( |
| 167 std::string href; | 171 const base::StringPiece& rel, FeedLink::FeedLinkType* result) { |
| 168 if (dictionary->GetString(kRelField, &rel) && | 172 for (size_t i = 0; i < arraysize(kFeedLinkTypeMap); i++) { |
| 169 dictionary->GetString(kHrefField, &href)) { | 173 if (rel == kFeedLinkTypeMap[i].rel) { |
| 170 href_ = GURL(href); | 174 *result = kFeedLinkTypeMap[i].type; |
| 171 type_ = GetFeedLinkType(rel); | |
| 172 if (type_ != FeedLink::UNKNOWN) | |
| 173 return true; | 175 return true; |
| 176 } |
| 174 } | 177 } |
| 178 DVLOG(1) << "Unknown feed link type for rel " << rel; |
| 175 return false; | 179 return false; |
| 176 } | 180 } |
| 177 | 181 |
| 178 // static. | 182 // static |
| 179 FeedLink::FeedLinkType FeedLink::GetFeedLinkType(const std::string& rel) { | 183 void FeedLink::RegisterJSONConverter( |
| 180 for (size_t i = 0; kFeedLinkTypeMap[i].rel; i++) { | 184 base::JSONValueConverter<FeedLink>* converter) { |
| 181 if (rel == kFeedLinkTypeMap[i].rel) | 185 converter->RegisterCustomField<FeedLink::FeedLinkType>( |
| 182 return kFeedLinkTypeMap[i].type; | 186 kRelField, &FeedLink::type_, &FeedLink::GetFeedLinkType); |
| 183 } | 187 converter->RegisterCustomField( |
| 184 DVLOG(1) << "Unknown feed link type for rel " << rel; | 188 kHrefField, &FeedLink::href_, &GetGURLFromString); |
| 185 return FeedLink::UNKNOWN; | |
| 186 } | 189 } |
| 187 | 190 |
| 188 const char Category::kLabelField[] = "label"; | 191 const char Category::kLabelField[] = "label"; |
| 189 const char Category::kSchemeField[] = "scheme"; | 192 const char Category::kSchemeField[] = "scheme"; |
| 190 const char Category::kTermField[] = "term"; | 193 const char Category::kTermField[] = "term"; |
| 191 | 194 |
| 192 Category::Category() { | 195 Category::Category() : type_(UNKNOWN) { |
| 193 } | 196 } |
| 194 | 197 |
| 195 bool Category::Parse(const DictionaryValue* dictionary) { | 198 // Converts category.scheme into CategoryType enum. |
| 196 std::string scheme; | 199 bool Category::GetCategoryTypeFromScheme( |
| 197 if (dictionary->GetString(kLabelField, &label_) && | 200 const base::StringPiece& scheme, Category::CategoryType* result) { |
| 198 dictionary->GetString(kSchemeField, &scheme) && | 201 for (size_t i = 0; i < arraysize(kCategoryTypeMap); i++) { |
| 199 dictionary->GetString(kTermField, &term_)) { | 202 if (scheme == kCategoryTypeMap[i].scheme) { |
| 200 type_ = GetCategoryTypeFromScheme(scheme); | 203 *result = kCategoryTypeMap[i].type; |
| 201 if (type_ != Category::UNKNOWN) | |
| 202 return true; | 204 return true; |
| 203 | 205 } |
| 204 DVLOG(1) << "Unknown category:" | |
| 205 << "\n label = " << label_ | |
| 206 << "\n scheme = " << scheme | |
| 207 << "\n term = " << term_; | |
| 208 } | 206 } |
| 207 DVLOG(1) << "Unknown feed link type for scheme " << scheme; |
| 209 return false; | 208 return false; |
| 210 } | 209 } |
| 211 | 210 |
| 212 // Converts category.scheme into CategoryType enum. | 211 // static |
| 213 Category::CategoryType Category::GetCategoryTypeFromScheme( | 212 void Category::RegisterJSONConverter( |
| 214 const std::string& scheme) { | 213 base::JSONValueConverter<Category>* converter) { |
| 215 for (size_t i = 0; kCategoryTypeMap[i].scheme; i++) { | 214 converter->RegisterStringField(kLabelField, &Category::label_); |
| 216 if (scheme == kCategoryTypeMap[i].scheme) | 215 converter->RegisterCustomField<Category::CategoryType>( |
| 217 return kCategoryTypeMap[i].type; | 216 kSchemeField, &Category::type_, &Category::GetCategoryTypeFromScheme); |
| 218 } | 217 converter->RegisterStringField(kTermField, &Category::term_); |
| 219 DVLOG(1) << "Unknown feed link type for scheme " << scheme; | |
| 220 return Category::UNKNOWN; | |
| 221 } | 218 } |
| 222 | 219 |
| 223 | |
| 224 const Link* GDataEntry::GetLinkByType(Link::LinkType type) const { | 220 const Link* GDataEntry::GetLinkByType(Link::LinkType type) const { |
| 225 for (ScopedVector<Link>::const_iterator iter = links_.begin(); | 221 for (size_t i = 0; i < links_.size(); ++i) { |
| 226 iter != links_.end(); ++iter) { | 222 if (links_[i]->type() == type) |
| 227 if ((*iter)->type() == type) | 223 return links_[i]; |
| 228 return (*iter); | |
| 229 } | 224 } |
| 230 return NULL; | 225 return NULL; |
| 231 } | 226 } |
| 232 | 227 |
| 228 const char Content::kSrcField[] = "src"; |
| 229 const char Content::kTypeField[] = "type"; |
| 230 |
| 231 Content::Content() { |
| 232 } |
| 233 |
| 234 // static |
| 235 void Content::RegisterJSONConverter( |
| 236 base::JSONValueConverter<Content>* converter) { |
| 237 converter->RegisterCustomField(kSrcField, &Content::url_, &GetGURLFromString); |
| 238 converter->RegisterStringField(kTypeField, &Content::mime_type_); |
| 239 } |
| 240 |
| 233 const char GDataEntry::kTimeParsingDelimiters[] = "-:.TZ"; | 241 const char GDataEntry::kTimeParsingDelimiters[] = "-:.TZ"; |
| 234 const char GDataEntry::kAuthorField[] = "author"; | 242 const char GDataEntry::kAuthorField[] = "author"; |
| 235 const char GDataEntry::kLinkField[] = "link"; | 243 const char GDataEntry::kLinkField[] = "link"; |
| 236 const char GDataEntry::kCategoryField[] = "category"; | 244 const char GDataEntry::kCategoryField[] = "category"; |
| 245 const char GDataEntry::kETagField[] = "gd$etag"; |
| 246 const char GDataEntry::kUpdatedField[] = "updated.$t"; |
| 237 | 247 |
| 238 GDataEntry::GDataEntry() { | 248 GDataEntry::GDataEntry() { |
| 239 } | 249 } |
| 240 | 250 |
| 241 GDataEntry::~GDataEntry() { | 251 GDataEntry::~GDataEntry() { |
| 242 } | 252 } |
| 243 | 253 |
| 244 bool GDataEntry::ParseAuthors(const DictionaryValue* value_dict) { | 254 // static |
| 245 ListValue* authors = NULL; | 255 void GDataEntry::RegisterJSONConverter( |
| 246 if (value_dict->GetList(kAuthorField, &authors)) { | 256 base::JSONValueConverter<GDataEntry>* converter) { |
| 247 for (ListValue::iterator iter = authors->begin(); | 257 converter->RegisterStringField(kETagField, &GDataEntry::etag_); |
| 248 iter != authors->end(); | 258 converter->RegisterRepeatedMessage(kAuthorField, &GDataEntry::authors_); |
| 249 ++iter) { | 259 converter->RegisterRepeatedMessage(kLinkField, &GDataEntry::links_); |
| 250 if ((*iter)->GetType() != Value::TYPE_DICTIONARY) { | 260 converter->RegisterRepeatedMessage(kCategoryField, &GDataEntry::categories_); |
| 251 DVLOG(1) << "Invalid author list element"; | 261 converter->RegisterCustomField<base::Time>( |
| 252 return false; | 262 kUpdatedField, |
| 253 } | 263 &GDataEntry::updated_time_, |
| 254 DictionaryValue* author_dict = | 264 &GDataEntry::GetTimeFromString); |
| 255 reinterpret_cast<DictionaryValue*>(*iter); | |
| 256 scoped_ptr<Author> author(new Author()); | |
| 257 if (author->Parse(author_dict)) { | |
| 258 authors_.push_back(author.release()); | |
| 259 } else { | |
| 260 DVLOG(1) << "Invalid author etag = " << etag_; | |
| 261 } | |
| 262 } | |
| 263 } | |
| 264 return true; | |
| 265 } | 265 } |
| 266 | 266 |
| 267 bool GDataEntry::ParseLinks(const DictionaryValue* value_dict) { | 267 // static |
| 268 ListValue* links = NULL; | 268 bool GDataEntry::GetTimeFromString(const base::StringPiece& raw_value, |
| 269 if (value_dict->GetList(kLinkField, &links)) { | |
| 270 for (ListValue::iterator iter = links->begin(); | |
| 271 iter != links->end(); | |
| 272 ++iter) { | |
| 273 if ((*iter)->GetType() != Value::TYPE_DICTIONARY) { | |
| 274 DVLOG(1) << "Invalid link list element"; | |
| 275 return false; | |
| 276 } | |
| 277 DictionaryValue* link_dict = | |
| 278 reinterpret_cast<DictionaryValue*>(*iter); | |
| 279 scoped_ptr<Link> link(new Link()); | |
| 280 if (link->Parse(link_dict)) | |
| 281 links_.push_back(link.release()); | |
| 282 else | |
| 283 DVLOG(1) << "Invalid link etag = " << etag_; | |
| 284 } | |
| 285 } | |
| 286 return true; | |
| 287 } | |
| 288 | |
| 289 // static. | |
| 290 bool GDataEntry::GetTimeFromString(const std::string& raw_value, | |
| 291 base::Time* time) { | 269 base::Time* time) { |
| 292 std::vector<std::string> parts; | 270 std::vector<base::StringPiece> parts; |
| 293 if (Tokenize(raw_value, kTimeParsingDelimiters, &parts) != 7) | 271 if (Tokenize(raw_value, kTimeParsingDelimiters, &parts) != 7) |
| 294 return false; | 272 return false; |
| 295 | 273 |
| 296 base::Time::Exploded exploded; | 274 base::Time::Exploded exploded; |
| 297 if (!base::StringToInt(parts[0], &exploded.year) || | 275 if (!base::StringToInt(parts[0], &exploded.year) || |
| 298 !base::StringToInt(parts[1], &exploded.month) || | 276 !base::StringToInt(parts[1], &exploded.month) || |
| 299 !base::StringToInt(parts[2], &exploded.day_of_month) || | 277 !base::StringToInt(parts[2], &exploded.day_of_month) || |
| 300 !base::StringToInt(parts[3], &exploded.hour) || | 278 !base::StringToInt(parts[3], &exploded.hour) || |
| 301 !base::StringToInt(parts[4], &exploded.minute) || | 279 !base::StringToInt(parts[4], &exploded.minute) || |
| 302 !base::StringToInt(parts[5], &exploded.second) || | 280 !base::StringToInt(parts[5], &exploded.second) || |
| 303 !base::StringToInt(parts[6], &exploded.millisecond)) { | 281 !base::StringToInt(parts[6], &exploded.millisecond)) { |
| 304 return false; | 282 return false; |
| 305 } | 283 } |
| 306 | 284 |
| 307 exploded.day_of_week = 0; | 285 exploded.day_of_week = 0; |
| 308 if (!exploded.HasValidValues()) | 286 if (!exploded.HasValidValues()) |
| 309 return false; | 287 return false; |
| 310 | 288 |
| 311 *time = base::Time::FromLocalExploded(exploded); | 289 *time = base::Time::FromLocalExploded(exploded); |
| 312 return true; | 290 return true; |
| 313 } | 291 } |
| 314 | 292 |
| 315 bool GDataEntry::ParseCategories(const DictionaryValue* value_dict) { | |
| 316 ListValue* categories = NULL; | |
| 317 if (value_dict->GetList(kCategoryField, &categories)) { | |
| 318 for (ListValue::iterator iter = categories->begin(); | |
| 319 iter != categories->end(); | |
| 320 ++iter) { | |
| 321 if ((*iter)->GetType() != Value::TYPE_DICTIONARY) { | |
| 322 DVLOG(1) << "Invalid category list element"; | |
| 323 return false; | |
| 324 } | |
| 325 DictionaryValue* category_dict = | |
| 326 reinterpret_cast<DictionaryValue*>(*iter); | |
| 327 scoped_ptr<Category> category(new Category()); | |
| 328 if (category->Parse(category_dict)) { | |
| 329 OnAddCategory(category.get()); | |
| 330 categories_->push_back(category.release()); | |
| 331 } else { | |
| 332 DVLOG(1) << "Invalid category etag = " << etag_; | |
| 333 } | |
| 334 } | |
| 335 } | |
| 336 return true; | |
| 337 } | |
| 338 | |
| 339 // static. | |
| 340 bool GDataEntry::ParseDateTime(const DictionaryValue* dict, | |
| 341 const std::string& field, | |
| 342 base::Time* time) { | |
| 343 std::string raw_value; | |
| 344 if (!dict->GetString(field, &raw_value)) | |
| 345 return false; | |
| 346 | |
| 347 return GetTimeFromString(raw_value, time); | |
| 348 } | |
| 349 | |
| 350 const char DocumentEntry::kFeedLinkField[] = "gd$feedLink"; | 293 const char DocumentEntry::kFeedLinkField[] = "gd$feedLink"; |
| 351 const char DocumentEntry::kContentField[] = "content"; | 294 const char DocumentEntry::kContentField[] = "content"; |
| 352 const char DocumentEntry::kSrcField[] = "src"; | |
| 353 const char DocumentEntry::kTypeField[] = "type"; | |
| 354 const char DocumentEntry::kFileNameField[] = "docs$filename.$t"; | 295 const char DocumentEntry::kFileNameField[] = "docs$filename.$t"; |
| 355 const char DocumentEntry::kMD5Field[] = "docs$md5Checksum.$t"; | 296 const char DocumentEntry::kMD5Field[] = "docs$md5Checksum.$t"; |
| 356 const char DocumentEntry::kSizeField[] = "docs$size.$t"; | 297 const char DocumentEntry::kSizeField[] = "docs$size.$t"; |
| 357 const char DocumentEntry::kSuggestedFileNameField[] = | 298 const char DocumentEntry::kSuggestedFileNameField[] = |
| 358 "docs$suggestedFilename.$t"; | 299 "docs$suggestedFilename.$t"; |
| 359 const char DocumentEntry::kETagField[] = "gd$etag"; | |
| 360 const char DocumentEntry::kResourceIdField[] = "gd$resourceId.$t"; | 300 const char DocumentEntry::kResourceIdField[] = "gd$resourceId.$t"; |
| 361 const char DocumentEntry::kIDField[] = "id.$t"; | 301 const char DocumentEntry::kIDField[] = "id.$t"; |
| 362 const char DocumentEntry::kTitleField[] = "title.$t"; | 302 const char DocumentEntry::kTitleField[] = "title.$t"; |
| 363 const char DocumentEntry::kUpdatedField[] = "updated.$t"; | |
| 364 const char DocumentEntry::kPublishedField[] = "published.$t"; | 303 const char DocumentEntry::kPublishedField[] = "published.$t"; |
| 365 | 304 |
| 366 DocumentEntry::DocumentEntry() : kind_(DocumentEntry::UNKNOWN), file_size_(0) { | 305 DocumentEntry::DocumentEntry() : kind_(DocumentEntry::UNKNOWN), file_size_(0) { |
| 367 } | 306 } |
| 368 | 307 |
| 369 DocumentEntry::~DocumentEntry() { | 308 DocumentEntry::~DocumentEntry() { |
| 370 } | 309 } |
| 371 | 310 |
| 372 bool DocumentEntry::ParseFeedLinks(const DictionaryValue* value_dict) { | 311 // static |
| 373 ListValue* links = NULL; | 312 void DocumentEntry::RegisterJSONConverter( |
| 374 if (value_dict->GetList(kFeedLinkField, &links)) { | 313 base::JSONValueConverter<DocumentEntry>* converter) { |
| 375 for (ListValue::iterator iter = links->begin(); | 314 // inheritant the parent registrations. |
| 376 iter != links->end(); | 315 GDataEntry::RegisterJSONConverter( |
| 377 ++iter) { | 316 reinterpret_cast<base::JSONValueConverter<GDataEntry>*>(converter)); |
| 378 if ((*iter)->GetType() != Value::TYPE_DICTIONARY) { | 317 converter->RegisterStringField( |
| 379 DVLOG(1) << "Invalid feed link list element"; | 318 kResourceIdField, &DocumentEntry::resource_id_); |
| 380 return false; | 319 converter->RegisterStringField(kIDField, &DocumentEntry::id_); |
| 381 } | 320 converter->RegisterStringField(kTitleField, &DocumentEntry::title_); |
| 382 DictionaryValue* link_dict = | 321 converter->RegisterCustomField<base::Time>( |
| 383 reinterpret_cast<DictionaryValue*>(*iter); | 322 kPublishedField, &DocumentEntry::published_time_, |
| 384 scoped_ptr<FeedLink> link(new FeedLink()); | 323 &GDataEntry::GetTimeFromString); |
| 385 if (link->Parse(link_dict)) { | 324 converter->RegisterRepeatedMessage( |
| 386 feed_links_.push_back(link.release()); | 325 kFeedLinkField, &DocumentEntry::feed_links_); |
| 387 } else { | 326 converter->RegisterNestedField(kContentField, &DocumentEntry::content_); |
| 388 DVLOG(1) << "Invalid feed link etag = " << etag_; | 327 |
| 389 } | 328 // File properties. If the document type is not a normal file, then |
| 390 } | 329 // that's no problem because those feed must not have these fields |
| 391 } | 330 // themselves, which does not report errors. |
| 392 return true; | 331 converter->RegisterStringField(kFileNameField, &DocumentEntry::filename_); |
| 332 converter->RegisterStringField(kMD5Field, &DocumentEntry::file_md5_); |
| 333 converter->RegisterCustomField<int64>( |
| 334 kSizeField, &DocumentEntry::file_size_, &base::StringToInt64); |
| 335 converter->RegisterStringField( |
| 336 kSuggestedFileNameField, &DocumentEntry::suggested_filename_); |
| 393 } | 337 } |
| 394 | 338 |
| 395 bool DocumentEntry::ParseContent(const DictionaryValue* value_dict) { | 339 // static |
| 396 base::DictionaryValue* content = NULL; | |
| 397 if (value_dict->GetDictionary(kContentField, &content)) { | |
| 398 std::string src; | |
| 399 if (content->GetString(kSrcField, &src) && | |
| 400 content->GetString(kTypeField, &content_mime_type_)) { | |
| 401 content_url_ = GURL(src); | |
| 402 return true; | |
| 403 } | |
| 404 } | |
| 405 DVLOG(1) << "Invalid item content etag = " << etag_; | |
| 406 return false; | |
| 407 } | |
| 408 | |
| 409 bool DocumentEntry::ParseFileProperties(const DictionaryValue* value_dict) { | |
| 410 if (!value_dict->GetString(kFileNameField, &filename_)) { | |
| 411 DVLOG(1) << "File item with no name! " << etag_; | |
| 412 return false; | |
| 413 } | |
| 414 | |
| 415 if (!value_dict->GetString(kMD5Field, &file_md5_)) { | |
| 416 DVLOG(1) << "File item with no md5! " << etag_; | |
| 417 return false; | |
| 418 } | |
| 419 | |
| 420 std::string file_size; | |
| 421 if (!value_dict->GetString(kSizeField, &file_size) || | |
| 422 !file_size.length()) { | |
| 423 DVLOG(1) << "File item with no size! " << etag_; | |
| 424 return false; | |
| 425 } | |
| 426 | |
| 427 if (!base::StringToInt64(file_size, &file_size_)) { | |
| 428 DVLOG(1) << "Invalid file size '" << file_size << "' for " << etag_; | |
| 429 return false; | |
| 430 } | |
| 431 | |
| 432 if (!value_dict->GetString(kSuggestedFileNameField, &suggested_filename_)) | |
| 433 DVLOG(1) << "File item with no docs$suggestedFilename! " << etag_; | |
| 434 | |
| 435 return true; | |
| 436 } | |
| 437 | |
| 438 bool DocumentEntry::Parse(const DictionaryValue* value_dict) { | |
| 439 if (!value_dict->GetString(kETagField, &etag_)) { | |
| 440 DVLOG(1) << "Item with no etag!"; | |
| 441 return false; | |
| 442 } | |
| 443 | |
| 444 if (!value_dict->GetString(kResourceIdField, &resource_id_)) { | |
| 445 DVLOG(1) << "Item with no resource id! " << etag_; | |
| 446 return false; | |
| 447 } | |
| 448 | |
| 449 if (!value_dict->GetString(kIDField, &id_)) { | |
| 450 DVLOG(1) << "Item with no id! " << etag_; | |
| 451 return false; | |
| 452 } | |
| 453 | |
| 454 if (!value_dict->GetString(kTitleField, &title_)) { | |
| 455 DVLOG(1) << "Item with no title! " << etag_; | |
| 456 return false; | |
| 457 } | |
| 458 | |
| 459 if (!ParseDateTime(value_dict, kUpdatedField, &updated_time_)) { | |
| 460 DVLOG(1) << "Item with no updated date! " << etag_; | |
| 461 return false; | |
| 462 } | |
| 463 | |
| 464 if (!ParseDateTime(value_dict, kPublishedField, &published_time_)) { | |
| 465 DVLOG(1) << "Item with no published date! " << etag_; | |
| 466 return false; | |
| 467 } | |
| 468 | |
| 469 // Parse categories, will set up entry->kind as well. | |
| 470 if (!ParseCategories(value_dict)) | |
| 471 return false; | |
| 472 | |
| 473 if (kind_ == DocumentEntry::FILE || kind_ == DocumentEntry::PDF) { | |
| 474 if (!ParseFileProperties(value_dict)) | |
| 475 return false; | |
| 476 } | |
| 477 | |
| 478 if (!ParseAuthors(value_dict)) | |
| 479 return false; | |
| 480 | |
| 481 if (!ParseContent(value_dict)) | |
| 482 return false; | |
| 483 | |
| 484 if (!ParseLinks(value_dict)) | |
| 485 return false; | |
| 486 | |
| 487 if (!ParseFeedLinks(value_dict)) | |
| 488 return false; | |
| 489 | |
| 490 return true; | |
| 491 } | |
| 492 | |
| 493 // static. | |
| 494 DocumentEntry::EntryKind DocumentEntry::GetEntryKindFromTerm( | 340 DocumentEntry::EntryKind DocumentEntry::GetEntryKindFromTerm( |
| 495 const std::string& term) { | 341 const std::string& term) { |
| 496 if (!StartsWithASCII(term, kTermPrefix, false)) { | 342 if (!StartsWithASCII(term, kTermPrefix, false)) { |
| 497 DVLOG(1) << "Unexpected term prefix term " << term; | 343 DVLOG(1) << "Unexpected term prefix term " << term; |
| 498 return DocumentEntry::UNKNOWN; | 344 return DocumentEntry::UNKNOWN; |
| 499 } | 345 } |
| 500 | 346 |
| 501 std::string type = term.substr(strlen(kTermPrefix)); | 347 std::string type = term.substr(strlen(kTermPrefix)); |
| 502 for (size_t i = 0; kEntryKindMap[i].entry; i++) { | 348 for (size_t i = 0; i < arraysize(kEntryKindMap); i++) { |
| 503 if (type == kEntryKindMap[i].entry) | 349 if (type == kEntryKindMap[i].entry) |
| 504 return kEntryKindMap[i].kind; | 350 return kEntryKindMap[i].kind; |
| 505 } | 351 } |
| 506 DVLOG(1) << "Unknown entry type for term " << term << ", type " << type; | 352 DVLOG(1) << "Unknown entry type for term " << term << ", type " << type; |
| 507 return DocumentEntry::UNKNOWN; | 353 return DocumentEntry::UNKNOWN; |
| 508 } | 354 } |
| 509 | 355 |
| 510 void DocumentEntry::OnAddCategory(Category* category) { | 356 void DocumentEntry::FillRemainingFields() { |
| 511 if (category->type() == Category::KIND) | 357 // Set |kind_| and |labels_| based on the |categories_| in the class. |
| 512 kind_ = GetEntryKindFromTerm(category->term()); | 358 // JSONValueConverter does not have the ability to catch an element in a list |
| 513 else if (category->type() == Category::LABEL) | 359 // based on a predicate. Thus we need to iterate over |categories_| and |
| 514 labels_.push_back(category->label()); | 360 // find the elements to set these fields as a post-process. |
| 361 for (size_t i = 0; i < categories_.size(); ++i) { |
| 362 const Category* category = categories_[i]; |
| 363 if (category->type() == Category::KIND) |
| 364 kind_ = GetEntryKindFromTerm(category->term()); |
| 365 else if (category->type() == Category::LABEL) |
| 366 labels_.push_back(category->label()); |
| 367 } |
| 515 } | 368 } |
| 516 | 369 |
| 517 | |
| 518 const char DocumentFeed::kETagField[] = "gd$etag"; | |
| 519 const char DocumentFeed::kStartIndexField[] = "openSearch$startIndex.$t"; | 370 const char DocumentFeed::kStartIndexField[] = "openSearch$startIndex.$t"; |
| 520 const char DocumentFeed::kItemsPerPageField[] = | 371 const char DocumentFeed::kItemsPerPageField[] = |
| 521 "openSearch$itemsPerPage.$t"; | 372 "openSearch$itemsPerPage.$t"; |
| 522 const char DocumentFeed::kUpdatedField[] = "updated.$t"; | |
| 523 const char DocumentFeed::kTitleField[] = "title.$t"; | 373 const char DocumentFeed::kTitleField[] = "title.$t"; |
| 524 const char DocumentFeed::kEntryField[] = "entry"; | 374 const char DocumentFeed::kEntryField[] = "entry"; |
| 525 | 375 |
| 526 DocumentFeed::DocumentFeed() : start_index_(0), items_per_page_(0) { | 376 DocumentFeed::DocumentFeed() : start_index_(0), items_per_page_(0) { |
| 527 } | 377 } |
| 528 | 378 |
| 529 DocumentFeed::~DocumentFeed() { | 379 DocumentFeed::~DocumentFeed() { |
| 530 } | 380 } |
| 531 | 381 |
| 532 bool DocumentFeed::Parse(const DictionaryValue* value_dict) { | 382 // static |
| 533 if (!value_dict->GetString(kETagField, &etag_)) { | 383 void DocumentFeed::RegisterJSONConverter( |
| 534 DVLOG(1) << "Feed with no etag!"; | 384 base::JSONValueConverter<DocumentFeed>* converter) { |
| 535 return false; | 385 // inheritance |
| 536 } | 386 GDataEntry::RegisterJSONConverter( |
| 537 | 387 reinterpret_cast<base::JSONValueConverter<GDataEntry>*>(converter)); |
| 538 // TODO(zelidrag): Once we figure out where these will be used, we should | 388 // TODO(zelidrag): Once we figure out where these will be used, we should |
| 539 // check for valid start_index_ and items_per_page_ values. | 389 // check for valid start_index_ and items_per_page_ values. |
| 540 std::string start_index; | 390 converter->RegisterCustomField<int>( |
| 541 if (!value_dict->GetString(kStartIndexField, &start_index) || | 391 kStartIndexField, &DocumentFeed::start_index_, &base::StringToInt); |
| 542 !base::StringToInt(start_index, &start_index_)) { | 392 converter->RegisterCustomField<int>( |
| 543 DVLOG(1) << "Feed with no startIndex! " << etag_; | 393 kItemsPerPageField, &DocumentFeed::items_per_page_, &base::StringToInt); |
| 544 return false; | 394 converter->RegisterStringField(kTitleField, &DocumentFeed::title_); |
| 545 } | 395 converter->RegisterRepeatedMessage(kEntryField, &DocumentFeed::entries_); |
| 546 | |
| 547 std::string items_per_page; | |
| 548 if (!value_dict->GetString(kItemsPerPageField, &items_per_page) || | |
| 549 !base::StringToInt(items_per_page, &items_per_page_)) { | |
| 550 DVLOG(1) << "Feed with no itemsPerPage! " << etag_; | |
| 551 return false; | |
| 552 } | |
| 553 | |
| 554 if (!ParseDateTime(value_dict, kUpdatedField, &updated_time_)) { | |
| 555 DVLOG(1) << "Feed with no updated date! " << etag_; | |
| 556 return false; | |
| 557 } | |
| 558 | |
| 559 if (!value_dict->GetString(kTitleField, &title_)) { | |
| 560 DVLOG(1) << "Feed with no title!"; | |
| 561 return false; | |
| 562 } | |
| 563 | |
| 564 ListValue* entries = NULL; | |
| 565 if (value_dict->GetList(kEntryField, &entries)) { | |
| 566 for (ListValue::iterator iter = entries->begin(); | |
| 567 iter != entries->end(); | |
| 568 ++iter) { | |
| 569 DocumentEntry* entry = DocumentEntry::CreateFrom(*iter); | |
| 570 if (entry) | |
| 571 entries_.push_back(entry); | |
| 572 } | |
| 573 } | |
| 574 | |
| 575 // Parse categories. | |
| 576 if (!ParseCategories(value_dict)) | |
| 577 return false; | |
| 578 | |
| 579 // Parse author list. | |
| 580 if (!ParseAuthors(value_dict)) | |
| 581 return false; | |
| 582 | |
| 583 if (!ParseLinks(value_dict)) | |
| 584 return false; | |
| 585 | |
| 586 return true; | |
| 587 } | 396 } |
| 588 | 397 |
| 589 // static. | 398 // static |
| 590 DocumentEntry* DocumentEntry::CreateFrom(base::Value* value) { | 399 DocumentEntry* DocumentEntry::CreateFrom(base::Value* value) { |
| 591 if (!value || value->GetType() != Value::TYPE_DICTIONARY) | 400 base::JSONValueConverter<DocumentEntry> converter; |
| 592 return NULL; | |
| 593 | |
| 594 DictionaryValue* root = reinterpret_cast<DictionaryValue*>(value); | |
| 595 scoped_ptr<DocumentEntry> entry(new DocumentEntry()); | 401 scoped_ptr<DocumentEntry> entry(new DocumentEntry()); |
| 596 if (!entry->Parse(root)) { | 402 if (!converter.Convert(*value, entry.get())) { |
| 597 DVLOG(1) << "Invalid document entry!"; | 403 DVLOG(1) << "Invalid document entry!"; |
| 598 return NULL; | 404 return NULL; |
| 599 } | 405 } |
| 600 | 406 |
| 407 entry->FillRemainingFields(); |
| 601 return entry.release(); | 408 return entry.release(); |
| 602 } | 409 } |
| 603 | 410 |
| 604 | 411 |
| 605 // static. | 412 bool DocumentFeed::Parse(base::Value* value) { |
| 413 base::JSONValueConverter<DocumentFeed> converter; |
| 414 if (!converter.Convert(*value, this)) { |
| 415 DVLOG(1) << "Invalid document feed!"; |
| 416 return false; |
| 417 } |
| 418 |
| 419 for (size_t i = 0; i < entries_.size(); ++i) { |
| 420 entries_[i]->FillRemainingFields(); |
| 421 } |
| 422 return true; |
| 423 } |
| 424 |
| 425 // static |
| 606 DocumentFeed* DocumentFeed::CreateFrom(base::Value* value) { | 426 DocumentFeed* DocumentFeed::CreateFrom(base::Value* value) { |
| 607 if (!value || value->GetType() != Value::TYPE_DICTIONARY) | |
| 608 return NULL; | |
| 609 | |
| 610 DictionaryValue* root_entry_dict = | |
| 611 reinterpret_cast<DictionaryValue*>(value); | |
| 612 scoped_ptr<DocumentFeed> feed(new DocumentFeed()); | 427 scoped_ptr<DocumentFeed> feed(new DocumentFeed()); |
| 613 if (!feed->Parse(root_entry_dict)) { | 428 if (!feed->Parse(value)) { |
| 614 DVLOG(1) << "Invalid document feed!"; | 429 DVLOG(1) << "Invalid document feed!"; |
| 615 return NULL; | 430 return NULL; |
| 616 } | 431 } |
| 617 | 432 |
| 618 return feed.release(); | 433 return feed.release(); |
| 619 } | 434 } |
| 620 | 435 |
| 621 bool DocumentFeed::GetNextFeedURL(GURL* url) { | 436 bool DocumentFeed::GetNextFeedURL(GURL* url) { |
| 622 DCHECK(url); | 437 DCHECK(url); |
| 623 for (ScopedVector<Link>::iterator iter = links_.begin(); | 438 for (size_t i = 0; i < links_.size(); ++i) { |
| 624 iter != links_.end(); ++iter) { | 439 if (links_[i]->type() == Link::NEXT) { |
| 625 if ((*iter)->type() == Link::NEXT) { | 440 *url = links_[i]->href(); |
| 626 *url = (*iter)->href(); | |
| 627 return true; | 441 return true; |
| 628 } | 442 } |
| 629 } | 443 } |
| 630 return false; | 444 return false; |
| 631 } | 445 } |
| 632 | 446 |
| 633 } // namespace gdata | 447 } // namespace gdata |
| OLD | NEW |