/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * GData Client * Copyright (C) Thibault Saunier 2009 * Copyright (C) Red Hat, Inc. 2015, 2016 * * GData Client is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GData Client is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with GData Client. If not, see . */ /** * SECTION:gdata-documents-entry * @short_description: GData document object abstract class * @stability: Stable * @include: gdata/services/documents/gdata-documents-entry.h * * #GDataDocumentsEntry is a subclass of #GDataEntry to represent a Google Drive entry, which is then further subclassed * to give specific document types. * * #GDataDocumentsEntry implements #GDataAccessHandler, meaning the access rules to it can be modified using that interface. As well as the * access roles defined for the base #GDataAccessRule (e.g. %GDATA_ACCESS_ROLE_NONE), #GDataDocumentsEntry has its own, such as * %GDATA_DOCUMENTS_ACCESS_ROLE_OWNER and %GDATA_DOCUMENTS_ACCESS_ROLE_READER. * * Documents can (confusingly) be referenced by three different types of IDs: their entry ID, their resource ID and their document ID (untyped resource * ID). Each is a substring of the previous ones (i.e. the entry ID contains the resource ID, which in turn contains the document ID). The resource ID * and document ID should almost always be considered as internal, and thus entry IDs (#GDataEntry:id) should normally be used to uniquely identify * documents. For more information, see #GDataDocumentsEntry:resource-id. * * For more details of Google Drive's GData API, see the * online documentation. * * * Moving an Entry Between Folders * * GDataDocumentsService *service; * GDataDocumentsEntry *entry, *intermediate_entry, *updated_entry; * GDataDocumentsFolder *old_folder, *new_folder; * GError *error = NULL; * * /* Create a service */ * service = create_documents_service (); * * /* Get the entry, the folder it's being moved out of, and the folder it's being moved into. The entry can either be a document or * * another folder, allowing hierarchies of folders to be constructed. */ * entry = query_user_for_entry (service); * old_folder = query_user_for_old_folder (service); * new_folder = query_user_for_new_folder (service); * * /* Add the entry to the new folder */ * intermediate_entry = gdata_documents_service_add_entry_to_folder (service, entry, new_folder, NULL, &error); * * g_object_unref (entry); * g_object_unref (new_folder); * * if (error != NULL) { * g_error ("Error adding entry to new folder: %s", error->message); * g_error_free (error); * g_object_unref (old_folder); * g_object_unref (service); * return; * } * * /* Remove the entry from the old folder */ * updated_entry = gdata_documents_service_remove_entry_from_folder (service, intermediate_entry, old_folder, NULL, &error); * * g_object_unref (intermediate_entry); * g_object_unref (old_folder); * g_object_unref (service); * * if (error != NULL) { * /* Note that you might want to attempt to remove the intermediate_entry from the new_folder in this error case, so that * * the operation is aborted cleanly. */ * g_error ("Error removing entry from previous folder: %s", error->message); * g_error_free (error); * return; * } * * /* Do something with the updated entry */ * * g_object_unref (updated_entry); * * * * Since: 0.4.0 */ #include #include #include #include "gdata-documents-entry.h" #include "gdata-parser.h" #include "gdata-types.h" #include "gdata-private.h" #include "gdata-access-handler.h" #include "gdata-documents-access-rule.h" #include "gdata-documents-service.h" #include "gdata-documents-spreadsheet.h" #include "gdata-documents-presentation.h" #include "gdata-documents-text.h" #include "gdata-documents-folder.h" #include "gdata-documents-utils.h" static void gdata_documents_entry_access_handler_init (GDataAccessHandlerIface *iface); static void gdata_documents_entry_finalize (GObject *object); static void gdata_entry_dispose (GObject *object); static const gchar *get_content_type (void); static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces); static void gdata_documents_entry_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void gdata_documents_entry_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static gboolean parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error); static gboolean post_parse_json (GDataParsable *parsable, gpointer user_data, GError **error); static void get_json (GDataParsable *parsable, JsonBuilder *builder); static gchar *get_entry_uri (const gchar *id); struct _GDataDocumentsEntryPrivate { gint64 last_viewed; gchar *resource_id; gboolean writers_can_invite; gboolean is_deleted; GDataAuthor *last_modified_by; goffset quota_used; /* bytes */ goffset file_size; /* bytes */ }; enum { PROP_EDITED = 1, PROP_LAST_VIEWED, PROP_DOCUMENT_ID, PROP_LAST_MODIFIED_BY, PROP_IS_DELETED, PROP_WRITERS_CAN_INVITE, PROP_RESOURCE_ID, PROP_QUOTA_USED, PROP_FILE_SIZE, }; G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GDataDocumentsEntry, gdata_documents_entry, GDATA_TYPE_ENTRY, G_IMPLEMENT_INTERFACE (GDATA_TYPE_ACCESS_HANDLER, gdata_documents_entry_access_handler_init)) static void gdata_documents_entry_class_init (GDataDocumentsEntryClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass); GDataEntryClass *entry_class = GDATA_ENTRY_CLASS (klass); g_type_class_add_private (klass, sizeof (GDataDocumentsEntryPrivate)); gobject_class->get_property = gdata_documents_entry_get_property; gobject_class->set_property = gdata_documents_entry_set_property; gobject_class->finalize = gdata_documents_entry_finalize; gobject_class->dispose = gdata_entry_dispose; parsable_class->parse_json = parse_json; parsable_class->post_parse_json = post_parse_json; parsable_class->get_content_type = get_content_type; parsable_class->get_json = get_json; parsable_class->get_namespaces = get_namespaces; entry_class->get_entry_uri = get_entry_uri; /** * GDataDocumentsEntry:edited: * * The last time the document was edited. If the document has not been edited yet, the content indicates the time it was created. * * Since: 0.4.0 * Deprecated: 0.17.0: This is identical to #GDataEntry:updated. */ g_object_class_install_property (gobject_class, PROP_EDITED, g_param_spec_int64 ("edited", "Edited", "The last time the document was edited.", -1, G_MAXINT64, -1, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED)); /** * GDataDocumentsEntry:last-viewed: * * The last time the document was viewed. * * Since: 0.4.0 */ g_object_class_install_property (gobject_class, PROP_LAST_VIEWED, g_param_spec_int64 ("last-viewed", "Last viewed", "The last time the document was viewed.", -1, G_MAXINT64, -1, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * GDataDocumentsEntry:writers-can-invite: * * Indicates whether the document entry writers can invite others to edit the document. * * Since: 0.4.0 */ g_object_class_install_property (gobject_class, PROP_WRITERS_CAN_INVITE, g_param_spec_boolean ("writers-can-invite", "Writers can invite?", "Indicates whether writers can invite others to edit.", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GDataDocumentsEntry:is-deleted: * * Indicates whether the document entry has been deleted (moved to the trash). Deleted documents will only * appear in query results if the #GDataDocumentsQuery:show-deleted property is %TRUE. * * Since: 0.5.0 */ g_object_class_install_property (gobject_class, PROP_IS_DELETED, g_param_spec_boolean ("is-deleted", "Deleted?", "Indicates whether the document entry has been deleted.", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GDataDocumentsEntry:resource-id: * * The resource ID of the document. This should not normally need to be used in client code, and is mostly for internal use. To uniquely * identify a given document or folder, use its #GDataEntry:id. * * Resource IDs have the form: * document|drawing|pdf|spreadsheet|presentation|folder:untyped resource ID; whereas * entry IDs have the form: * https://docs.google.com/feeds/id/resource ID in version 3 of the API. * * For more information, see the * Google Documents * API reference. * * Since: 0.11.0 */ g_object_class_install_property (gobject_class, PROP_RESOURCE_ID, g_param_spec_string ("resource-id", "Resource ID", "The resource ID of the document.", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * GDataDocumentsEntry:document-id: * * The document ID of the document, which is different from its entry ID (GDataEntry:id). The * online GData * Documentation refers to these as “untyped resource IDs”. * * Since: 0.4.0 * Deprecated: 0.11.0: This a substring of the #GDataDocumentsEntry:resource-id, which is more general and should be used instead. */ g_object_class_install_property (gobject_class, PROP_DOCUMENT_ID, g_param_spec_string ("document-id", "Document ID", "The document ID of the document.", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED)); /** * GDataDocumentsEntry:last-modified-by: * * Indicates the author of the last modification. * * Since: 0.4.0 */ g_object_class_install_property (gobject_class, PROP_LAST_MODIFIED_BY, g_param_spec_object ("last-modified-by", "Last modified by", "Indicates the author of the last modification.", GDATA_TYPE_AUTHOR, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * GDataDocumentsEntry:quota-used: * * The amount of user quota the document is occupying. Currently, only arbitrary files consume file space quota (whereas standard document * formats, such as #GDataDocumentsText, #GDataDocumentsSpreadsheet and #GDataDocumentsFolder don't). Measured in bytes. * * This property will be 0 for documents which aren't consuming any quota. * * Since: 0.13.0 */ g_object_class_install_property (gobject_class, PROP_QUOTA_USED, g_param_spec_int64 ("quota-used", "Quota used", "The amount of user quota the document is occupying.", 0, G_MAXINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** * GDataDocumentsEntry:file-size: * * The size of the document. This is only set for non-document files. * Standard formats, such as #GDataDocumentsText, * #GDataDocumentsSpreadsheet and #GDataDocumentsFolder are not binary * data and so have no size. Measured in bytes. * * Since: 0.17.7 */ g_object_class_install_property (gobject_class, PROP_FILE_SIZE, g_param_spec_int64 ("file-size", "File size", "The size of the document.", 0, G_MAXINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); } static gboolean is_owner_rule (GDataAccessRule *rule) { return (strcmp (gdata_access_rule_get_role (rule), GDATA_DOCUMENTS_ACCESS_ROLE_OWNER) == 0) ? TRUE : FALSE; } static GDataAuthorizationDomain * get_authorization_domain (GDataAccessHandler *self) { return gdata_documents_service_get_primary_authorization_domain (); } static GDataFeed * get_rules (GDataAccessHandler *self, GDataService *service, GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error) { GDataAccessHandlerIface *iface; GDataAuthorizationDomain *domain = NULL; GDataFeed *feed; GDataLink *_link; SoupMessage *message; _link = gdata_entry_look_up_link (GDATA_ENTRY (self), GDATA_LINK_ACCESS_CONTROL_LIST); g_assert (_link != NULL); iface = GDATA_ACCESS_HANDLER_GET_IFACE (self); if (iface->get_authorization_domain != NULL) { domain = iface->get_authorization_domain (self); } message = _gdata_service_query (service, domain, gdata_link_get_uri (_link), NULL, cancellable, error); if (message == NULL) { return NULL; } g_assert (message->response_body->data != NULL); feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, message->response_body->data, message->response_body->length, GDATA_TYPE_DOCUMENTS_ACCESS_RULE, progress_callback, progress_user_data, error); g_object_unref (message); return feed; } static void gdata_documents_entry_access_handler_init (GDataAccessHandlerIface *iface) { iface->is_owner_rule = is_owner_rule; iface->get_authorization_domain = get_authorization_domain; iface->get_rules = get_rules; } static void gdata_documents_entry_init (GDataDocumentsEntry *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_DOCUMENTS_ENTRY, GDataDocumentsEntryPrivate); self->priv->last_viewed = -1; } static void gdata_entry_dispose (GObject *object) { GDataDocumentsEntryPrivate *priv = GDATA_DOCUMENTS_ENTRY (object)->priv; if (priv->last_modified_by != NULL) g_object_unref (priv->last_modified_by); priv->last_modified_by = NULL; /* Chain up to the parent class */ G_OBJECT_CLASS (gdata_documents_entry_parent_class)->dispose (object); } static void gdata_documents_entry_finalize (GObject *object) { GDataDocumentsEntryPrivate *priv = GDATA_DOCUMENTS_ENTRY (object)->priv; g_free (priv->resource_id); /* Chain up to the parent class */ G_OBJECT_CLASS (gdata_documents_entry_parent_class)->finalize (object); } static void gdata_documents_entry_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GDataDocumentsEntryPrivate *priv = GDATA_DOCUMENTS_ENTRY (object)->priv; switch (property_id) { case PROP_RESOURCE_ID: g_value_set_string (value, priv->resource_id); break; case PROP_DOCUMENT_ID: g_value_set_string (value, gdata_entry_get_id (GDATA_ENTRY (object))); break; case PROP_WRITERS_CAN_INVITE: g_value_set_boolean (value, priv->writers_can_invite); break; case PROP_IS_DELETED: g_value_set_boolean (value, priv->is_deleted); break; case PROP_EDITED: g_value_set_int64 (value, gdata_entry_get_updated (GDATA_ENTRY (object))); break; case PROP_LAST_VIEWED: g_value_set_int64 (value, priv->last_viewed); break; case PROP_LAST_MODIFIED_BY: g_value_set_object (value, priv->last_modified_by); break; case PROP_QUOTA_USED: g_value_set_int64 (value, priv->quota_used); break; case PROP_FILE_SIZE: g_value_set_int64 (value, priv->file_size); break; default: /* We don't have any other property... */ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gdata_documents_entry_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GDataDocumentsEntry *self = GDATA_DOCUMENTS_ENTRY (object); switch (property_id) { case PROP_WRITERS_CAN_INVITE: gdata_documents_entry_set_writers_can_invite (self, g_value_get_boolean (value)); break; case PROP_QUOTA_USED: case PROP_FILE_SIZE: /* Read only. */ default: /* We don't have any other property... */ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void get_kind_email_and_name (JsonReader *reader, gchar **out_kind, gchar **out_email, gchar **out_name, GError **error) { GError *child_error = NULL; gboolean success; gchar *email = NULL; gchar *kind = NULL; gchar *name = NULL; guint i, members; for (i = 0, members = (guint) json_reader_count_members (reader); i < members; i++) { json_reader_read_element (reader, i); if (gdata_parser_string_from_json_member (reader, "kind", P_REQUIRED | P_NON_EMPTY, &kind, &success, &child_error) == TRUE) { if (!success && child_error != NULL) { g_propagate_prefixed_error (error, child_error, /* Translators: the parameter is an error message */ _("Error parsing JSON: %s"), "Failed to find ‘kind’."); json_reader_end_element (reader); goto out; } } if (gdata_parser_string_from_json_member (reader, "displayName", P_REQUIRED | P_NON_EMPTY, &name, &success, &child_error) == TRUE) { if (!success && child_error != NULL) { g_propagate_prefixed_error (error, child_error, /* Translators: the parameter is an error message */ _("Error parsing JSON: %s"), "Failed to find ‘displayName’."); json_reader_end_element (reader); goto out; } } if (gdata_parser_string_from_json_member (reader, "emailAddress", P_DEFAULT, &email, &success, &child_error) == TRUE) { if (!success && child_error != NULL) { g_propagate_prefixed_error (error, child_error, /* Translators: the parameter is an error message */ _("Error parsing JSON: %s"), "Failed to find ‘emailAddress’."); json_reader_end_element (reader); goto out; } } json_reader_end_element (reader); } if (out_kind != NULL) { *out_kind = kind; kind = NULL; } if (out_email != NULL) { *out_email = email; email = NULL; } if (out_name != NULL) { *out_name = name; name = NULL; } out: g_free (kind); g_free (email); g_free (name); } static void get_kind_and_parent_link (JsonReader *reader, gchar **out_kind, gchar **out_parent_link, GError **error) { GError *child_error = NULL; gboolean success; gchar *kind = NULL; gchar *parent_link = NULL; guint i, members; for (i = 0, members = (guint) json_reader_count_members (reader); i < members; i++) { json_reader_read_element (reader, i); if (gdata_parser_string_from_json_member (reader, "kind", P_REQUIRED | P_NON_EMPTY, &kind, &success, &child_error) == TRUE) { if (!success && child_error != NULL) { g_propagate_prefixed_error (error, child_error, /* Translators: the parameter is an error message */ _("Error parsing JSON: %s"), "Failed to find ‘kind’."); json_reader_end_element (reader); goto out; } } if (gdata_parser_string_from_json_member (reader, "parentLink", P_REQUIRED | P_NON_EMPTY, &parent_link, &success, &child_error) == TRUE) { if (!success && child_error != NULL) { g_propagate_prefixed_error (error, child_error, /* Translators: the parameter is an error message */ _("Error parsing JSON: %s"), "Failed to find ‘parentLink’."); json_reader_end_element (reader); goto out; } } json_reader_end_element (reader); } if (out_kind != NULL) { *out_kind = kind; kind = NULL; } if (out_parent_link != NULL) { *out_parent_link = parent_link; parent_link = NULL; } out: g_free (kind); g_free (parent_link); } static gboolean parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error) { GDataDocumentsEntryPrivate *priv = GDATA_DOCUMENTS_ENTRY (parsable)->priv; GDataCategory *category; GError *child_error = NULL; gboolean shared; gboolean success = TRUE; gchar *alternate_uri = NULL; gchar *kind = NULL; gchar *mime_type = NULL; gchar *quota_used = NULL; gchar *file_size = NULL; gint64 published; gint64 updated; /* JSON format: https://developers.google.com/drive/v2/reference/files */ if (gdata_parser_string_from_json_member (reader, "alternateLink", P_DEFAULT, &alternate_uri, &success, error) == TRUE) { if (success && alternate_uri != NULL && alternate_uri[0] != '\0') { GDataLink *_link; _link = gdata_link_new (alternate_uri, GDATA_LINK_ALTERNATE); gdata_entry_add_link (GDATA_ENTRY (parsable), _link); g_object_unref (_link); } g_free (alternate_uri); return success; } else if (gdata_parser_string_from_json_member (reader, "mimeType", P_DEFAULT, &mime_type, &success, error) == TRUE) { if (success) gdata_documents_utils_add_content_type (GDATA_DOCUMENTS_ENTRY (parsable), mime_type); g_free (mime_type); return success; } else if (gdata_parser_int64_time_from_json_member (reader, "lastViewedByMeDate", P_DEFAULT, &(priv->last_viewed), &success, error) == TRUE || gdata_parser_string_from_json_member (reader, "kind", P_REQUIRED | P_NON_EMPTY, &kind, &success, error) == TRUE) { g_free (kind); return success; } else if (gdata_parser_int64_time_from_json_member (reader, "createdDate", P_DEFAULT, &published, &success, error) == TRUE) { if (success) _gdata_entry_set_published (GDATA_ENTRY (parsable), published); return success; } else if (gdata_parser_int64_time_from_json_member (reader, "modifiedDate", P_DEFAULT, &updated, &success, error) == TRUE) { if (success) _gdata_entry_set_updated (GDATA_ENTRY (parsable), updated); return success; } else if (gdata_parser_string_from_json_member (reader, "quotaBytesUsed", P_DEFAULT, "a_used, &success, error) == TRUE) { gchar *end_ptr; guint64 val; /* Even though ‘quotaBytesUsed’ is documented as long, * it is actually a string in the JSON. */ val = g_ascii_strtoull (quota_used, &end_ptr, 10); if (*end_ptr == '\0') priv->quota_used = (goffset) val; g_free (quota_used); return success; } else if (gdata_parser_string_from_json_member (reader, "fileSize", P_DEFAULT, &file_size, &success, error) == TRUE) { gchar *end_ptr; guint64 val; /* like 'quotaBytesUsed', 'fileSize' is also a string * in the JSON. */ val = g_ascii_strtoull (file_size, &end_ptr, 10); if (*end_ptr == '\0') priv->file_size = (goffset) val; g_free (file_size); return success; } else if (gdata_parser_boolean_from_json_member (reader, "shared", P_DEFAULT, &shared, &success, error) == TRUE) { if (success && shared) { category = gdata_category_new ("http://schemas.google.com/g/2005/labels#shared", GDATA_CATEGORY_SCHEMA_LABELS, "shared"); gdata_entry_add_category (GDATA_ENTRY (parsable), category); g_object_unref (category); } return success; } else if (g_strcmp0 (json_reader_get_member_name (reader), "labels") == 0) { guint i, members; if (json_reader_is_object (reader) == FALSE) { g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, /* Translators: the parameter is an error message */ _("Error parsing JSON: %s"), "JSON node ‘labels’ is not an object."); return FALSE; } for (i = 0, members = (guint) json_reader_count_members (reader); i < members; i++) { gboolean starred; gboolean viewed; json_reader_read_element (reader, i); gdata_parser_boolean_from_json_member (reader, "starred", P_DEFAULT, &starred, &success, NULL); if (success && starred) { category = gdata_category_new (GDATA_CATEGORY_SCHEMA_LABELS_STARRED, GDATA_CATEGORY_SCHEMA_LABELS, "starred"); gdata_entry_add_category (GDATA_ENTRY (parsable), category); g_object_unref (category); } gdata_parser_boolean_from_json_member (reader, "viewed", P_DEFAULT, &viewed, &success, NULL); if (success && viewed) { category = gdata_category_new ("http://schemas.google.com/g/2005/labels#viewed", GDATA_CATEGORY_SCHEMA_LABELS, "viewed"); gdata_entry_add_category (GDATA_ENTRY (parsable), category); g_object_unref (category); } json_reader_end_element (reader); } return TRUE; } else if (g_strcmp0 (json_reader_get_member_name (reader), "owners") == 0) { guint i, elements; if (json_reader_is_array (reader) == FALSE) { g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, /* Translators: the parameter is an error message */ _("Error parsing JSON: %s"), "JSON node ‘owners’ is not an array."); return FALSE; } /* Loop through the elements array. */ for (i = 0, elements = json_reader_count_elements (reader); success && i < elements; i++) { gchar *email = NULL; gchar *name = NULL; json_reader_read_element (reader, i); if (json_reader_is_object (reader) == FALSE) { g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, /* Translators: the parameter is an error message */ _("Error parsing JSON: %s"), "JSON node inside ‘owners’ is not an object."); success = FALSE; goto continue_owners; } get_kind_email_and_name (reader, &kind, &email, &name, &child_error); if (child_error != NULL) { g_propagate_error (error, child_error); success = FALSE; goto continue_owners; } if (name == NULL || name[0] == '\0') { g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, /* Translators: the parameter is an error message */ _("Error parsing JSON: %s"), "Failed to find ‘displayName’."); success = FALSE; goto continue_owners; } if (g_strcmp0 (kind, "drive#user") == 0) { GDataAuthor *author; author = gdata_author_new (name, NULL, email); gdata_entry_add_author (GDATA_ENTRY (parsable), author); g_object_unref (author); } else { g_warning ("%s authors are not handled yet", kind); } continue_owners: g_free (email); g_free (kind); g_free (name); json_reader_end_element (reader); } return success; } else if (g_strcmp0 (json_reader_get_member_name (reader), "parents") == 0) { guint i, elements; if (json_reader_is_array (reader) == FALSE) { g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, /* Translators: the parameter is an error message */ _("Error parsing JSON: %s"), "JSON node ‘parents’ is not an array."); return FALSE; } /* Loop through the elements array. */ for (i = 0, elements = (guint) json_reader_count_elements (reader); success && i < elements; i++) { GDataLink *_link = NULL; const gchar *relation_type = NULL; gchar *uri = NULL; json_reader_read_element (reader, i); if (json_reader_is_object (reader) == FALSE) { g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, /* Translators: the parameter is an error message */ _("Error parsing JSON: %s"), "JSON node inside ‘parents’ is not an object."); success = FALSE; goto continue_parents; } get_kind_and_parent_link (reader, &kind, &uri, &child_error); if (child_error != NULL) { g_propagate_error (error, child_error); success = FALSE; goto continue_parents; } if (g_strcmp0 (kind, "drive#parentReference") == 0) { relation_type = GDATA_LINK_PARENT; } else { g_warning ("%s parents are not handled yet", kind); } if (relation_type == NULL) goto continue_parents; _link = gdata_link_new (uri, relation_type); gdata_entry_add_link (GDATA_ENTRY (parsable), _link); continue_parents: g_clear_object (&_link); g_free (kind); g_free (uri); json_reader_end_element (reader); } return success; } return GDATA_PARSABLE_CLASS (gdata_documents_entry_parent_class)->parse_json (parsable, reader, user_data, error); } static gboolean post_parse_json (GDataParsable *parsable, gpointer user_data, GError **error) { GDataDocumentsEntryPrivate *priv = GDATA_DOCUMENTS_ENTRY (parsable)->priv; GDataLink *_link; const gchar *id; gchar *uri; id = gdata_entry_get_id (GDATA_ENTRY (parsable)); /* gdata_access_handler_get_rules requires the presence of a GDATA_LINK_ACCESS_CONTROL_LIST link with the * right URI. */ uri = g_strconcat ("https://www.googleapis.com/drive/v2/files/", id, "/permissions", NULL); _link = gdata_link_new (uri, GDATA_LINK_ACCESS_CONTROL_LIST); gdata_entry_add_link (GDATA_ENTRY (parsable), _link); g_free (uri); g_object_unref (_link); /* Since the document-id is identical to GDataEntry:id, which is parsed by the parent class, we can't * create the resource-id while parsing. */ priv->resource_id = g_strconcat ("document:", id, NULL); return TRUE; } static const gchar * get_content_type (void) { return "application/json"; } static void get_json (GDataParsable *parsable, JsonBuilder *builder) { GList *i; GList *parent_folders_list; const gchar *mime_type; GDATA_PARSABLE_CLASS (gdata_documents_entry_parent_class)->get_json (parsable, builder); /* Inserting files: https://developers.google.com/drive/v2/reference/files/insert */ mime_type = gdata_documents_utils_get_content_type (GDATA_DOCUMENTS_ENTRY (parsable)); if (mime_type != NULL) { json_builder_set_member_name (builder, "mimeType"); json_builder_add_string_value (builder, mime_type); } /* Upload to a folder: https://developers.google.com/drive/v2/web/folder */ json_builder_set_member_name (builder, "parents"); json_builder_begin_array (builder); parent_folders_list = gdata_entry_look_up_links (GDATA_ENTRY (parsable), GDATA_LINK_PARENT); for (i = parent_folders_list; i != NULL; i = i->next) { GDataLink *_link = GDATA_LINK (i->data); const gchar *id; id = gdata_documents_utils_get_id_from_link (_link); if (id != NULL) { json_builder_begin_object (builder); json_builder_set_member_name (builder, "kind"); json_builder_add_string_value (builder, "drive#fileLink"); json_builder_set_member_name (builder, "id"); json_builder_add_string_value (builder, id); json_builder_end_object (builder); } } json_builder_end_array (builder); g_list_free (parent_folders_list); } static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces) { /* Chain up to the parent class */ GDATA_PARSABLE_CLASS (gdata_documents_entry_parent_class)->get_namespaces (parsable, namespaces); g_hash_table_insert (namespaces, (gchar*) "docs", (gchar*) "http://schemas.google.com/docs/2007"); } static gchar * get_entry_uri (const gchar *id) { return g_strconcat ("https://www.googleapis.com/drive/v2/files/", id, NULL); } /** * gdata_documents_entry_get_edited: * @self: a #GDataDocumentsEntry * * Gets the #GDataDocumentsEntry:edited property. If the property is unset, -1 will be returned. * * Return value: the UNIX timestamp for the time the document was last edited, or -1 * * Since: 0.4.0 * Deprecated: 0.17.0: Use gdata_entry_get_updated() instead. See #GDataDocumentsEntry:edited. */ gint64 gdata_documents_entry_get_edited (GDataDocumentsEntry *self) { g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self), -1); return gdata_entry_get_updated (GDATA_ENTRY (self)); } /** * gdata_documents_entry_get_last_viewed: * @self: a #GDataDocumentsEntry * * Gets the #GDataDocumentsEntry:last-viewed property. If the property is unset, -1 will be returned. * * Return value: the UNIX timestamp for the time the document was last viewed, or -1 * * Since: 0.4.0 */ gint64 gdata_documents_entry_get_last_viewed (GDataDocumentsEntry *self) { g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self), -1); return self->priv->last_viewed; } /** * gdata_documents_entry_get_path: * @self: a #GDataDocumentsEntry * * Builds a path for the #GDataDocumentsEntry, starting from a root node and traversing the folders containing the document, then ending with the * document's ID. * * An example path would be: /folder_id1/folder_id2/document_id. * * Note: the path is based on the entry/document IDs of the folders (#GDataEntry:id) and document (#GDataDocumentsEntry:document-id), * and not the entries' human-readable names (#GDataEntry:title). * * Return value: the folder hierarchy path containing the document, or %NULL; free with g_free() * * Since: 0.4.0 */ gchar * gdata_documents_entry_get_path (GDataDocumentsEntry *self) { GList *element, *parent_folders_list = NULL; GString *path; const gchar *id; g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self), NULL); path = g_string_new ("/"); parent_folders_list = gdata_entry_look_up_links (GDATA_ENTRY (self), GDATA_LINK_PARENT); /* We check all the folders contained that are parents of the GDataDocumentsEntry */ for (element = parent_folders_list; element != NULL; element = element->next) { guint i; gchar *folder_id = NULL; gchar **link_href_cut = g_strsplit (gdata_link_get_uri (GDATA_LINK (element->data)), "/", 0); /* Extract the folder ID from the folder URI, which is of the form: * http://docs.google.com/feeds/documents/private/full/folder%3Afolder_id * We want the "folder_id" bit. */ for (i = 0;; i++) { gchar **path_cut = NULL; if (link_href_cut[i] == NULL) break; path_cut = g_strsplit (link_href_cut[i], "%3A", 2); if (*path_cut != NULL) { if (strcmp (path_cut[0], "folder") == 0){ folder_id = g_strdup (path_cut[1]); g_strfreev (path_cut); break; } } g_strfreev (path_cut); } g_strfreev (link_href_cut); g_assert (folder_id != NULL); /* Append the folder ID to our path */ g_string_append (path, folder_id); g_string_append_c (path, '/'); g_free (folder_id); } /* Append the entry ID */ id = gdata_entry_get_id (GDATA_ENTRY (self)); g_string_append (path, id); return g_string_free (path, FALSE); } /** * gdata_documents_entry_get_document_id: * @self: a #GDataDocumentsEntry * * Gets the #GDataDocumentsEntry:document-id property. The * online GData Documentation * refers to these as “untyped resource IDs”. * * Return value: the document's document ID * * Since: 0.4.0 * Deprecated: 0.11.0: Use gdata_documents_entry_get_resource_id() instead. See #GDataDocumentsEntry:document-id. */ const gchar * gdata_documents_entry_get_document_id (GDataDocumentsEntry *self ) { g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self), NULL); return gdata_entry_get_id (GDATA_ENTRY (self)); } /** * gdata_documents_entry_get_resource_id: * @self: a #GDataDocumentsEntry * * Gets the #GDataDocumentsEntry:resource-id property. * * Return value: the document's resource ID * * Since: 0.11.0 */ const gchar * gdata_documents_entry_get_resource_id (GDataDocumentsEntry *self) { g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self), NULL); return self->priv->resource_id; } /** * gdata_documents_entry_set_writers_can_invite: * @self: a #GDataDocumentsEntry * @writers_can_invite: %TRUE if writers can invite other people to edit the document, %FALSE otherwise * * Sets the #GDataDocumentsEntry:writers-can-invite property to @writers_can_invite. * * Since: 0.4.0 */ void gdata_documents_entry_set_writers_can_invite (GDataDocumentsEntry *self, gboolean writers_can_invite) { g_return_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self)); self->priv->writers_can_invite = writers_can_invite; g_object_notify (G_OBJECT (self), "writers-can-invite"); } /** * gdata_documents_entry_writers_can_invite: * @self: a #GDataDocumentsEntry * * Gets the #GDataDocumentsEntry:writers-can-invite property. * * Return value: %TRUE if writers can invite other people to edit the document, %FALSE otherwise * * Since: 0.4.0 */ gboolean gdata_documents_entry_writers_can_invite (GDataDocumentsEntry *self) { g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self ), FALSE); return self->priv->writers_can_invite; } /** * gdata_documents_entry_get_last_modified_by: * @self: a #GDataDocumentsEntry * * Gets the #GDataDocumentsEntry:last-modified-by property. * * Return value: (transfer none): the author who last modified the document * * Since: 0.4.0 */ GDataAuthor * gdata_documents_entry_get_last_modified_by (GDataDocumentsEntry *self) { g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self), NULL); return self->priv->last_modified_by; } /** * gdata_documents_entry_get_quota_used: * @self: a #GDataDocumentsEntry * * Gets the #GDataDocumentsEntry:quota-used property. * * Return value: the number of quota bytes used by the document * * Since: 0.13.0 */ goffset gdata_documents_entry_get_quota_used (GDataDocumentsEntry *self) { g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self), 0); return self->priv->quota_used; } /** * gdata_documents_entry_get_file_size: * @self: a #GDataDocumentsEntry * * Gets the #GDataDocumentsEntry:file-size property. * * Return value: the size of the document in bytes * * Since: 0.17.7 */ goffset gdata_documents_entry_get_file_size (GDataDocumentsEntry *self) { g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self), 0); return self->priv->file_size; } /** * gdata_documents_entry_is_deleted: * @self: a #GDataDocumentsEntry * * Gets the #GDataDocumentsEntry:is-deleted property. * * Return value: %TRUE if the document has been deleted, %FALSE otherwise * * Since: 0.5.0 */ gboolean gdata_documents_entry_is_deleted (GDataDocumentsEntry *self) { g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (self), FALSE); return self->priv->is_deleted; }