Home | History | Annotate | Download | only in drive
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #ifndef GOOGLE_APIS_DRIVE_GDATA_WAPI_PARSER_H_
      6 #define GOOGLE_APIS_DRIVE_GDATA_WAPI_PARSER_H_
      7 
      8 #include <string>
      9 #include <utility>
     10 #include <vector>
     11 
     12 #include "base/compiler_specific.h"
     13 #include "base/memory/scoped_ptr.h"
     14 #include "base/memory/scoped_vector.h"
     15 #include "base/strings/string_piece.h"
     16 #include "base/time/time.h"
     17 #include "google_apis/drive/drive_entry_kinds.h"
     18 #include "url/gurl.h"
     19 
     20 namespace base {
     21 class FilePath;
     22 class DictionaryValue;
     23 class Value;
     24 
     25 template <class StructType>
     26 class JSONValueConverter;
     27 
     28 namespace internal {
     29 template <class NestedType>
     30 class RepeatedMessageConverter;
     31 }  // namespace internal
     32 
     33 }  // namespace base
     34 
     35 // Defines data elements of Google Documents API as described in
     36 // http://code.google.com/apis/documents/.
     37 namespace google_apis {
     38 
     39 // Defines link (URL) of an entity (document, file, feed...). Each entity could
     40 // have more than one link representing it.
     41 class Link {
     42  public:
     43   enum LinkType {
     44     LINK_UNKNOWN,
     45     LINK_SELF,
     46     LINK_NEXT,
     47     LINK_PARENT,
     48     LINK_ALTERNATE,
     49     LINK_EDIT,
     50     LINK_EDIT_MEDIA,
     51     LINK_ALT_EDIT_MEDIA,
     52     LINK_ALT_POST,
     53     LINK_FEED,
     54     LINK_POST,
     55     LINK_BATCH,
     56     LINK_RESUMABLE_EDIT_MEDIA,
     57     LINK_RESUMABLE_CREATE_MEDIA,
     58     LINK_TABLES_FEED,
     59     LINK_WORKSHEET_FEED,
     60     LINK_THUMBNAIL,
     61     LINK_EMBED,
     62     LINK_PRODUCT,
     63     LINK_ICON,
     64     LINK_OPEN_WITH,
     65     LINK_SHARE,
     66   };
     67   Link();
     68   ~Link();
     69 
     70   // Registers the mapping between JSON field names and the members in
     71   // this class.
     72   static void RegisterJSONConverter(base::JSONValueConverter<Link>* converter);
     73 
     74   // Type of the link.
     75   LinkType type() const { return type_; }
     76 
     77   // URL of the link.
     78   const GURL& href() const { return href_; }
     79 
     80   // Title of the link.
     81   const std::string& title() const { return title_; }
     82 
     83   // For OPEN_WITH links, this contains the application ID. For all other link
     84   // types, it is the empty string.
     85   const std::string& app_id() const { return app_id_; }
     86 
     87   // Link MIME type.
     88   const std::string& mime_type() const { return mime_type_; }
     89 
     90   void set_type(LinkType type) { type_ = type; }
     91   void set_href(const GURL& href) { href_ = href; }
     92   void set_title(const std::string& title) { title_ = title; }
     93   void set_app_id(const std::string& app_id) { app_id_ = app_id; }
     94   void set_mime_type(const std::string& mime_type) { mime_type_ = mime_type; }
     95 
     96  private:
     97   friend class ResourceEntry;
     98   // Converts value of link.rel into LinkType. Outputs to |type| and returns
     99   // true when |rel| has a valid value. Otherwise does nothing and returns
    100   // false.
    101   static bool GetLinkType(const base::StringPiece& rel, LinkType* type);
    102 
    103   // Converts value of link.rel to application ID, if there is one embedded in
    104   // the link.rel field. Outputs to |app_id| and returns true when |rel| has a
    105   // valid value. Otherwise does nothing and returns false.
    106   static bool GetAppID(const base::StringPiece& rel, std::string* app_id);
    107 
    108   LinkType type_;
    109   GURL href_;
    110   std::string title_;
    111   std::string app_id_;
    112   std::string mime_type_;
    113 
    114   DISALLOW_COPY_AND_ASSIGN(Link);
    115 };
    116 
    117 // Feed links define links (URLs) to special list of entries (i.e. list of
    118 // previous document revisions).
    119 class ResourceLink {
    120  public:
    121   enum ResourceLinkType {
    122     FEED_LINK_UNKNOWN,
    123     FEED_LINK_ACL,
    124     FEED_LINK_REVISIONS,
    125   };
    126   ResourceLink();
    127 
    128   // Registers the mapping between JSON field names and the members in
    129   // this class.
    130   static void RegisterJSONConverter(
    131       base::JSONValueConverter<ResourceLink>* converter);
    132 
    133   // MIME type of the feed.
    134   ResourceLinkType type() const { return type_; }
    135 
    136   // URL of the feed.
    137   const GURL& href() const { return href_; }
    138 
    139   void set_type(ResourceLinkType type) { type_ = type; }
    140   void set_href(const GURL& href) { href_ = href; }
    141 
    142  private:
    143   friend class ResourceEntry;
    144   // Converts value of gd$feedLink.rel into ResourceLinkType enum.
    145   // Outputs to |result| and returns true when |rel| has a valid
    146   // value.  Otherwise does nothing and returns false.
    147   static bool GetFeedLinkType(
    148       const base::StringPiece& rel, ResourceLinkType* result);
    149 
    150   ResourceLinkType type_;
    151   GURL href_;
    152 
    153   DISALLOW_COPY_AND_ASSIGN(ResourceLink);
    154 };
    155 
    156 // Author represents an author of an entity.
    157 class Author {
    158  public:
    159   Author();
    160 
    161   // Registers the mapping between JSON field names and the members in
    162   // this class.
    163   static void RegisterJSONConverter(
    164       base::JSONValueConverter<Author>* converter);
    165 
    166   // Getters.
    167   const std::string& name() const { return name_; }
    168   const std::string& email() const { return email_; }
    169 
    170   void set_name(const std::string& name) { name_ = name; }
    171   void set_email(const std::string& email) { email_ = email; }
    172 
    173  private:
    174   friend class ResourceEntry;
    175 
    176   std::string name_;
    177   std::string email_;
    178 
    179   DISALLOW_COPY_AND_ASSIGN(Author);
    180 };
    181 
    182 // Entry category.
    183 class Category {
    184  public:
    185   enum CategoryType {
    186     CATEGORY_UNKNOWN,
    187     CATEGORY_ITEM,
    188     CATEGORY_KIND,
    189     CATEGORY_LABEL,
    190   };
    191 
    192   Category();
    193 
    194   // Registers the mapping between JSON field names and the members in
    195   // this class.
    196   static void RegisterJSONConverter(
    197       base::JSONValueConverter<Category>* converter);
    198 
    199   // Category label.
    200   const std::string& label() const { return label_; }
    201 
    202   // Category type.
    203   CategoryType type() const { return type_; }
    204 
    205   // Category term.
    206   const std::string& term() const { return term_; }
    207 
    208   void set_label(const std::string& label) { label_ = label; }
    209   void set_type(CategoryType type) { type_ = type; }
    210   void set_term(const std::string& term) { term_ = term; }
    211 
    212  private:
    213   friend class ResourceEntry;
    214   // Converts category scheme into CategoryType enum. For example,
    215   // http://schemas.google.com/g/2005#kind => Category::CATEGORY_KIND
    216   // Returns false and does not change |result| when |scheme| has an
    217   // unrecognizable value.
    218   static bool GetCategoryTypeFromScheme(
    219       const base::StringPiece& scheme, CategoryType* result);
    220 
    221   std::string label_;
    222   CategoryType type_;
    223   std::string term_;
    224 
    225   DISALLOW_COPY_AND_ASSIGN(Category);
    226 };
    227 
    228 // Content details of a resource: mime-type, url, and so on.
    229 class Content {
    230  public:
    231   Content();
    232 
    233   // Registers the mapping between JSON field names and the members in
    234   // this class.
    235   static void RegisterJSONConverter(
    236       base::JSONValueConverter<Content>* converter);
    237 
    238   // The URL to download the file content.
    239   // Note that the url can expire, so we'll fetch the latest resource
    240   // entry before starting a download to get the download URL.
    241   const GURL& url() const { return url_; }
    242   const std::string& mime_type() const { return mime_type_; }
    243 
    244   void set_url(const GURL& url) { url_ = url; }
    245   void set_mime_type(const std::string& mime_type) { mime_type_ = mime_type; }
    246 
    247  private:
    248   friend class ResourceEntry;
    249 
    250   GURL url_;
    251   std::string mime_type_;
    252 };
    253 
    254 // Base class for feed entries. This class defines fields commonly used by
    255 // various feeds.
    256 class CommonMetadata {
    257  public:
    258   CommonMetadata();
    259   virtual ~CommonMetadata();
    260 
    261   // Returns a link of a given |type| for this entry. If not found, it returns
    262   // NULL.
    263   const Link* GetLinkByType(Link::LinkType type) const;
    264 
    265   // Entry update time.
    266   base::Time updated_time() const { return updated_time_; }
    267 
    268   // Entry ETag.
    269   const std::string& etag() const { return etag_; }
    270 
    271   // List of entry authors.
    272   const ScopedVector<Author>& authors() const { return authors_; }
    273 
    274   // List of entry links.
    275   const ScopedVector<Link>& links() const { return links_; }
    276   ScopedVector<Link>* mutable_links() { return &links_; }
    277 
    278   // List of entry categories.
    279   const ScopedVector<Category>& categories() const { return categories_; }
    280 
    281   void set_etag(const std::string& etag) { etag_ = etag; }
    282   void set_authors(ScopedVector<Author> authors) {
    283     authors_ = authors.Pass();
    284   }
    285   void set_links(ScopedVector<Link> links) {
    286     links_ = links.Pass();
    287   }
    288   void set_categories(ScopedVector<Category> categories) {
    289     categories_ = categories.Pass();
    290   }
    291   void set_updated_time(const base::Time& updated_time) {
    292     updated_time_ = updated_time;
    293   }
    294 
    295  protected:
    296   // Registers the mapping between JSON field names and the members in
    297   // this class.
    298   template<typename CommonMetadataDescendant>
    299   static void RegisterJSONConverter(
    300       base::JSONValueConverter<CommonMetadataDescendant>* converter);
    301 
    302   std::string etag_;
    303   ScopedVector<Author> authors_;
    304   ScopedVector<Link> links_;
    305   ScopedVector<Category> categories_;
    306   base::Time updated_time_;
    307 
    308   DISALLOW_COPY_AND_ASSIGN(CommonMetadata);
    309 };
    310 
    311 // This class represents a resource entry. A resource is a generic term which
    312 // refers to a file and a directory.
    313 class ResourceEntry : public CommonMetadata {
    314  public:
    315   ResourceEntry();
    316   virtual ~ResourceEntry();
    317 
    318   // Extracts "entry" dictionary from the JSON value, and parse the contents,
    319   // using CreateFrom(). Returns NULL on failure. The input JSON data, coming
    320   // from the gdata server, looks like:
    321   //
    322   // {
    323   //   "encoding": "UTF-8",
    324   //   "entry": { ... },   // This function will extract this and parse.
    325   //   "version": "1.0"
    326   // }
    327   //
    328   // The caller should delete the returned object.
    329   static scoped_ptr<ResourceEntry> ExtractAndParse(const base::Value& value);
    330 
    331   // Creates resource entry from parsed JSON Value.  You should call
    332   // this instead of instantiating JSONValueConverter by yourself
    333   // because this method does some post-process for some fields.  See
    334   // FillRemainingFields comment and implementation for the details.
    335   static scoped_ptr<ResourceEntry> CreateFrom(const base::Value& value);
    336 
    337   // Returns name of entry node.
    338   static std::string GetEntryNodeName();
    339 
    340   // Registers the mapping between JSON field names and the members in
    341   // this class.
    342   static void RegisterJSONConverter(
    343       base::JSONValueConverter<ResourceEntry>* converter);
    344 
    345   // Sets true to |result| if the field exists.
    346   // Always returns true even when the field does not exist.
    347   static bool HasFieldPresent(const base::Value* value, bool* result);
    348 
    349   // Parses |value| as int64 and sets it to |result|. If the field does not
    350   // exist, sets 0 to |result| as default value.
    351   // Returns true if |value| is NULL or it is parsed as int64 successfully.
    352   static bool ParseChangestamp(const base::Value* value, int64* result);
    353 
    354   // The resource ID is used to identify a resource, which looks like:
    355   // file:d41d8cd98f00b204e9800998ecf8
    356   const std::string& resource_id() const { return resource_id_; }
    357 
    358   // This is a URL looks like:
    359   // https://docs.google.com/feeds/id/file%3Ad41d8cd98f00b204e9800998ecf8.
    360   // The URL is currently not used.
    361   const std::string& id() const { return id_; }
    362 
    363   DriveEntryKind kind() const { return kind_; }
    364   const std::string& title() const { return title_; }
    365   base::Time published_time() const { return published_time_; }
    366   base::Time last_viewed_time() const { return last_viewed_time_; }
    367   const std::vector<std::string>& labels() const { return labels_; }
    368 
    369   // The URL to download a file content.
    370   // Search for 'download_url' in gdata_wapi_requests.h for details.
    371   const GURL& download_url() const { return content_.url(); }
    372 
    373   const std::string& content_mime_type() const { return content_.mime_type(); }
    374 
    375   // The resource links contain extra links for revisions and access control,
    376   // etc.  Note that links() contain more basic links like edit URL,
    377   // alternative URL, etc.
    378   const ScopedVector<ResourceLink>& resource_links() const {
    379     return resource_links_;
    380   }
    381 
    382   // File name (exists only for kinds FILE and PDF).
    383   const std::string& filename() const { return filename_; }
    384 
    385   // Suggested file name (exists only for kinds FILE and PDF).
    386   const std::string& suggested_filename() const { return suggested_filename_; }
    387 
    388   // File content MD5 (exists only for kinds FILE and PDF).
    389   const std::string& file_md5() const { return file_md5_; }
    390 
    391   // File size (exists only for kinds FILE and PDF).
    392   int64 file_size() const { return file_size_; }
    393 
    394   // True if the file or directory is deleted (applicable to change list only).
    395   bool deleted() const { return deleted_ || removed_; }
    396 
    397   // Changestamp (exists only for change query results).
    398   // If not exists, defaults to 0.
    399   int64 changestamp() const { return changestamp_; }
    400 
    401   // Image width (exists only for images).
    402   // If doesn't exist, then equals -1.
    403   int64 image_width() const { return image_width_; }
    404 
    405   // Image height (exists only for images).
    406   // If doesn't exist, then equals -1.
    407   int64 image_height() const { return image_height_; }
    408 
    409   // Image rotation in clockwise degrees (exists only for images).
    410   // If doesn't exist, then equals -1.
    411   int64 image_rotation() const { return image_rotation_; }
    412 
    413   // The time of this modification.
    414   // Note: This property is filled only when converted from ChangeResource.
    415   const base::Time& modification_date() const { return modification_date_; }
    416 
    417   // Text version of resource entry kind. Returns an empty string for
    418   // unknown entry kind.
    419   std::string GetEntryKindText() const;
    420 
    421   // Returns preferred file extension for hosted documents. If entry is not
    422   // a hosted document, this call returns an empty string.
    423   static std::string GetHostedDocumentExtension(DriveEntryKind kind);
    424 
    425   // True if resource entry is remotely hosted.
    426   bool is_hosted_document() const {
    427     return (ClassifyEntryKind(kind_) & KIND_OF_HOSTED_DOCUMENT) > 0;
    428   }
    429   // True if resource entry hosted by Google Documents.
    430   bool is_google_document() const {
    431     return (ClassifyEntryKind(kind_) & KIND_OF_GOOGLE_DOCUMENT) > 0;
    432   }
    433   // True if resource entry is hosted by an external application.
    434   bool is_external_document() const {
    435     return (ClassifyEntryKind(kind_) & KIND_OF_EXTERNAL_DOCUMENT) > 0;
    436   }
    437   // True if resource entry is a folder (collection).
    438   bool is_folder() const {
    439     return (ClassifyEntryKind(kind_) & KIND_OF_FOLDER) > 0;
    440   }
    441   // True if resource entry is regular file.
    442   bool is_file() const {
    443     return (ClassifyEntryKind(kind_) & KIND_OF_FILE) > 0;
    444   }
    445   // True if resource entry can't be mapped to the file system.
    446   bool is_special() const {
    447     return !is_file() && !is_folder() && !is_hosted_document();
    448   }
    449 
    450   // The following constructs are exposed for unit tests.
    451 
    452   // Classes of EntryKind. Used for ClassifyEntryKind().
    453   enum EntryKindClass {
    454     KIND_OF_NONE = 0,
    455     KIND_OF_HOSTED_DOCUMENT = 1,
    456     KIND_OF_GOOGLE_DOCUMENT = 1 << 1,
    457     KIND_OF_EXTERNAL_DOCUMENT = 1 << 2,
    458     KIND_OF_FOLDER = 1 << 3,
    459     KIND_OF_FILE = 1 << 4,
    460   };
    461 
    462   // Returns the kind enum corresponding to the extension in form ".xxx".
    463   static DriveEntryKind GetEntryKindFromExtension(const std::string& extension);
    464 
    465   // Classifies the EntryKind. The returned value is a bitmask of
    466   // EntryKindClass. For example, DOCUMENT is classified as
    467   // KIND_OF_HOSTED_DOCUMENT and KIND_OF_GOOGLE_DOCUMENT, hence the returned
    468   // value is KIND_OF_HOSTED_DOCUMENT | KIND_OF_GOOGLE_DOCUMENT.
    469   static int ClassifyEntryKind(DriveEntryKind kind);
    470 
    471   // Classifies the EntryKind by the file extension of specific path. The
    472   // returned value is a bitmask of EntryKindClass. See also ClassifyEntryKind.
    473   static int ClassifyEntryKindByFileExtension(const base::FilePath& file);
    474 
    475   void set_resource_id(const std::string& resource_id) {
    476     resource_id_ = resource_id;
    477   }
    478   void set_id(const std::string& id) { id_ = id; }
    479   void set_kind(DriveEntryKind kind) { kind_ = kind; }
    480   void set_title(const std::string& title) { title_ = title; }
    481   void set_published_time(const base::Time& published_time) {
    482     published_time_ = published_time;
    483   }
    484   void set_last_viewed_time(const base::Time& last_viewed_time) {
    485     last_viewed_time_ = last_viewed_time;
    486   }
    487   void set_labels(const std::vector<std::string>& labels) {
    488     labels_ = labels;
    489   }
    490   void set_content(const Content& content) {
    491     content_ = content;
    492   }
    493   void set_resource_links(ScopedVector<ResourceLink> resource_links) {
    494     resource_links_ = resource_links.Pass();
    495   }
    496   void set_filename(const std::string& filename) { filename_ = filename; }
    497   void set_suggested_filename(const std::string& suggested_filename) {
    498     suggested_filename_ = suggested_filename;
    499   }
    500   void set_file_md5(const std::string& file_md5) { file_md5_ = file_md5; }
    501   void set_file_size(int64 file_size) { file_size_ = file_size; }
    502   void set_deleted(bool deleted) { deleted_ = deleted; }
    503   void set_removed(bool removed) { removed_ = removed; }
    504   void set_changestamp(int64 changestamp) { changestamp_ = changestamp; }
    505   void set_image_width(int64 image_width) { image_width_ = image_width; }
    506   void set_image_height(int64 image_height) { image_height_ = image_height; }
    507   void set_image_rotation(int64 image_rotation) {
    508     image_rotation_ = image_rotation;
    509   }
    510   void set_modification_date(const base::Time& modification_date) {
    511     modification_date_ = modification_date;
    512   }
    513 
    514   // Fills the remaining fields where JSONValueConverter cannot catch.
    515   // Currently, sets |kind_| and |labels_| based on the |categories_| in the
    516   // class.
    517   void FillRemainingFields();
    518 
    519  private:
    520   friend class base::internal::RepeatedMessageConverter<ResourceEntry>;
    521   friend class ResourceList;
    522   friend class ResumeUploadRequest;
    523 
    524   // Converts categories.term into DriveEntryKind enum.
    525   static DriveEntryKind GetEntryKindFromTerm(const std::string& term);
    526   // Converts |kind| into its text identifier equivalent.
    527   static const char* GetEntryKindDescription(DriveEntryKind kind);
    528 
    529   std::string resource_id_;
    530   std::string id_;
    531   DriveEntryKind kind_;
    532   std::string title_;
    533   base::Time published_time_;
    534   // Last viewed value may be unreliable. See: crbug.com/152628.
    535   base::Time last_viewed_time_;
    536   std::vector<std::string> labels_;
    537   Content content_;
    538   ScopedVector<ResourceLink> resource_links_;
    539   // Optional fields for files only.
    540   std::string filename_;
    541   std::string suggested_filename_;
    542   std::string file_md5_;
    543   int64 file_size_;
    544   bool deleted_;
    545   bool removed_;
    546   int64 changestamp_;
    547   int64 image_width_;
    548   int64 image_height_;
    549   int64 image_rotation_;
    550 
    551   base::Time modification_date_;
    552 
    553   DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
    554 };
    555 
    556 // This class represents a list of resource entries with some extra metadata
    557 // such as the root upload URL. The feed is paginated and the rest of the
    558 // feed can be fetched by retrieving the remaining parts of the feed from
    559 // URLs provided by GetNextFeedURL() method.
    560 class ResourceList : public CommonMetadata {
    561  public:
    562   ResourceList();
    563   virtual ~ResourceList();
    564 
    565   // Extracts "feed" dictionary from the JSON value, and parse the contents,
    566   // using CreateFrom(). Returns NULL on failure. The input JSON data, coming
    567   // from the gdata server, looks like:
    568   //
    569   // {
    570   //   "encoding": "UTF-8",
    571   //   "feed": { ... },   // This function will extract this and parse.
    572   //   "version": "1.0"
    573   // }
    574   static scoped_ptr<ResourceList> ExtractAndParse(const base::Value& value);
    575 
    576   // Creates feed from parsed JSON Value.  You should call this
    577   // instead of instantiating JSONValueConverter by yourself because
    578   // this method does some post-process for some fields.  See
    579   // FillRemainingFields comment and implementation in ResourceEntry
    580   // class for the details.
    581   static scoped_ptr<ResourceList> CreateFrom(const base::Value& value);
    582 
    583   // Registers the mapping between JSON field names and the members in
    584   // this class.
    585   static void RegisterJSONConverter(
    586       base::JSONValueConverter<ResourceList>* converter);
    587 
    588   // Returns true and passes|url| of the next feed if the current entry list
    589   // does not completed this feed.
    590   bool GetNextFeedURL(GURL* url) const;
    591 
    592   // List of resource entries.
    593   const ScopedVector<ResourceEntry>& entries() const { return entries_; }
    594   ScopedVector<ResourceEntry>* mutable_entries() { return &entries_; }
    595 
    596   // Releases entries_ into |entries|. This is a transfer of ownership, so the
    597   // caller is responsible for deleting the elements of |entries|.
    598   void ReleaseEntries(std::vector<ResourceEntry*>* entries);
    599 
    600   // Start index of the resource entry list.
    601   int start_index() const { return start_index_; }
    602 
    603   // Number of items per feed of the resource entry list.
    604   int items_per_page() const { return items_per_page_; }
    605 
    606   // The largest changestamp. Next time the resource list should be fetched
    607   // from this changestamp.
    608   int64 largest_changestamp() const { return largest_changestamp_; }
    609 
    610   // Resource entry list title.
    611   const std::string& title() { return title_; }
    612 
    613   void set_entries(ScopedVector<ResourceEntry> entries) {
    614     entries_ = entries.Pass();
    615   }
    616   void set_start_index(int start_index) {
    617     start_index_ = start_index;
    618   }
    619   void set_items_per_page(int items_per_page) {
    620     items_per_page_ = items_per_page;
    621   }
    622   void set_title(const std::string& title) {
    623     title_ = title;
    624   }
    625   void set_largest_changestamp(int64 largest_changestamp) {
    626     largest_changestamp_ = largest_changestamp;
    627   }
    628 
    629  private:
    630   // Parses and initializes data members from content of |value|.
    631   // Return false if parsing fails.
    632   bool Parse(const base::Value& value);
    633 
    634   ScopedVector<ResourceEntry> entries_;
    635   int start_index_;
    636   int items_per_page_;
    637   std::string title_;
    638   int64 largest_changestamp_;
    639 
    640   DISALLOW_COPY_AND_ASSIGN(ResourceList);
    641 };
    642 
    643 }  // namespace google_apis
    644 
    645 #endif  // GOOGLE_APIS_DRIVE_GDATA_WAPI_PARSER_H_
    646