From 0bef6dd9126fbc59e48d4aa629f51035fd33ec4c Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Tue, 6 Apr 2021 19:10:53 +0200 Subject: [PATCH 01/45] require glib 2.68 This is needed in order to be able to use g_memdup2 as well as newer gobject APIs. These are required in order to be able to cleanly use libsoup3, as libsoup3 relies on much newer glib. --- meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 68eb0b1a..dac09e72 100644 --- a/meson.build +++ b/meson.build @@ -76,14 +76,14 @@ cc = meson.get_compiler('c') # Requirements gdata_deps = [ dependency('gobject-2.0'), - dependency('glib-2.0', version: '>= 2.62.0'), + dependency('glib-2.0', version: '>= 2.68'), dependency('gio-2.0', version: '>= 2.44.0'), dependency('json-glib-1.0', version: '>= 0.15'), dependency('libxml-2.0'), ] common_c_args = [ - '-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_62', + '-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_68', '-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_68', ] -- GitLab From 14d020677e941d0d6ed8c32e9cbcba521edebce6 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 28 Oct 2021 17:10:35 +0200 Subject: [PATCH 02/45] switch to uhttpmock-1.0 --- gdata/tests/common.c | 14 ++++++++------ gdata/tests/common.h | 4 ++-- meson.build | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/gdata/tests/common.c b/gdata/tests/common.c index 21090044..529afc66 100644 --- a/gdata/tests/common.c +++ b/gdata/tests/common.c @@ -985,12 +985,13 @@ gdata_test_mock_server_start_trace (UhmServer *server, const gchar *trace_filena * Since: 0.13.4 */ gboolean -gdata_test_mock_server_handle_message_error (UhmServer *server, SoupMessage *message, SoupClientContext *client, gpointer user_data) +gdata_test_mock_server_handle_message_error (UhmServer *server, UhmMessage *message, gpointer user_data) { const GDataTestRequestErrorData *data = user_data; - soup_message_set_status_full (message, data->status_code, data->reason_phrase); - soup_message_body_append (message->response_body, SOUP_MEMORY_STATIC, data->message_body, strlen (data->message_body)); + uhm_message_set_status (message, SOUP_STATUS_PRECONDITION_FAILED, data->reason_phrase); + soup_message_body_append (uhm_message_get_response_body (message), SOUP_MEMORY_STATIC, "This is a failure response.", strlen ("This is a failure response.") + 1); + soup_message_body_complete (uhm_message_get_response_body (message)); return TRUE; } @@ -1009,13 +1010,14 @@ gdata_test_mock_server_handle_message_error (UhmServer *server, SoupMessage *mes * Since: 0.13.4 */ gboolean -gdata_test_mock_server_handle_message_timeout (UhmServer *server, SoupMessage *message, SoupClientContext *client, gpointer user_data) +gdata_test_mock_server_handle_message_timeout (UhmServer *server, UhmMessage *message, gpointer user_data) { /* Sleep for longer than the timeout set on the client. */ g_usleep (2 * G_USEC_PER_SEC); - soup_message_set_status_full (message, SOUP_STATUS_REQUEST_TIMEOUT, "Request Timeout"); - soup_message_body_append (message->response_body, SOUP_MEMORY_STATIC, "Request timed out.", strlen ("Request timed out.")); + uhm_message_set_status (message, SOUP_STATUS_REQUEST_TIMEOUT, "Request Timeout"); + soup_message_body_append (uhm_message_get_response_body (message), SOUP_MEMORY_STATIC, "Request timed out.", strlen ("Request timed out.") + 1); + soup_message_body_complete (uhm_message_get_response_body (message)); return TRUE; } diff --git a/gdata/tests/common.h b/gdata/tests/common.h index 92ff7538..ec65fd1a 100644 --- a/gdata/tests/common.h +++ b/gdata/tests/common.h @@ -334,8 +334,8 @@ typedef struct { void gdata_test_set_https_port (UhmServer *server); void gdata_test_mock_server_start_trace (UhmServer *server, const gchar *trace_filename); -gboolean gdata_test_mock_server_handle_message_error (UhmServer *server, SoupMessage *message, SoupClientContext *client, gpointer user_data); -gboolean gdata_test_mock_server_handle_message_timeout (UhmServer *server, SoupMessage *message, SoupClientContext *client, gpointer user_data); +gboolean gdata_test_mock_server_handle_message_error (UhmServer *server, UhmMessage *message, gpointer user_data); +gboolean gdata_test_mock_server_handle_message_timeout (UhmServer *server, UhmMessage *message, gpointer user_data); gchar *gdata_test_query_user_for_verifier (const gchar *authentication_uri) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; diff --git a/meson.build b/meson.build index dac09e72..d2f2d625 100644 --- a/meson.build +++ b/meson.build @@ -130,7 +130,7 @@ always_build_tests = get_option('always_build_tests') install_tests = get_option('installed_tests') if always_build_tests - libuhttpmock_dep = dependency('libuhttpmock-0.0', version: '>= 0.5.0') + libuhttpmock_dep = dependency('libuhttpmock-1.0', version: '>= 0.5.0') gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0', version: '>= 2.14') config_h.set('HAVE_GDK_PIXBUF', gdk_pixbuf_dep.found()) -- GitLab From 2e1b8b1f56fd305679c75059184e0339460c7da1 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Fri, 8 Jul 2022 14:16:26 +0200 Subject: [PATCH 03/45] ci: Build against libsoup3 Build the soup3-uhttpmock until it's widely available. --- .gitlab-ci.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0a2bb878..e3bd4c66 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,10 +7,9 @@ variables: glib-networking libxml2-devel gtk3-devel - libsoup-devel + libsoup3-devel gcr-devel gnome-online-accounts-devel - uhttpmock-devel gtk-doc gobject-introspection-devel meson @@ -19,9 +18,11 @@ variables: gcc-c++ glibc-devel vala + git libabigail OLD_ABI_DEPENDENCIES: liboauth-devel + libsoup-devel LAST_ABI_BREAK: 27fb43ff72435854984f1c4ed35deff96d3c652a build_stable: @@ -31,6 +32,11 @@ build_stable: - dnf update -y --nogpgcheck - dnf install -y --nogpgcheck $DEPENDENCIES script: + - git clone https://gitlab.freedesktop.org/pwithnall/uhttpmock.git + - pushd uhttpmock + - meson . _build --prefix=/usr -Dintrospection=false -Dvapi=disabled -Dgtk_doc=false + - ninja -C _build install + - popd - meson . _build --prefix=/usr -Dgtk=enabled -Dgnome=enabled @@ -46,4 +52,4 @@ build_stable: - ninja -C _build test - curl https://gitlab.freedesktop.org/hadess/check-abi/-/raw/main/contrib/check-abi-fedora.sh | bash - dnf install -y $OLD_ABI_DEPENDENCIES - - check-abi --suppr=.ci/gdata.suppr --parameters="-Dgtk=enabled -Dgnome=enabled -Dgoa=enabled -Dgtk_doc=false -Dintrospection=false" ${LAST_ABI_BREAK} $(git rev-parse HEAD) + - check-abi --suppr=.ci/gdata.suppr --parameters="-Dgtk=enabled -Dgnome=enabled -Dgoa=enabled -Dgtk_doc=false -Dintrospection=false -Dalways_build_tests=false" ${LAST_ABI_BREAK} $(git rev-parse HEAD) -- GitLab From 4c9924aae4afbc2f44bc5edcd926e0ebfd192dd3 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:10 +0100 Subject: [PATCH 04/45] soup3: mechanical replacement message->foo -> soup_message_get_... --- gdata/gdata-access-handler.c | 2 +- gdata/gdata-batch-operation.c | 3 +- gdata/gdata-download-stream.c | 20 ++++---- gdata/gdata-goa-authorizer.c | 2 +- gdata/gdata-oauth2-authorizer.c | 14 +++--- gdata/gdata-service.c | 48 +++++++++++-------- .../documents/gdata-documents-service.c | 16 ++++--- gdata/tests/authorization.c | 14 +++--- gdata/tests/gdata-dummy-authorizer.c | 2 +- gdata/tests/oauth2-authorizer.c | 6 +-- 10 files changed, 72 insertions(+), 55 deletions(-) diff --git a/gdata/gdata-access-handler.c b/gdata/gdata-access-handler.c index 8b044ecf..a754d319 100644 --- a/gdata/gdata-access-handler.c +++ b/gdata/gdata-access-handler.c @@ -111,7 +111,7 @@ gdata_access_handler_real_get_rules (GDataAccessHandler *self, g_assert (message->response_body->data != NULL); - headers = message->response_headers; + headers = soup_message_get_response_headers (message); content_type = soup_message_headers_get_content_type (headers, NULL); if (g_strcmp0 (content_type, "application/json") == 0) { diff --git a/gdata/gdata-batch-operation.c b/gdata/gdata-batch-operation.c index 212fcb0b..06ec4b2f 100644 --- a/gdata/gdata-batch-operation.c +++ b/gdata/gdata-batch-operation.c @@ -689,7 +689,8 @@ gdata_batch_operation_run (GDataBatchOperation *self, GCancellable *cancellable, /* Error */ GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (priv->service); g_assert (klass->parse_error_response != NULL); - klass->parse_error_response (priv->service, GDATA_OPERATION_BATCH, status, message->reason_phrase, message->response_body->data, + klass->parse_error_response (priv->service, GDATA_OPERATION_BATCH, status, + soup_message_get_reaspon_phrase (message), message->response_body->data, message->response_body->length, &child_error); } g_object_unref (message); diff --git a/gdata/gdata-download-stream.c b/gdata/gdata-download-stream.c index a6d64316..b9870ed2 100644 --- a/gdata/gdata-download-stream.c +++ b/gdata/gdata-download-stream.c @@ -546,12 +546,14 @@ gdata_download_stream_read (GInputStream *stream, void *buffer, gsize count, GCa length_read = -1; goto done; - } else if (SOUP_STATUS_IS_SUCCESSFUL (priv->message->status_code) == FALSE) { + } else if (SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (priv->message)) == FALSE) { GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (priv->service); /* Set an appropriate error */ g_assert (klass->parse_error_response != NULL); - klass->parse_error_response (priv->service, GDATA_OPERATION_DOWNLOAD, priv->message->status_code, priv->message->reason_phrase, + klass->parse_error_response (priv->service, GDATA_OPERATION_DOWNLOAD, + soup_message_get_status (priv->message), + soup_message_get_reason_phrase (priv->message), NULL, 0, &child_error); length_read = -1; @@ -820,13 +822,13 @@ got_headers_cb (SoupMessage *message, GDataDownloadStream *self) /* Don't get the client's hopes up by setting the Content-Type or -Length if the response * is actually unsuccessful. */ - if (SOUP_STATUS_IS_SUCCESSFUL (message->status_code) == FALSE) + if (SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (message)) == FALSE) return; g_mutex_lock (&(self->priv->content_mutex)); - self->priv->content_type = g_strdup (soup_message_headers_get_content_type (message->response_headers, NULL)); - self->priv->content_length = soup_message_headers_get_content_length (message->response_headers); - if (soup_message_headers_get_content_range (message->response_headers, &start, &end, &total_length)) { + self->priv->content_type = g_strdup (soup_message_headers_get_content_type (soup_message_get_response_headers (message), NULL)); + self->priv->content_length = soup_message_headers_get_content_length (soup_message_get_response_headers (message)); + if (soup_message_headers_get_content_range (soup_message_get_response_headers (message), &start, &end, &total_length)) { self->priv->content_length = (gssize) total_length; } g_mutex_unlock (&(self->priv->content_mutex)); @@ -842,7 +844,7 @@ static void got_chunk_cb (SoupMessage *message, SoupBuffer *buffer, GDataDownloadStream *self) { /* Ignore the chunk if the response is unsuccessful or it has zero length */ - if (SOUP_STATUS_IS_SUCCESSFUL (message->status_code) == FALSE || buffer->length == 0) + if (SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (message)) == FALSE || buffer->length == 0) return; /* Push the data onto the buffer immediately */ @@ -878,9 +880,9 @@ download_thread (GDataDownloadStream *self) /* Set a Range header if our starting offset is non-zero */ if (priv->offset > 0) { - soup_message_headers_set_range (priv->message->request_headers, priv->offset, -1); + soup_message_headers_set_range (soup_message_get_request_headers (priv->message), priv->offset, -1); } else { - soup_message_headers_remove (priv->message->request_headers, "Range"); + soup_message_headers_remove (soup_message_get_request_headers (priv->message), "Range"); } _gdata_service_actually_send_message (priv->session, priv->message, priv->network_cancellable, NULL); diff --git a/gdata/gdata-goa-authorizer.c b/gdata/gdata-goa-authorizer.c index 88c014e9..525adef7 100644 --- a/gdata/gdata-goa-authorizer.c +++ b/gdata/gdata-goa-authorizer.c @@ -118,7 +118,7 @@ gdata_goa_authorizer_add_oauth2_authorization (GDataAuthorizer *authorizer, Soup g_string_append (authorization, priv->access_token); /* Use replace here, not append, to make sure there's only one "Authorization" header. */ - soup_message_headers_replace (message->request_headers, "Authorization", authorization->str); + soup_message_headers_replace (soup_message_get_request_headers (message), "Authorization", authorization->str); g_string_free (authorization, TRUE); } diff --git a/gdata/gdata-oauth2-authorizer.c b/gdata/gdata-oauth2-authorizer.c index b81f0d42..b56a16dd 100644 --- a/gdata/gdata-oauth2-authorizer.c +++ b/gdata/gdata-oauth2-authorizer.c @@ -626,7 +626,7 @@ sign_message_locked (GDataOAuth2Authorizer *self, SoupMessage *message, /* Add the authorisation header. */ auth_header = g_strdup_printf ("Bearer %s", access_token); - soup_message_headers_append (message->request_headers, + soup_message_headers_append (soup_message_get_request_headers (message), "Authorization", auth_header); g_free (auth_header); } @@ -679,7 +679,7 @@ refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, /* Send the message */ _gdata_service_actually_send_message (priv->session, message, cancellable, error); - status = message->status_code; + status = soup_message_get_status (message); if (status == SOUP_STATUS_CANCELLED) { /* Cancelled (the error has already been set) */ @@ -687,7 +687,7 @@ refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, return FALSE; } else if (status != SOUP_STATUS_OK) { parse_grant_error (GDATA_OAUTH2_AUTHORIZER (self), - status, message->reason_phrase, + status, soup_message_get_reason_phrase (message), message->response_body->data, message->response_body->length, error); @@ -700,7 +700,7 @@ refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, /* Parse and handle the response */ parse_grant_response (GDATA_OAUTH2_AUTHORIZER (self), - status, message->reason_phrase, + status, soup_message_get_reason_phrase (message), message->response_body->data, message->response_body->length, &child_error); @@ -1195,14 +1195,14 @@ gdata_oauth2_authorizer_request_authorization (GDataOAuth2Authorizer *self, /* Send the message */ _gdata_service_actually_send_message (priv->session, message, cancellable, error); - status = message->status_code; + status = soup_message_get_status (message); if (status == SOUP_STATUS_CANCELLED) { /* Cancelled (the error has already been set) */ g_object_unref (message); return FALSE; } else if (status != SOUP_STATUS_OK) { - parse_grant_error (self, status, message->reason_phrase, + parse_grant_error (self, status, soup_message_get_reason_phrase (message), message->response_body->data, message->response_body->length, error); @@ -1214,7 +1214,7 @@ gdata_oauth2_authorizer_request_authorization (GDataOAuth2Authorizer *self, g_assert (message->response_body->data != NULL); /* Parse and handle the response */ - parse_grant_response (self, status, message->reason_phrase, + parse_grant_response (self, status, soup_message_get_reason_phrase (message), message->response_body->data, message->response_body->length, &child_error); diff --git a/gdata/gdata-service.c b/gdata/gdata-service.c index 6e1f0d40..532f00fc 100644 --- a/gdata/gdata-service.c +++ b/gdata/gdata-service.c @@ -305,11 +305,11 @@ real_append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, } /* Set the GData-Version header to tell it we want to use the v2 API */ - soup_message_headers_append (message->request_headers, "GData-Version", GDATA_SERVICE_GET_CLASS (self)->api_version); + soup_message_headers_append (soup_message_get_request_headers (message), "GData-Version", GDATA_SERVICE_GET_CLASS (self)->api_version); /* Set the locale, if it's been set for the service */ if (self->priv->locale != NULL) - soup_message_headers_append (message->request_headers, "Accept-Language", self->priv->locale); + soup_message_headers_append (soup_message_get_request_headers (message), "Accept-Language", self->priv->locale); } static void @@ -565,7 +565,7 @@ _gdata_service_build_message (GDataService *self, GDataAuthorizationDomain *doma /* Append the ETag header if possible */ if (etag != NULL) - soup_message_headers_append (message->request_headers, (etag_if_match == TRUE) ? "If-Match" : "If-None-Match", etag); + soup_message_headers_append (soup_message_get_request_headers (message), (etag_if_match == TRUE) ? "If-Match" : "If-None-Match", etag); return message; } @@ -602,6 +602,7 @@ _gdata_service_actually_send_message (SoupSession *session, SoupMessage *message { MessageData data; gulong cancel_signal = 0, request_queued_signal = 0; + guint status; /* Hold references to the session and message so they can't be freed by other threads. For example, if the SoupSession was freed by another * thread while we were making a request, the request would be unexpectedly cancelled. See bgo#650835 for an example of this breaking things. @@ -656,11 +657,12 @@ _gdata_service_actually_send_message (SoupSession *session, SoupMessage *message * libsoup may internally cancel messages if, for example, the proxy URI of the SoupSession is changed. * libsoup also sometimes seems to return a SOUP_STATUS_IO_ERROR when we cancel a message, even though we've specified SOUP_STATUS_CANCELLED * at cancellation time. Ho Hum. */ - g_assert (message->status_code != SOUP_STATUS_NONE); + status = soup_message_get_status (message); + g_assert (status != SOUP_STATUS_NONE); - if (message->status_code == SOUP_STATUS_CANCELLED || - ((message->status_code == SOUP_STATUS_IO_ERROR || message->status_code == SOUP_STATUS_SSL_FAILED || - message->status_code == SOUP_STATUS_CANT_CONNECT || message->status_code == SOUP_STATUS_CANT_RESOLVE) && + if (status == SOUP_STATUS_CANCELLED || + ((status == SOUP_STATUS_IO_ERROR || status == SOUP_STATUS_SSL_FAILED || + status == SOUP_STATUS_CANT_CONNECT || status == SOUP_STATUS_CANT_RESOLVE) && cancellable != NULL && g_cancellable_is_cancelled (cancellable) == TRUE)) { /* We hackily create and cancel a new GCancellable so that we can set the error using it and therefore save ourselves a translatable * string and the associated maintenance. */ @@ -693,11 +695,11 @@ _gdata_service_send_message (GDataService *self, SoupMessage *message, GCancella soup_message_set_flags (message, 0); /* Handle redirections specially so we don't lose our custom headers when making the second request */ - if (SOUP_STATUS_IS_REDIRECTION (message->status_code)) { + if (SOUP_STATUS_IS_REDIRECTION (soup_message_get_status (message))) { SoupURI *new_uri; const gchar *new_location; - new_location = soup_message_headers_get_one (message->response_headers, "Location"); + new_location = soup_message_headers_get_one (soup_message_get_response_headers (message), "Location"); g_return_val_if_fail (new_location != NULL, SOUP_STATUS_NONE); new_uri = soup_uri_new_with_base (soup_message_get_uri (message), new_location); @@ -725,9 +727,9 @@ _gdata_service_send_message (GDataService *self, SoupMessage *message, GCancella * * Note that we have to re-process the message with the authoriser so that its authorisation headers get updated after the refresh * (bgo#653535). */ - if (message->status_code == SOUP_STATUS_UNAUTHORIZED || - message->status_code == SOUP_STATUS_FORBIDDEN || - message->status_code == SOUP_STATUS_NOT_FOUND) { + if (soup_message_get_status (message) == SOUP_STATUS_UNAUTHORIZED || + soup_message_get_status (message) == SOUP_STATUS_FORBIDDEN || + soup_message_get_status (message) == SOUP_STATUS_NOT_FOUND) { GDataAuthorizer *authorizer = self->priv->authorizer; if (authorizer != NULL && gdata_authorizer_refresh_authorization (authorizer, cancellable, NULL) == TRUE) { @@ -745,7 +747,7 @@ _gdata_service_send_message (GDataService *self, SoupMessage *message, GCancella } } - return message->status_code; + return soup_message_get_status (message); } typedef struct { @@ -906,7 +908,9 @@ _gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, cons /* Error */ GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self); g_assert (klass->parse_error_response != NULL); - klass->parse_error_response (self, GDATA_OPERATION_QUERY, status, message->reason_phrase, message->response_body->data, + klass->parse_error_response (self, GDATA_OPERATION_QUERY, status, + soup_message_get_reason_phrase (message), + message->response_body->data, message->response_body->length, error); g_object_unref (message); return NULL; @@ -967,7 +971,7 @@ real_parse_feed (GDataService *self, const gchar *content_type; klass = GDATA_SERVICE_GET_CLASS (self); - headers = message->response_headers; + headers = soup_message_get_response_headers (message); content_type = soup_message_headers_get_content_type (headers, NULL); if (content_type != NULL && strcmp (content_type, "application/json") == 0) { @@ -1119,7 +1123,7 @@ gdata_service_query_single_entry (GDataService *self, GDataAuthorizationDomain * g_assert (message->response_body->data != NULL); - headers = message->response_headers; + headers = soup_message_get_response_headers (message); content_type = soup_message_headers_get_content_type (headers, NULL); if (g_strcmp0 (content_type, "application/json") == 0) { @@ -1423,7 +1427,9 @@ gdata_service_insert_entry (GDataService *self, GDataAuthorizationDomain *domain /* Error: for XML APIs Google returns CREATED and for JSON it returns OK. */ GDataServiceClass *service_klass = GDATA_SERVICE_GET_CLASS (self); g_assert (service_klass->parse_error_response != NULL); - service_klass->parse_error_response (self, GDATA_OPERATION_INSERTION, status, message->reason_phrase, message->response_body->data, + service_klass->parse_error_response (self, GDATA_OPERATION_INSERTION, status, + soup_message_get_reason_phrase (message), + message->response_body->data, message->response_body->length, error); g_object_unref (message); return NULL; @@ -1614,7 +1620,9 @@ gdata_service_update_entry (GDataService *self, GDataAuthorizationDomain *domain /* Error */ GDataServiceClass *service_klass = GDATA_SERVICE_GET_CLASS (self); g_assert (service_klass->parse_error_response != NULL); - service_klass->parse_error_response (self, GDATA_OPERATION_UPDATE, status, message->reason_phrase, message->response_body->data, + service_klass->parse_error_response (self, GDATA_OPERATION_UPDATE, status, + soup_message_get_reason_phrase (message), + message->response_body->data, message->response_body->length, error); g_object_unref (message); return NULL; @@ -1794,7 +1802,9 @@ gdata_service_delete_entry (GDataService *self, GDataAuthorizationDomain *domain /* Error */ GDataServiceClass *service_klass = GDATA_SERVICE_GET_CLASS (self); g_assert (service_klass->parse_error_response != NULL); - service_klass->parse_error_response (self, GDATA_OPERATION_DELETION, status, message->reason_phrase, message->response_body->data, + service_klass->parse_error_response (self, GDATA_OPERATION_DELETION, status, + soup_message_get_reason_phrase (message), + message->response_body->data, message->response_body->length, error); g_object_unref (message); return FALSE; diff --git a/gdata/services/documents/gdata-documents-service.c b/gdata/services/documents/gdata-documents-service.c index fa0b81ec..54015f70 100644 --- a/gdata/services/documents/gdata-documents-service.c +++ b/gdata/services/documents/gdata-documents-service.c @@ -305,7 +305,7 @@ append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, Soup { g_assert (message != NULL); - if (message->method == SOUP_METHOD_POST && soup_message_headers_get_one (message->request_headers, "X-Upload-Content-Length") == NULL) { + if (soup_message_get_method (message) == SOUP_METHOD_POST && soup_message_headers_get_one (soup_message_get_request_headers (message), "X-Upload-Content-Length") == NULL) { gchar *upload_uri; const gchar *v3_pos; @@ -321,10 +321,10 @@ append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, Soup * This allows methods like gdata_service_insert_entry() (which aren't resumable-upload-aware) to continue working for creating * documents with metadata only, by simulating the initial request of a resumable upload as described here: * https://developers.google.com/google-apps/documents-list/#creating_a_new_document_or_file_with_metadata_only */ - soup_message_headers_replace (message->request_headers, "X-Upload-Content-Length", "0"); + soup_message_headers_replace (soup_message_get_request_headers (message), "X-Upload-Content-Length", "0"); /* Also set the encoding to be content length encoding. */ - soup_message_headers_set_encoding (message->request_headers, SOUP_ENCODING_CONTENT_LENGTH); + soup_message_headers_set_encoding (soup_message_get_request_headers (message), SOUP_ENCODING_CONTENT_LENGTH); /* HACK: Work around http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3033 by changing the upload URI * to the v2 API's upload URI. Grrr. */ @@ -451,7 +451,9 @@ gdata_documents_service_get_metadata (GDataDocumentsService *self, GCancellable /* Error */ GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self); g_assert (klass->parse_error_response != NULL); - klass->parse_error_response (GDATA_SERVICE (self), GDATA_OPERATION_QUERY, status, message->reason_phrase, message->response_body->data, + klass->parse_error_response (GDATA_SERVICE (self), GDATA_OPERATION_QUERY, status, + soup_message_get_reason_phrase (message), + message->response_body->data, message->response_body->length, error); g_object_unref (message); return NULL; @@ -1355,7 +1357,9 @@ gdata_documents_service_add_entry_to_folder (GDataDocumentsService *self, GDataD /* Error */ GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self); g_assert (klass->parse_error_response != NULL); - klass->parse_error_response (GDATA_SERVICE (self), operation_type, status, message->reason_phrase, message->response_body->data, + klass->parse_error_response (GDATA_SERVICE (self), operation_type, status, + soup_message_get_reason_phrase (message), + message->response_body->data, message->response_body->length, error); g_object_unref (message); return NULL; @@ -1573,7 +1577,7 @@ gdata_documents_service_remove_entry_from_folder (GDataDocumentsService *self, G service_klass->parse_error_response (GDATA_SERVICE (self), GDATA_OPERATION_DELETION, status, - message->reason_phrase, + soup_message_get_reason_phrase (message), message->response_body->data, message->response_body->length, error); diff --git a/gdata/tests/authorization.c b/gdata/tests/authorization.c index 61e49e4a..6a5a2952 100644 --- a/gdata/tests/authorization.c +++ b/gdata/tests/authorization.c @@ -108,10 +108,10 @@ simple_authorizer_process_request (GDataAuthorizer *self, GDataAuthorizationDoma /* Check that this is the first time we've touched the message, and if so, flag the message as touched */ if (domain != NULL) { - g_assert (soup_message_headers_get_one (message->request_headers, "process_request") == NULL); - soup_message_headers_append (message->request_headers, "process_request", "1"); + g_assert (soup_message_headers_get_one (soup_message_get_request_headers (message), "process_request") == NULL); + soup_message_headers_append (soup_message_get_request_headers (message), "process_request", "1"); } else { - soup_message_headers_append (message->request_headers, "process_request_null", "1"); + soup_message_headers_append (soup_message_get_request_headers (message), "process_request_null", "1"); } } @@ -342,8 +342,8 @@ test_authorizer_process_request (AuthorizerData *data, gconstpointer user_data) message = soup_message_new (SOUP_METHOD_GET, "http://example.com/"); gdata_authorizer_process_request (data->authorizer, test_domain1, message); - g_assert_cmpstr (soup_message_headers_get_one (message->request_headers, "process_request"), ==, "1"); - g_assert (soup_message_headers_get_one (message->request_headers, "process_request_null") == NULL); + g_assert_cmpstr (soup_message_headers_get_one (soup_message_get_request_headers (message), "process_request"), ==, "1"); + g_assert (soup_message_headers_get_one (soup_message_get_request_headers (message), "process_request_null") == NULL); g_object_unref (message); } @@ -357,8 +357,8 @@ test_authorizer_process_request_null (AuthorizerData *data, gconstpointer user_d message = soup_message_new (SOUP_METHOD_GET, "http://example.com/"); gdata_authorizer_process_request (data->authorizer, NULL, message); - g_assert (soup_message_headers_get_one (message->request_headers, "process_request") == NULL); - g_assert_cmpstr (soup_message_headers_get_one (message->request_headers, "process_request_null"), ==, "1"); + g_assert (soup_message_headers_get_one (soup_message_get_request_headers (message), "process_request") == NULL); + g_assert_cmpstr (soup_message_headers_get_one (soup_message_get_request_headers (message), "process_request_null"), ==, "1"); g_object_unref (message); } diff --git a/gdata/tests/gdata-dummy-authorizer.c b/gdata/tests/gdata-dummy-authorizer.c index c6b2c219..fe4d4a32 100644 --- a/gdata/tests/gdata-dummy-authorizer.c +++ b/gdata/tests/gdata-dummy-authorizer.c @@ -116,7 +116,7 @@ process_request (GDataAuthorizer *self, GDataAuthorizationDomain *domain, g_mutex_lock (&(priv->mutex)); if (g_hash_table_lookup (priv->authorization_domains, domain) != NULL) { - soup_message_headers_replace (message->request_headers, + soup_message_headers_replace (soup_message_get_request_headers (message), "Authorization", "dummy"); } diff --git a/gdata/tests/oauth2-authorizer.c b/gdata/tests/oauth2-authorizer.c index 5eb23642..98358543 100644 --- a/gdata/tests/oauth2-authorizer.c +++ b/gdata/tests/oauth2-authorizer.c @@ -444,7 +444,7 @@ test_oauth2_authorizer_process_request_null (OAuth2AuthorizerData *data, gconstp gdata_authorizer_process_request (GDATA_AUTHORIZER (data->authorizer), NULL, message); /* Check that the set of request headers is still empty */ - soup_message_headers_iter_init (&iter, message->request_headers); + soup_message_headers_iter_init (&iter, soup_message_get_request_headers (message)); while (soup_message_headers_iter_next (&iter, &name, &value) == TRUE) { header_count++; @@ -471,7 +471,7 @@ test_oauth2_authorizer_process_request_unauthenticated (OAuth2AuthorizerData *da gdata_authorizer_process_request (GDATA_AUTHORIZER (data->authorizer), gdata_tasks_service_get_primary_authorization_domain (), message); /* Check that the set of request headers is still empty */ - soup_message_headers_iter_init (&iter, message->request_headers); + soup_message_headers_iter_init (&iter, soup_message_get_request_headers (message)); while (soup_message_headers_iter_next (&iter, &name, &value) == TRUE) { header_count++; @@ -504,7 +504,7 @@ test_oauth2_authorizer_process_request_authenticated (OAuth2AuthorizerData *data gdata_authorizer_process_request (GDATA_AUTHORIZER (data->authorizer), gdata_tasks_service_get_primary_authorization_domain (), message); /* Check that at least one new header has been set */ - soup_message_headers_iter_init (&iter, message->request_headers); + soup_message_headers_iter_init (&iter, soup_message_get_request_headers (message)); while (soup_message_headers_iter_next (&iter, &name, &value) == TRUE) { header_count++; -- GitLab From 6a977643d4df2fc90e2dbcacab6ec2c62e15b81f Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:11 +0100 Subject: [PATCH 05/45] soup3: replace upload-stream with uploader --- gdata/gdata-core.symbols | 28 +- gdata/gdata-upload-stream.c | 1658 ----------------- gdata/gdata-upload-stream.h | 124 -- gdata/gdata-uploader.c | 1412 ++++++++++++++ gdata/gdata-uploader.h | 87 + gdata/gdata.h | 2 +- gdata/meson.build | 4 +- .../documents/gdata-documents-service.c | 2 +- .../documents/gdata-documents-service.h | 2 +- .../documents/gdata-documents-upload-query.c | 2 +- .../picasaweb/gdata-picasaweb-service.c | 2 +- .../picasaweb/gdata-picasaweb-service.h | 2 +- .../services/youtube/gdata-youtube-service.c | 2 +- .../services/youtube/gdata-youtube-service.h | 2 +- gdata/symbol.map | 28 +- 15 files changed, 1541 insertions(+), 1816 deletions(-) delete mode 100644 gdata/gdata-upload-stream.c delete mode 100644 gdata/gdata-upload-stream.h create mode 100644 gdata/gdata-uploader.c create mode 100644 gdata/gdata-uploader.h diff --git a/gdata/gdata-core.symbols b/gdata/gdata-core.symbols index b92120b2..2d4be6be 100644 --- a/gdata/gdata-core.symbols +++ b/gdata/gdata-core.symbols @@ -596,13 +596,22 @@ gdata_download_stream_get_service gdata_download_stream_get_download_uri gdata_download_stream_get_content_type gdata_download_stream_get_content_length -gdata_upload_stream_get_type -gdata_upload_stream_new -gdata_upload_stream_get_service -gdata_upload_stream_get_upload_uri -gdata_upload_stream_get_entry -gdata_upload_stream_get_slug -gdata_upload_stream_get_content_type +gdata_uploader_get_type +gdata_uploader_new +gdata_uploader_new_resumable +gdata_uploader_set_input +gdata_uploader_set_input_from_bytes +gdata_uploader_send +gdata_uploader_send_async +gdata_uploader_send_finish +gdata_uploader_get_service +gdata_uploader_get_authorization_domain +gdata_uploader_get_method +gdata_uploader_get_upload_uri +gdata_uploader_get_entry +gdata_uploader_get_slug +gdata_uploader_get_content_type +gdata_uploader_get_content_length gdata_gd_name_get_type gdata_gd_name_new gdata_gd_name_get_given_name @@ -703,7 +712,6 @@ gdata_batch_operation_run_finish gdata_batch_operation_type_get_type gdata_batchable_get_type gdata_batchable_create_operation -gdata_upload_stream_get_method gdata_documents_document_get_type gdata_documents_document_download gdata_documents_document_get_download_uri @@ -724,7 +732,6 @@ gdata_calendar_service_query_events_async gdata_calendar_service_insert_calendar_event_async gdata_youtube_video_get_coordinates gdata_youtube_video_set_coordinates -gdata_upload_stream_get_cancellable gdata_download_stream_get_cancellable gdata_service_is_authorized gdata_service_get_authorizer @@ -739,7 +746,6 @@ gdata_authorization_domain_get_type gdata_authorization_domain_get_service_name gdata_authorization_domain_get_scope gdata_download_stream_get_authorization_domain -gdata_upload_stream_get_authorization_domain gdata_batch_operation_get_authorization_domain gdata_calendar_service_get_primary_authorization_domain gdata_documents_service_get_primary_authorization_domain @@ -774,8 +780,6 @@ gdata_youtube_video_get_media_rating gdata_documents_entry_get_resource_id gdata_youtube_query_get_license gdata_youtube_query_set_license -gdata_upload_stream_new_resumable -gdata_upload_stream_get_content_length gdata_documents_service_upload_document_resumable gdata_documents_service_update_document_resumable gdata_documents_upload_query_get_type diff --git a/gdata/gdata-upload-stream.c b/gdata/gdata-upload-stream.c deleted file mode 100644 index 71e8b71c..00000000 --- a/gdata/gdata-upload-stream.c +++ /dev/null @@ -1,1658 +0,0 @@ -/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ -/* - * GData Client - * Copyright (C) Philip Withnall 2009 - * - * 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-upload-stream - * @short_description: GData upload stream object - * @stability: Stable - * @include: gdata/gdata-upload-stream.h - * - * #GDataUploadStream is a #GOutputStream subclass to allow uploading of files from GData services with authorization from a #GDataService under - * the given #GDataAuthorizationDomain. If authorization is not required to perform the upload, a #GDataAuthorizationDomain doesn't have to be - * specified. - * - * Once a #GDataUploadStream is instantiated with gdata_upload_stream_new(), the standard #GOutputStream API can be used on the stream to upload - * the file. Network communication may not actually begin until the first call to g_output_stream_write(), so having a #GDataUploadStream around is no - * guarantee that data is being uploaded. - * - * Uploads of a file, or a file with associated metadata (a #GDataEntry) should use #GDataUploadStream, but if you want to simply upload a single - * #GDataEntry, use gdata_service_insert_entry() instead. #GDataUploadStream is for large streaming uploads. - * - * Once an upload is complete, the server's response can be retrieved from the #GDataUploadStream using gdata_upload_stream_get_response(). In order - * for network communication to be guaranteed to have stopped (and thus the response definitely available), g_output_stream_close() must be called - * on the #GDataUploadStream first. Otherwise, gdata_upload_stream_get_response() may return saying that the operation is still in progress. - * - * If the server returns an error instead of a success response, the error will be returned by g_output_stream_close() as a #GDataServiceError. - * - * The entire upload operation can be cancelled using the #GCancellable instance provided to gdata_upload_stream_new(), or returned by - * gdata_upload_stream_get_cancellable(). Cancelling this at any time will cause all future #GOutputStream method calls to return - * %G_IO_ERROR_CANCELLED. If any #GOutputStream methods are in the process of being called, they will be cancelled and return %G_IO_ERROR_CANCELLED as - * soon as possible. - * - * Note that cancelling an individual method call (such as a call to g_output_stream_write()) using the #GCancellable parameter of the method will not - * cancel the upload as a whole — just that particular method call. In the case of g_output_stream_write(), this will cause it to return the number of - * bytes it has successfully written up to the point of cancellation (up to the requested number of bytes), or return a %G_IO_ERROR_CANCELLED if it - * had not managed to write any bytes to the network by that point. This is also the behaviour of g_output_stream_write() when the upload operation as - * a whole is cancelled. - * - * In the case of g_output_stream_close(), the call will return immediately if network activity hasn't yet started. If it has, the network activity - * will be cancelled, regardless of whether the call to g_output_stream_close() is cancelled. Cancelling a pending call to g_output_stream_close() - * (either using the method's #GCancellable, or by cancelling the upload stream as a whole) will cause it to stop waiting for the network activity to - * finish, and return %G_IO_ERROR_CANCELLED immediately. Network activity will continue to be shut down in the background. - * - * Any outstanding data is guaranteed to be written to the network successfully even if a call to g_output_stream_close() is cancelled. However, if - * the upload stream as a whole is cancelled using #GDataUploadStream:cancellable, no more data will be sent over the network, and the network - * connection will be closed immediately. i.e. #GDataUploadStream will do its best to instruct the server to cancel the upload and any associated - * server-side changes of state. - * - * If the server returns an error message (for example, if the user is not correctly authenticated/authorized or doesn't have suitable permissions - * to upload from the given URI), it will be returned as a #GDataServiceError by g_output_stream_close(). - * - * - * Uploading from a File - * - * GDataService *service; - * GDataAuthorizationDomain *domain; - * GCancellable *cancellable; - * GInputStream *input_stream; - * GOutputStream *upload_stream; - * GFile *file; - * GFileInfo *file_info; - * GError *error = NULL; - * - * /* Get the file to upload */ - * file = get_file_to_upload (); - * file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," - * G_FILE_ATTRIBUTE_STANDARD_SIZE, - * G_FILE_QUERY_INFO_NONE, NULL, &error); - * - * if (file_info == NULL) { - * g_error ("Error getting file info: %s", error->message); - * g_error_free (error); - * g_object_unref (file); - * return; - * } - * - * input_stream = g_file_read (file, NULL, &error); - * g_object_unref (file); - * - * if (input_stream == NULL) { - * g_error ("Error getting file input stream: %s", error->message); - * g_error_free (error); - * g_object_unref (file_info); - * return; - * } - * - * /* Create the upload stream */ - * service = create_my_service (); - * domain = get_my_authorization_domain_from_service (service); - * cancellable = g_cancellable_new (); /* cancel this to cancel the entire upload operation */ - * upload_stream = gdata_upload_stream_new_resumable (service, domain, SOUP_METHOD_POST, upload_uri, NULL, - * g_file_info_get_display_name (file_info), g_file_info_get_content_type (file_info), - * g_file_info_get_size (file_info), cancellable); - * g_object_unref (file_info); - * - * /* Perform the upload asynchronously */ - * g_output_stream_splice_async (upload_stream, input_stream, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, - * G_PRIORITY_DEFAULT, NULL, (GAsyncReadyCallback) upload_splice_cb, NULL); - * - * g_object_unref (upload_stream); - * g_object_unref (input_stream); - * g_object_unref (cancellable); - * g_object_unref (domain); - * g_object_unref (service); - * - * static void - * upload_splice_cb (GOutputStream *upload_stream, GAsyncResult *result, gpointer user_data) - * { - * gssize length; - * GError *error = NULL; - * - * g_output_stream_splice_finish (upload_stream, result, &error); - * - * if (error != NULL && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) == FALSE)) { - * /* Error upload the file; potentially an I/O error (GIOError), or an error response from the server - * * (GDataServiceError). */ - * g_error ("Error uploading file: %s", error->message); - * g_error_free (error); - * } - * - * /* If the upload was successful, carry on to parse the result. Note that this will normally be handled by methods like - * * gdata_youtube_service_finish_video_upload(), gdata_picasaweb_service_finish_file_upload() and - * * gdata_documents_service_finish_upload() */ - * parse_server_result (gdata_upload_stream_get_response (GDATA_UPLOAD_STREAM (upload_stream), &length), length); - * } - * - * - * - * Since: 0.5.0 - */ - -/* - * We have a network thread which does all the uploading work. We send the message encoded as chunks, but cannot use the SoupMessageBody as a - * data buffer, since it can only ever be touched by the network thread. Instead, we pass data to the network thread through a GDataBuffer, with - * the main thread pushing it on as and when write() is called. The network thread cannot block on popping data off the buffer, as it requests fixed- - * size chunks, and there's no way to notify it that we've reached EOF; so when it gets to popping the last chunk off the buffer, which may well be - * smaller than its chunk size, it would block for more data and therefore hang. Consequently, the network thread instead pops as much data as it can - * off the buffer, up to its chunk size, which is a non-blocking operation. - * - * The write() and close() operations on the output stream are synchronised with the network thread, so that the write() call only returns once the - * network thread has written at least as many bytes as were passed to the write() call, and the close() call only returns once all network activity - * has finished (including receiving the response from the server). Async versions of these calls are provided by GOutputStream. - * - * The number of bytes in the various buffers are recorded using: - * • message_bytes_outstanding: the number of bytes in the GDataBuffer which are waiting to be written to the SoupMessageBody - * • network_bytes_outstanding: the number of bytes which have been written to the SoupMessageBody, and are waiting to be written to the network - * • network_bytes_written: the total number of bytes which have been successfully written to the network - * - * Mutex locking order: - * 1. response_mutex - * 2. write_mutex - */ - -#include -#include -#include -#include - -#include "gdata-upload-stream.h" -#include "gdata-buffer.h" -#include "gdata-private.h" - -#define BOUNDARY_STRING "0003Z5W789deadbeefRTE456KlemsnoZV" -#define MAX_RESUMABLE_CHUNK_SIZE (512 * 1024) /* bytes = 512 KiB */ - -static void gdata_upload_stream_constructed (GObject *object); -static void gdata_upload_stream_dispose (GObject *object); -static void gdata_upload_stream_finalize (GObject *object); -static void gdata_upload_stream_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); -static void gdata_upload_stream_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); - -static gssize gdata_upload_stream_write (GOutputStream *stream, const void *buffer, gsize count, GCancellable *cancellable, GError **error); -static gboolean gdata_upload_stream_flush (GOutputStream *stream, GCancellable *cancellable, GError **error); -static gboolean gdata_upload_stream_close (GOutputStream *stream, GCancellable *cancellable, GError **error); - -static void create_network_thread (GDataUploadStream *self, GError **error); - -typedef enum { - STATE_INITIAL_REQUEST, /* initial POST request to the resumable-create-media link (unused for non-resumable uploads) */ - STATE_DATA_REQUESTS, /* one or more subsequent PUT requests (only state used for non-resumable uploads) */ - STATE_FINISHED, /* finished successfully or in error */ -} UploadState; - -struct _GDataUploadStreamPrivate { - gchar *method; - gchar *upload_uri; - GDataService *service; - GDataAuthorizationDomain *authorization_domain; - GDataEntry *entry; - gchar *slug; - gchar *content_type; - goffset content_length; /* -1 for non-resumable uploads; 0 or greater for resumable ones */ - SoupSession *session; - SoupMessage *message; - GDataBuffer *buffer; - - GCancellable *cancellable; - GThread *network_thread; - - UploadState state; /* protected by write_mutex */ - GMutex write_mutex; /* mutex for write operations (specifically, write_finished) */ - /* This persists across all resumable upload chunks. Note that it doesn't count bytes from the entry XML. */ - gsize total_network_bytes_written; /* the number of bytes which have been written to the network in STATE_DATA_REQUESTS */ - - /* All of the following apply only to the current resumable upload chunk. */ - gsize message_bytes_outstanding; /* the number of bytes which have been written to the buffer but not libsoup (signalled by write_cond) */ - gsize network_bytes_outstanding; /* the number of bytes which have been written to libsoup but not the network (signalled by write_cond) */ - gsize network_bytes_written; /* the number of bytes which have been written to the network (signalled by write_cond) */ - gsize chunk_size; /* the size of the current chunk (in bytes); 0 iff content_length <= 0; must be <= MAX_RESUMABLE_CHUNK_SIZE */ - GCond write_cond; /* signalled when a chunk has been written (protected by write_mutex) */ - - GCond finished_cond; /* signalled when sending the message (and receiving the response) is finished (protected by response_mutex) */ - guint response_status; /* set once we finish receiving the response (SOUP_STATUS_NONE otherwise) (protected by response_mutex) */ - GError *response_error; /* error asynchronously set by the network thread, and picked up by the main thread when appropriate */ - GMutex response_mutex; /* mutex for ->response_error, ->response_status and ->finished_cond */ -}; - -enum { - PROP_SERVICE = 1, - PROP_UPLOAD_URI, - PROP_ENTRY, - PROP_SLUG, - PROP_CONTENT_TYPE, - PROP_METHOD, - PROP_CANCELLABLE, - PROP_AUTHORIZATION_DOMAIN, - PROP_CONTENT_LENGTH, -}; - -G_DEFINE_TYPE_WITH_PRIVATE (GDataUploadStream, gdata_upload_stream, G_TYPE_OUTPUT_STREAM) - -static void -gdata_upload_stream_class_init (GDataUploadStreamClass *klass) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - GOutputStreamClass *stream_class = G_OUTPUT_STREAM_CLASS (klass); - - gobject_class->constructed = gdata_upload_stream_constructed; - gobject_class->dispose = gdata_upload_stream_dispose; - gobject_class->finalize = gdata_upload_stream_finalize; - gobject_class->get_property = gdata_upload_stream_get_property; - gobject_class->set_property = gdata_upload_stream_set_property; - - /* We use the default implementations of the async functions, which just run - * our implementation of the sync function in a thread. */ - stream_class->write_fn = gdata_upload_stream_write; - stream_class->flush = gdata_upload_stream_flush; - stream_class->close_fn = gdata_upload_stream_close; - - /** - * GDataUploadStream:service: - * - * The service which is used to authorize the upload, and to which the upload relates. - * - * Since: 0.5.0 - */ - g_object_class_install_property (gobject_class, PROP_SERVICE, - g_param_spec_object ("service", - "Service", "The service which is used to authorize the upload.", - GDATA_TYPE_SERVICE, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - /** - * GDataUploadStream:authorization-domain: - * - * The authorization domain for the upload, against which the #GDataService:authorizer for the #GDataDownloadStream:service should be - * authorized. This may be %NULL if authorization is not needed for the upload. - * - * Since: 0.9.0 - */ - g_object_class_install_property (gobject_class, PROP_AUTHORIZATION_DOMAIN, - g_param_spec_object ("authorization-domain", - "Authorization domain", "The authorization domain for the upload.", - GDATA_TYPE_AUTHORIZATION_DOMAIN, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - /** - * GDataUploadStream:method: - * - * The HTTP request method to use when uploading the file. - * - * Since: 0.7.0 - */ - g_object_class_install_property (gobject_class, PROP_METHOD, - g_param_spec_string ("method", - "Method", "The HTTP request method to use when uploading the file.", - NULL, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - /** - * GDataUploadStream:upload-uri: - * - * The URI to upload the data and metadata to. This must be HTTPS. - * - * Since: 0.5.0 - */ - g_object_class_install_property (gobject_class, PROP_UPLOAD_URI, - g_param_spec_string ("upload-uri", - "Upload URI", "The URI to upload the data and metadata to.", - NULL, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - /** - * GDataUploadStream:entry: - * - * The entry used for metadata to upload. - * - * Since: 0.5.0 - */ - g_object_class_install_property (gobject_class, PROP_ENTRY, - g_param_spec_object ("entry", - "Entry", "The entry used for metadata to upload.", - GDATA_TYPE_ENTRY, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - /** - * GDataUploadStream:slug: - * - * The slug of the file being uploaded. This is usually the display name of the file (i.e. as returned by g_file_info_get_display_name()). - * - * Since: 0.5.0 - */ - g_object_class_install_property (gobject_class, PROP_SLUG, - g_param_spec_string ("slug", - "Slug", "The slug of the file being uploaded.", - NULL, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - /** - * GDataUploadStream:content-length: - * - * The content length (in bytes) of the file being uploaded (i.e. as returned by g_file_info_get_size()). Note that this does not include the - * length of the XML serialisation of #GDataUploadStream:entry, if set. - * - * If this is -1 the upload will be non-resumable; if it is non-negative, the upload will be resumable. - * - * Since: 0.13.0 - */ - g_object_class_install_property (gobject_class, PROP_CONTENT_LENGTH, - g_param_spec_int64 ("content-length", - "Content length", "The content length (in bytes) of the file being uploaded.", - -1, G_MAXINT64, -1, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - /** - * GDataUploadStream:content-type: - * - * The content type of the file being uploaded (i.e. as returned by g_file_info_get_content_type()). - * - * Since: 0.5.0 - */ - g_object_class_install_property (gobject_class, PROP_CONTENT_TYPE, - g_param_spec_string ("content-type", - "Content type", "The content type of the file being uploaded.", - NULL, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - /** - * GDataUploadStream:cancellable: - * - * An optional cancellable used to cancel the entire upload operation. If a #GCancellable instance isn't provided for this property at - * construction time (i.e. to gdata_upload_stream_new()), one will be created internally and can be retrieved using - * gdata_upload_stream_get_cancellable() and used to cancel the upload operation with g_cancellable_cancel() just as if it was passed to - * gdata_upload_stream_new(). - * - * If the upload operation is cancelled using this #GCancellable, any ongoing network activity will be stopped, and any pending or future calls - * to #GOutputStream API on the #GDataUploadStream will return %G_IO_ERROR_CANCELLED. Note that the #GCancellable objects which can be passed - * to individual #GOutputStream operations will not cancel the upload operation proper if cancelled — they will merely cancel that API call. - * The only way to cancel the upload operation completely is using #GDataUploadStream:cancellable. - * - * Since: 0.8.0 - */ - g_object_class_install_property (gobject_class, PROP_CANCELLABLE, - g_param_spec_object ("cancellable", - "Cancellable", "An optional cancellable used to cancel the entire upload operation.", - G_TYPE_CANCELLABLE, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); -} - -static void -gdata_upload_stream_init (GDataUploadStream *self) -{ - self->priv = gdata_upload_stream_get_instance_private (self); - self->priv->buffer = gdata_buffer_new (); - g_mutex_init (&(self->priv->write_mutex)); - g_cond_init (&(self->priv->write_cond)); - g_cond_init (&(self->priv->finished_cond)); - g_mutex_init (&(self->priv->response_mutex)); -} - -static SoupMessage * -build_message (GDataUploadStream *self, const gchar *method, const gchar *upload_uri) -{ - SoupMessage *new_message; - SoupURI *_uri; - - /* Build the message */ - _uri = soup_uri_new (upload_uri); - soup_uri_set_port (_uri, _gdata_service_get_https_port ()); - new_message = soup_message_new_from_uri (method, _uri); - soup_uri_free (_uri); - - /* We don't want to accumulate chunks */ - soup_message_body_set_accumulate (new_message->request_body, FALSE); - - return new_message; -} - -static void -gdata_upload_stream_constructed (GObject *object) -{ - GDataUploadStreamPrivate *priv; - GDataServiceClass *service_klass; - SoupURI *uri = NULL; - - /* Chain up to the parent class */ - G_OBJECT_CLASS (gdata_upload_stream_parent_class)->constructed (object); - priv = GDATA_UPLOAD_STREAM (object)->priv; - - /* The upload URI must be HTTPS. */ - uri = soup_uri_new (priv->upload_uri); - g_assert_cmpstr (soup_uri_get_scheme (uri), ==, SOUP_URI_SCHEME_HTTPS); - soup_uri_free (uri); - - /* Create a #GCancellable for the entire upload operation if one wasn't specified for #GDataUploadStream:cancellable during construction */ - if (priv->cancellable == NULL) - priv->cancellable = g_cancellable_new (); - - /* Build the message */ - priv->message = build_message (GDATA_UPLOAD_STREAM (object), priv->method, priv->upload_uri); - - if (priv->slug != NULL) - soup_message_headers_append (priv->message->request_headers, "Slug", priv->slug); - - if (priv->content_length == -1) { - /* Non-resumable upload */ - soup_message_headers_set_encoding (priv->message->request_headers, SOUP_ENCODING_CHUNKED); - - /* The Content-Type should be multipart/related if we're also uploading the metadata (entry != NULL), - * and the given content_type otherwise. */ - if (priv->entry != NULL) { - gchar *first_part_header, *upload_data; - gchar *second_part_header; - GDataParsableClass *parsable_klass; - - parsable_klass = GDATA_PARSABLE_GET_CLASS (priv->entry); - g_assert (parsable_klass->get_content_type != NULL); - - soup_message_headers_set_content_type (priv->message->request_headers, "multipart/related; boundary=" BOUNDARY_STRING, NULL); - - if (g_strcmp0 (parsable_klass->get_content_type (), "application/json") == 0) { - upload_data = gdata_parsable_get_json (GDATA_PARSABLE (priv->entry)); - } else { - upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (priv->entry)); - } - - /* Start by writing out the entry; then the thread has something to write to the network when it's created */ - first_part_header = g_strdup_printf ("--" BOUNDARY_STRING "\n" - "Content-Type: %s; charset=UTF-8\n\n", - parsable_klass->get_content_type ()); - second_part_header = g_strdup_printf ("\n--" BOUNDARY_STRING "\n" - "Content-Type: %s\n" - "Content-Transfer-Encoding: binary\n\n", - priv->content_type); - - /* Push the message parts onto the message body; we can skip the buffer, since the network thread hasn't yet been created, - * so we're the sole thread accessing the SoupMessage. */ - soup_message_body_append (priv->message->request_body, - SOUP_MEMORY_TAKE, - first_part_header, - strlen (first_part_header)); - soup_message_body_append (priv->message->request_body, - SOUP_MEMORY_TAKE, upload_data, - strlen (upload_data)); - soup_message_body_append (priv->message->request_body, - SOUP_MEMORY_TAKE, - second_part_header, - strlen (second_part_header)); - - first_part_header = NULL; - upload_data = NULL; - second_part_header = NULL; - - priv->network_bytes_outstanding = priv->message->request_body->length; - } else { - soup_message_headers_set_content_type (priv->message->request_headers, priv->content_type, NULL); - } - - /* Non-resumable uploads start with the data requests immediately. */ - priv->state = STATE_DATA_REQUESTS; - } else { - gchar *content_length_str; - - /* Resumable upload's initial request */ - soup_message_headers_set_encoding (priv->message->request_headers, SOUP_ENCODING_CONTENT_LENGTH); - soup_message_headers_replace (priv->message->request_headers, "X-Upload-Content-Type", priv->content_type); - - content_length_str = g_strdup_printf ("%" G_GOFFSET_FORMAT, priv->content_length); - soup_message_headers_replace (priv->message->request_headers, "X-Upload-Content-Length", content_length_str); - g_free (content_length_str); - - if (priv->entry != NULL) { - GDataParsableClass *parsable_klass; - gchar *content_type, *upload_data; - - parsable_klass = GDATA_PARSABLE_GET_CLASS (priv->entry); - g_assert (parsable_klass->get_content_type != NULL); - - if (g_strcmp0 (parsable_klass->get_content_type (), "application/json") == 0) { - upload_data = gdata_parsable_get_json (GDATA_PARSABLE (priv->entry)); - } else { - upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (priv->entry)); - } - - content_type = g_strdup_printf ("%s; charset=UTF-8", - parsable_klass->get_content_type ()); - soup_message_headers_set_content_type (priv->message->request_headers, - content_type, - NULL); - g_free (content_type); - - soup_message_body_append (priv->message->request_body, - SOUP_MEMORY_TAKE, - upload_data, - strlen (upload_data)); - upload_data = NULL; - - priv->network_bytes_outstanding = priv->message->request_body->length; - } else { - soup_message_headers_set_content_length (priv->message->request_headers, 0); - } - - /* Resumable uploads always start with an initial request, which either contains the XML or is empty. */ - priv->state = STATE_INITIAL_REQUEST; - priv->chunk_size = MIN (priv->content_length, MAX_RESUMABLE_CHUNK_SIZE); - } - - /* Make sure the headers are set. HACK: This should actually be in build_message(), but we have to work around - * http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3033 in GDataDocumentsService's append_query_headers(). */ - service_klass = GDATA_SERVICE_GET_CLASS (priv->service); - if (service_klass->append_query_headers != NULL) { - service_klass->append_query_headers (priv->service, priv->authorization_domain, priv->message); - } - - /* If the entry exists and has an ETag, we assume we're updating the entry, so we can set the If-Match header */ - if (priv->entry != NULL && gdata_entry_get_etag (priv->entry) != NULL) - soup_message_headers_append (priv->message->request_headers, "If-Match", gdata_entry_get_etag (priv->entry)); - - /* Uploading doesn't actually start until the first call to write() */ -} - -static void -gdata_upload_stream_dispose (GObject *object) -{ - GDataUploadStreamPrivate *priv = GDATA_UPLOAD_STREAM (object)->priv; - - /* Close the stream before unreffing things like priv->service, which stops crashes like bgo#602156 if the stream is unreffed in the middle - * of network operations */ - g_output_stream_close (G_OUTPUT_STREAM (object), NULL, NULL); - - if (priv->cancellable != NULL) - g_object_unref (priv->cancellable); - priv->cancellable = NULL; - - if (priv->service != NULL) - g_object_unref (priv->service); - priv->service = NULL; - - if (priv->authorization_domain != NULL) - g_object_unref (priv->authorization_domain); - priv->authorization_domain = NULL; - - if (priv->message != NULL) - g_object_unref (priv->message); - priv->message = NULL; - - if (priv->entry != NULL) - g_object_unref (priv->entry); - priv->entry = NULL; - - /* Chain up to the parent class */ - G_OBJECT_CLASS (gdata_upload_stream_parent_class)->dispose (object); -} - -static void -gdata_upload_stream_finalize (GObject *object) -{ - GDataUploadStreamPrivate *priv = GDATA_UPLOAD_STREAM (object)->priv; - - g_mutex_clear (&(priv->response_mutex)); - g_cond_clear (&(priv->finished_cond)); - g_cond_clear (&(priv->write_cond)); - g_mutex_clear (&(priv->write_mutex)); - gdata_buffer_free (priv->buffer); - g_clear_error (&(priv->response_error)); - g_free (priv->upload_uri); - g_free (priv->method); - g_free (priv->slug); - g_free (priv->content_type); - - /* Chain up to the parent class */ - G_OBJECT_CLASS (gdata_upload_stream_parent_class)->finalize (object); -} - -static void -gdata_upload_stream_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) -{ - GDataUploadStreamPrivate *priv = GDATA_UPLOAD_STREAM (object)->priv; - - switch (property_id) { - case PROP_SERVICE: - g_value_set_object (value, priv->service); - break; - case PROP_AUTHORIZATION_DOMAIN: - g_value_set_object (value, priv->authorization_domain); - break; - case PROP_METHOD: - g_value_set_string (value, priv->method); - break; - case PROP_UPLOAD_URI: - g_value_set_string (value, priv->upload_uri); - break; - case PROP_ENTRY: - g_value_set_object (value, priv->entry); - break; - case PROP_SLUG: - g_value_set_string (value, priv->slug); - break; - case PROP_CONTENT_TYPE: - g_value_set_string (value, priv->content_type); - break; - case PROP_CONTENT_LENGTH: - g_value_set_int64 (value, priv->content_length); - break; - case PROP_CANCELLABLE: - g_value_set_object (value, priv->cancellable); - break; - default: - /* We don't have any other property... */ - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } -} - -static void -gdata_upload_stream_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) -{ - GDataUploadStreamPrivate *priv = GDATA_UPLOAD_STREAM (object)->priv; - - switch (property_id) { - case PROP_SERVICE: - priv->service = g_value_dup_object (value); - priv->session = _gdata_service_get_session (priv->service); - break; - case PROP_AUTHORIZATION_DOMAIN: - priv->authorization_domain = g_value_dup_object (value); - break; - case PROP_METHOD: - priv->method = g_value_dup_string (value); - break; - case PROP_UPLOAD_URI: - priv->upload_uri = g_value_dup_string (value); - break; - case PROP_ENTRY: - priv->entry = g_value_dup_object (value); - break; - case PROP_SLUG: - priv->slug = g_value_dup_string (value); - break; - case PROP_CONTENT_TYPE: - priv->content_type = g_value_dup_string (value); - break; - case PROP_CONTENT_LENGTH: - priv->content_length = g_value_get_int64 (value); - break; - case PROP_CANCELLABLE: - /* Construction only */ - priv->cancellable = g_value_dup_object (value); - break; - default: - /* We don't have any other property... */ - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } -} - -typedef struct { - GDataUploadStream *upload_stream; - gboolean *cancelled; -} CancelledData; - -static void -write_cancelled_cb (GCancellable *cancellable, CancelledData *data) -{ - GDataUploadStreamPrivate *priv = data->upload_stream->priv; - - /* Signal the gdata_upload_stream_write() function that it should stop blocking and cancel */ - g_mutex_lock (&(priv->write_mutex)); - *(data->cancelled) = TRUE; - g_cond_signal (&(priv->write_cond)); - g_mutex_unlock (&(priv->write_mutex)); -} - -static gssize -gdata_upload_stream_write (GOutputStream *stream, const void *buffer, gsize count, GCancellable *cancellable, GError **error_out) -{ - GDataUploadStreamPrivate *priv = GDATA_UPLOAD_STREAM (stream)->priv; - gssize length_written = -1; - gulong cancelled_signal = 0, global_cancelled_signal = 0; - gboolean cancelled = FALSE; /* must only be touched with ->write_mutex held */ - gsize old_total_network_bytes_written; - CancelledData data; - GError *error = NULL; - - /* Listen for cancellation events */ - data.upload_stream = GDATA_UPLOAD_STREAM (stream); - data.cancelled = &cancelled; - - global_cancelled_signal = g_cancellable_connect (priv->cancellable, (GCallback) write_cancelled_cb, &data, NULL); - - if (cancellable != NULL) - cancelled_signal = g_cancellable_connect (cancellable, (GCallback) write_cancelled_cb, &data, NULL); - - /* Check for an error and return if necessary */ - g_mutex_lock (&(priv->write_mutex)); - - if (cancelled == TRUE) { - g_assert (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE || - g_cancellable_set_error_if_cancelled (priv->cancellable, &error) == TRUE); - g_mutex_unlock (&(priv->write_mutex)); - - length_written = -1; - goto done; - } - - g_mutex_unlock (&(priv->write_mutex)); - - /* Increment the number of bytes outstanding for the new write, and keep a record of the old number written so we know if the write's - * finished before we reach write_cond. */ - old_total_network_bytes_written = priv->total_network_bytes_written; - priv->message_bytes_outstanding += count; - - /* Handle the more common case of the network thread already having been created first */ - if (priv->network_thread != NULL) { - /* Push the new data into the buffer */ - gdata_buffer_push_data (priv->buffer, buffer, count); - goto write; - } - - /* Write out the first chunk of data, so there's guaranteed to be something in the buffer */ - gdata_buffer_push_data (priv->buffer, buffer, count); - - /* Create the thread and let the writing commence! */ - create_network_thread (GDATA_UPLOAD_STREAM (stream), &error); - if (priv->network_thread == NULL) { - length_written = -1; - goto done; - } - -write: - g_mutex_lock (&(priv->write_mutex)); - - /* Wait for it to be written */ - while (priv->total_network_bytes_written - old_total_network_bytes_written < count && cancelled == FALSE && priv->state != STATE_FINISHED) { - g_cond_wait (&(priv->write_cond), &(priv->write_mutex)); - } - length_written = MIN (count, priv->total_network_bytes_written - old_total_network_bytes_written); - - /* Check for an error and return if necessary */ - if (cancelled == TRUE && length_written < 1) { - /* Cancellation. */ - g_assert (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE || - g_cancellable_set_error_if_cancelled (priv->cancellable, &error) == TRUE); - length_written = -1; - } else if (priv->state == STATE_FINISHED && (length_written < 0 || (gsize) length_written < count)) { - /* Resumable upload error. */ - g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Error received from server after uploading a resumable upload chunk.")); - length_written = -1; - } - - g_mutex_unlock (&(priv->write_mutex)); - -done: - /* Disconnect from the cancelled signals. Note that we have to do this with @write_mutex not held, as g_cancellable_disconnect() blocks - * until any outstanding cancellation callbacks return, and they will block on @write_mutex. */ - if (cancelled_signal != 0) - g_cancellable_disconnect (cancellable, cancelled_signal); - if (global_cancelled_signal != 0) - g_cancellable_disconnect (priv->cancellable, global_cancelled_signal); - - g_assert (error != NULL || length_written > 0); - - if (error != NULL) { - g_propagate_error (error_out, error); - } - - return length_written; -} - -static void -flush_cancelled_cb (GCancellable *cancellable, CancelledData *data) -{ - GDataUploadStreamPrivate *priv = data->upload_stream->priv; - - /* Signal the gdata_upload_stream_flush() function that it should stop blocking and cancel */ - g_mutex_lock (&(priv->write_mutex)); - *(data->cancelled) = TRUE; - g_cond_signal (&(priv->write_cond)); - g_mutex_unlock (&(priv->write_mutex)); -} - -/* Block until ->network_bytes_outstanding reaches zero. Cancelling the cancellable passed to gdata_upload_stream_flush() breaks out of the wait(), - * but doesn't stop the network thread from continuing to write the remaining bytes to the network. - * The wrapper function, g_output_stream_flush(), calls g_output_stream_set_pending() before calling this function, and calls - * g_output_stream_clear_pending() afterwards, so we don't need to worry about other operations happening concurrently. We also don't need to worry - * about being called after the stream has been closed (though the network activity could finish before or during this function). */ -static gboolean -gdata_upload_stream_flush (GOutputStream *stream, GCancellable *cancellable, GError **error) -{ - GDataUploadStreamPrivate *priv = GDATA_UPLOAD_STREAM (stream)->priv; - gulong cancelled_signal = 0, global_cancelled_signal = 0; - gboolean cancelled = FALSE; /* must only be touched with ->write_mutex held */ - gboolean success = TRUE; - CancelledData data; - - /* Listen for cancellation events */ - data.upload_stream = GDATA_UPLOAD_STREAM (stream); - data.cancelled = &cancelled; - - global_cancelled_signal = g_cancellable_connect (priv->cancellable, (GCallback) flush_cancelled_cb, &data, NULL); - - if (cancellable != NULL) - cancelled_signal = g_cancellable_connect (cancellable, (GCallback) flush_cancelled_cb, &data, NULL); - - /* Create the thread if it hasn't been created already. This can happen if flush() is called immediately after creating the stream. */ - if (priv->network_thread == NULL) { - create_network_thread (GDATA_UPLOAD_STREAM (stream), error); - if (priv->network_thread == NULL) { - success = FALSE; - goto done; - } - } - - /* Start the flush operation proper */ - g_mutex_lock (&(priv->write_mutex)); - - /* Wait for all outstanding bytes to be written to the network */ - while (priv->network_bytes_outstanding > 0 && cancelled == FALSE && priv->state != STATE_FINISHED) { - g_cond_wait (&(priv->write_cond), &(priv->write_mutex)); - } - - /* Check for an error and return if necessary */ - if (cancelled == TRUE) { - g_assert (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE || - g_cancellable_set_error_if_cancelled (priv->cancellable, error) == TRUE); - success = FALSE; - } else if (priv->state == STATE_FINISHED && priv->network_bytes_outstanding > 0) { - /* Resumable upload error. */ - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Error received from server after uploading a resumable upload chunk.")); - success = FALSE; - } - - g_mutex_unlock (&(priv->write_mutex)); - -done: - /* Disconnect from the cancelled signals. Note that we have to do this without @write_mutex held, as g_cancellable_disconnect() blocks - * until any outstanding cancellation callbacks return, and they will block on @write_mutex. */ - if (cancelled_signal != 0) - g_cancellable_disconnect (cancellable, cancelled_signal); - if (global_cancelled_signal != 0) - g_cancellable_disconnect (priv->cancellable, global_cancelled_signal); - - return success; -} - -static void -close_cancelled_cb (GCancellable *cancellable, CancelledData *data) -{ - GDataUploadStreamPrivate *priv = data->upload_stream->priv; - - /* Signal the gdata_upload_stream_close() function that it should stop blocking and cancel */ - g_mutex_lock (&(priv->response_mutex)); - *(data->cancelled) = TRUE; - g_cond_signal (&(priv->finished_cond)); - g_mutex_unlock (&(priv->response_mutex)); -} - -/* It's guaranteed that we have set ->response_status and ->response_error and are done with *all* network activity before this returns, unless it's - * cancelled. This means that it's safe to call gdata_upload_stream_get_response() once a call to close() has returned without being cancelled. - * - * Even though calling g_output_stream_close() multiple times on this stream is guaranteed to call gdata_upload_stream_close() at most once, other - * GIO methods (notably g_output_stream_splice()) can call gdata_upload_stream_close() directly. Consequently, we need to be careful to be idempotent - * after the first call. - * - * If the network thread hasn't yet been started (i.e. gdata_upload_stream_write() hasn't been called at all yet), %TRUE will be returned immediately. - * - * If the global cancellable, ->cancellable, or @cancellable are cancelled before the call to gdata_upload_stream_close(), gdata_upload_stream_close() - * should return immediately with %G_IO_ERROR_CANCELLED. If they're cancelled during the call, gdata_upload_stream_close() should stop waiting for - * any outstanding data to be flushed to the network and return %G_IO_ERROR_CANCELLED (though the operation to finish off network activity and close - * the stream will still continue). - * - * If the call to gdata_upload_stream_close() is not cancelled by any #GCancellable, it will wait until all the data has been flushed to the network - * and a response has been received. At this point, ->response_status and ->response_error have been set (and won't ever change) and we can return - * either success or an error code. */ -static gboolean -gdata_upload_stream_close (GOutputStream *stream, GCancellable *cancellable, GError **error) -{ - GDataUploadStreamPrivate *priv = GDATA_UPLOAD_STREAM (stream)->priv; - gboolean success = TRUE; - gboolean cancelled = FALSE; /* must only be touched with ->response_mutex held */ - gulong cancelled_signal = 0, global_cancelled_signal = 0; - CancelledData data; - gboolean is_finished; - GError *child_error = NULL; - - /* If the operation was never started, return successfully immediately */ - if (priv->network_thread == NULL) - return TRUE; - - /* If we've already closed the stream, return G_IO_ERROR_CLOSED */ - g_mutex_lock (&(priv->response_mutex)); - - if (priv->response_status != SOUP_STATUS_NONE) { - g_mutex_unlock (&(priv->response_mutex)); - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CLOSED, _("Stream is already closed")); - return FALSE; - } - - g_mutex_unlock (&(priv->response_mutex)); - - /* Allow cancellation */ - data.upload_stream = GDATA_UPLOAD_STREAM (stream); - data.cancelled = &cancelled; - - global_cancelled_signal = g_cancellable_connect (priv->cancellable, (GCallback) close_cancelled_cb, &data, NULL); - - if (cancellable != NULL) - cancelled_signal = g_cancellable_connect (cancellable, (GCallback) close_cancelled_cb, &data, NULL); - - g_mutex_lock (&(priv->response_mutex)); - - g_mutex_lock (&(priv->write_mutex)); - is_finished = (priv->state == STATE_FINISHED); - g_mutex_unlock (&(priv->write_mutex)); - - /* If an operation is still in progress, the upload thread hasn't finished yet… */ - if (!is_finished) { - /* We've reached the end of the stream, so append the footer if the entire operation hasn't been cancelled. */ - if (priv->entry != NULL && g_cancellable_is_cancelled (priv->cancellable) == FALSE) { - const gchar *footer = "\n--" BOUNDARY_STRING "--"; - gsize footer_length = strlen (footer); - - gdata_buffer_push_data (priv->buffer, (const guint8*) footer, footer_length); - - g_mutex_lock (&(priv->write_mutex)); - priv->message_bytes_outstanding += footer_length; - g_mutex_unlock (&(priv->write_mutex)); - } - - /* Mark the buffer as having reached EOF, and the write operation will close in its own time */ - gdata_buffer_push_data (priv->buffer, NULL, 0); - - /* Wait for the signal that we've finished. Cancelling the call to gdata_upload_stream_close() will cause this wait to be aborted, - * but won't actually prevent the stream being closed (i.e. all it means is that the stream isn't guaranteed to have been closed by - * the time gdata_upload_stream_close() returns — whereas normally it would be). */ - if (cancelled == FALSE) { - g_cond_wait (&(priv->finished_cond), &(priv->response_mutex)); - } - } - - g_assert (priv->response_status == SOUP_STATUS_NONE); - g_assert (priv->response_error == NULL); - - g_mutex_lock (&(priv->write_mutex)); - is_finished = (priv->state == STATE_FINISHED); - g_mutex_unlock (&(priv->write_mutex)); - - /* Error handling */ - if (!is_finished && cancelled == TRUE) { - /* Cancelled? If ->state == STATE_FINISHED, the network activity finished before the gdata_upload_stream_close() operation was - * cancelled, so we don't need to return an error. */ - g_assert (g_cancellable_set_error_if_cancelled (cancellable, &child_error) == TRUE || - g_cancellable_set_error_if_cancelled (priv->cancellable, &child_error) == TRUE); - priv->response_status = SOUP_STATUS_CANCELLED; - success = FALSE; - } else if (SOUP_STATUS_IS_SUCCESSFUL (priv->message->status_code) == FALSE) { - GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (priv->service); - - /* Parse the error */ - g_assert (klass->parse_error_response != NULL); - klass->parse_error_response (priv->service, GDATA_OPERATION_UPLOAD, priv->message->status_code, priv->message->reason_phrase, - priv->message->response_body->data, priv->message->response_body->length, &child_error); - priv->response_status = priv->message->status_code; - success = FALSE; - } else { - /* Success! Set the response status */ - priv->response_status = priv->message->status_code; - } - - g_assert (priv->response_status != SOUP_STATUS_NONE && (SOUP_STATUS_IS_SUCCESSFUL (priv->response_status) || child_error != NULL)); - - g_mutex_unlock (&(priv->response_mutex)); - - /* Disconnect from the signal handler. Note that we have to do this with @response_mutex not held, as g_cancellable_disconnect() blocks - * until any outstanding cancellation callbacks return, and they will block on @response_mutex. */ - if (cancelled_signal != 0) - g_cancellable_disconnect (cancellable, cancelled_signal); - if (global_cancelled_signal != 0) - g_cancellable_disconnect (priv->cancellable, global_cancelled_signal); - - g_assert ((success == TRUE && child_error == NULL) || (success == FALSE && child_error != NULL)); - - if (child_error != NULL) - g_propagate_error (error, child_error); - - return success; -} - -/* In the network thread context, called just after writing the headers, or just after writing a chunk, to write the next chunk to libsoup. - * We don't let it return until we've finished pushing all the data into the buffer. - * This is due to http://bugzilla.gnome.org/show_bug.cgi?id=522147, which means that - * we can't use soup_session_(un)?pause_message() with a SoupSessionSync. - * If we don't return from this signal handler, the message is never paused, and thus - * Bad Things don't happen (due to the bug, messages can be paused, but not unpaused). - * Obviously this means that our memory usage will increase, and we'll eventually end - * up storing the entire request body in memory, but that's unavoidable at this point. */ -static void -write_next_chunk (GDataUploadStream *self, SoupMessage *message) -{ - #define CHUNK_SIZE 8192 /* 8KiB */ - - GDataUploadStreamPrivate *priv = self->priv; - gboolean has_network_bytes_outstanding, is_complete; - gsize length; - gboolean reached_eof = FALSE; - guint8 next_buffer[CHUNK_SIZE]; - - g_mutex_lock (&(priv->write_mutex)); - has_network_bytes_outstanding = (priv->network_bytes_outstanding > 0); - is_complete = (priv->state == STATE_INITIAL_REQUEST || - (priv->content_length != -1 && priv->network_bytes_written + priv->network_bytes_outstanding == priv->chunk_size)); - g_mutex_unlock (&(priv->write_mutex)); - - /* If there are still bytes in libsoup's buffer, don't block on getting new bytes into the stream. Also, if we're making the initial request - * of a resumable upload, don't push new data onto the network, since all of the XML was pushed into the buffer when we started. */ - if (has_network_bytes_outstanding) { - return; - } else if (is_complete) { - soup_message_body_complete (priv->message->request_body); - - return; - } - - /* Append the next chunk to the message body so it can join in the fun. - * Note that this call isn't necessarily blocking, and can return less than the CHUNK_SIZE. This is because - * we could deadlock if we block on getting CHUNK_SIZE bytes at the end of the stream. write() could - * easily be called with fewer bytes, but has no way to notify us that we've reached the end of the - * stream, so we'd happily block on receiving more bytes which weren't forthcoming. - * - * Note also that we can't block on this call with write_mutex locked, or we could get into a deadlock if the stream is flushed at the same - * time (in the case that we don't know the content length ahead of time). */ - if (priv->content_length == -1) { - /* Non-resumable upload. */ - length = gdata_buffer_pop_data_limited (priv->buffer, next_buffer, CHUNK_SIZE, &reached_eof); - } else { - /* Resumable upload. Ensure we don't exceed the chunk size. */ - length = gdata_buffer_pop_data_limited (priv->buffer, next_buffer, - MIN (CHUNK_SIZE, priv->chunk_size - (priv->network_bytes_written + - priv->network_bytes_outstanding)), &reached_eof); - } - - g_mutex_lock (&(priv->write_mutex)); - - priv->message_bytes_outstanding -= length; - priv->network_bytes_outstanding += length; - - /* Append whatever data was returned */ - if (length > 0) - soup_message_body_append (priv->message->request_body, SOUP_MEMORY_COPY, next_buffer, length); - - /* Finish off the request body if we've reached EOF (i.e. the stream has been closed), or if we're doing a resumable upload and we reach - * the maximum chunk size. */ - if (reached_eof == TRUE || - (priv->content_length != -1 && priv->network_bytes_written + priv->network_bytes_outstanding == priv->chunk_size)) { - g_assert (reached_eof == FALSE || priv->message_bytes_outstanding == 0); - - soup_message_body_complete (priv->message->request_body); - } - - g_mutex_unlock (&(priv->write_mutex)); -} - -static void -wrote_headers_cb (SoupMessage *message, GDataUploadStream *self) -{ - GDataUploadStreamPrivate *priv = self->priv; - - /* Signal the main thread that the headers have been written */ - g_mutex_lock (&(priv->write_mutex)); - g_cond_signal (&(priv->write_cond)); - g_mutex_unlock (&(priv->write_mutex)); - - /* Send the first chunk to libsoup */ - write_next_chunk (self, message); -} - -static void -wrote_body_data_cb (SoupMessage *message, SoupBuffer *buffer, GDataUploadStream *self) -{ - GDataUploadStreamPrivate *priv = self->priv; - - /* Signal the main thread that the chunk has been written */ - g_mutex_lock (&(priv->write_mutex)); - g_assert (priv->network_bytes_outstanding > 0); - priv->network_bytes_outstanding -= buffer->length; - priv->network_bytes_written += buffer->length; - - if (priv->state == STATE_DATA_REQUESTS) { - priv->total_network_bytes_written += buffer->length; - } - - g_cond_signal (&(priv->write_cond)); - g_mutex_unlock (&(priv->write_mutex)); - - /* Send the next chunk to libsoup */ - write_next_chunk (self, message); -} - -static gpointer -upload_thread (GDataUploadStream *self) -{ - GDataUploadStreamPrivate *priv = self->priv; - GDataAuthorizer *authorizer; - - g_assert (priv->cancellable != NULL); - - /* FIXME: Refresh authorization before sending message in order to prevent authorization errors during transfer. - * See: https://gitlab.gnome.org/GNOME/libgdata/issues/23 */ - authorizer = gdata_service_get_authorizer (priv->service); - if (authorizer) { - g_autoptr(GError) error = NULL; - - gdata_authorizer_refresh_authorization (authorizer, priv->cancellable, &error); - if (error != NULL) - g_debug ("Error returned when refreshing authorization: %s", error->message); - else - gdata_authorizer_process_request (authorizer, priv->authorization_domain, priv->message); - } - - while (TRUE) { - GDataServiceClass *klass; - gulong wrote_headers_signal, wrote_body_data_signal; - gchar *new_uri; - SoupMessage *new_message; - gsize next_chunk_length; - - /* Connect to the wrote-* signals so we can prepare the next chunk for transmission */ - wrote_headers_signal = g_signal_connect (priv->message, "wrote-headers", (GCallback) wrote_headers_cb, self); - wrote_body_data_signal = g_signal_connect (priv->message, "wrote-body-data", (GCallback) wrote_body_data_cb, self); - - _gdata_service_actually_send_message (priv->session, priv->message, priv->cancellable, NULL); - - g_mutex_lock (&(priv->write_mutex)); - - /* If this is a resumable upload, continue to the next chunk. If it's a non-resumable upload, we're done. We have several cases: - * • Non-resumable upload: - * - Content only: STATE_DATA_REQUESTS → STATE_FINISHED - * - Metadata only: not supported - * - Content and metadata: STATE_DATA_REQUESTS → STATE_FINISHED - * • Resumable upload: - * - Content only: - * * STATE_INITIAL_REQUEST → STATE_DATA_REQUESTS - * * STATE_DATA_REQUESTS → STATE_DATA_REQUESTS - * * STATE_DATA_REQUESTS → STATE_FINISHED - * - Metadata only: STATE_INITIAL_REQUEST → STATE_FINISHED - * - Content and metadata: - * * STATE_INITIAL_REQUEST → STATE_DATA_REQUESTS - * * STATE_DATA_REQUESTS → STATE_DATA_REQUESTS - * * STATE_DATA_REQUESTS → STATE_FINISHED - */ - switch (priv->state) { - case STATE_INITIAL_REQUEST: - /* We're either a content-only or a content-and-metadata resumable upload. */ - priv->state = STATE_DATA_REQUESTS; - - /* Check the response. On success it should be empty, status 200, with a Location header telling us where to upload - * next. If it's an error response, bail out and let the code in gdata_upload_stream_close() parse the error..*/ - if (!SOUP_STATUS_IS_SUCCESSFUL (priv->message->status_code)) { - goto finished; - } else if (priv->content_length == 0 && priv->message->status_code == SOUP_STATUS_CREATED) { - /* If this was a metadata-only upload, we're done. */ - goto finished; - } - - /* Fall out and prepare the next message */ - g_assert (priv->total_network_bytes_written == 0); /* haven't written any data yet */ - - break; - case STATE_DATA_REQUESTS: - /* Check the response. On completion it should contain the resulting entry's XML, status 201. On continuation it should - * be empty, status 308, with a Range header and potentially a Location header telling us what/where to upload next. - * If it's an error response, bail out and let the code in gdata_upload_stream_close() parse the error..*/ - if (priv->message->status_code == 308) { - /* Continuation: fall out and prepare the next message */ - g_assert (priv->content_length == -1 || priv->total_network_bytes_written < (gsize) priv->content_length); - } else if (SOUP_STATUS_IS_SUCCESSFUL (priv->message->status_code)) { - /* Completion. Check the server isn't misbehaving. */ - g_assert (priv->content_length == -1 || priv->total_network_bytes_written == (gsize) priv->content_length); - - goto finished; - } else { - /* Error */ - goto finished; - } - - /* Fall out and prepare the next message */ - g_assert (priv->total_network_bytes_written > 0); - - break; - case STATE_FINISHED: - default: - g_assert_not_reached (); - } - - /* Prepare the next message. */ - g_assert (priv->content_length != -1); - - next_chunk_length = MIN (priv->content_length - priv->total_network_bytes_written, MAX_RESUMABLE_CHUNK_SIZE); - - new_uri = g_strdup (soup_message_headers_get_one (priv->message->response_headers, "Location")); - if (new_uri == NULL) { - new_uri = soup_uri_to_string (soup_message_get_uri (priv->message), FALSE); - } - - new_message = build_message (self, SOUP_METHOD_PUT, new_uri); - - g_free (new_uri); - - soup_message_headers_set_encoding (new_message->request_headers, SOUP_ENCODING_CONTENT_LENGTH); - soup_message_headers_set_content_type (new_message->request_headers, priv->content_type, NULL); - soup_message_headers_set_content_length (new_message->request_headers, next_chunk_length); - soup_message_headers_set_content_range (new_message->request_headers, priv->total_network_bytes_written, - priv->total_network_bytes_written + next_chunk_length - 1, priv->content_length); - - /* Make sure the headers are set. HACK: This should actually be in build_message(), but we have to work around - * http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3033 in GDataDocumentsService's append_query_headers(). */ - klass = GDATA_SERVICE_GET_CLASS (priv->service); - if (klass->append_query_headers != NULL) { - klass->append_query_headers (priv->service, priv->authorization_domain, new_message); - } - - g_signal_handler_disconnect (priv->message, wrote_body_data_signal); - g_signal_handler_disconnect (priv->message, wrote_headers_signal); - - g_object_unref (priv->message); - priv->message = new_message; - - /* Reset various counters for the next upload. Note that message_bytes_outstanding may be > 0 at this point, since the client may - * have pushed some content into the buffer while we were waiting for the response to this request. */ - g_assert (priv->network_bytes_outstanding == 0); - priv->chunk_size = next_chunk_length; - priv->network_bytes_written = 0; - - /* Loop round and upload this chunk now. */ - g_mutex_unlock (&(priv->write_mutex)); - - continue; - -finished: - g_mutex_unlock (&(priv->write_mutex)); - - goto finished_outer; - } - -finished_outer: - /* Signal that the operation has finished (either successfully or in error). - * Also signal write_cond, just in case we errored out and finished sending in the middle of a write. */ - g_mutex_lock (&(priv->write_mutex)); - priv->state = STATE_FINISHED; - g_cond_signal (&(priv->write_cond)); - g_mutex_unlock (&(priv->write_mutex)); - - g_cond_signal (&(priv->finished_cond)); - - /* Referenced in create_network_thread(). */ - g_object_unref (self); - - return NULL; -} - -static void -create_network_thread (GDataUploadStream *self, GError **error) -{ - GDataUploadStreamPrivate *priv = self->priv; - - g_assert (priv->network_thread == NULL); - g_object_ref (self); /* ownership transferred to thread */ - priv->network_thread = g_thread_try_new ("upload-thread", (GThreadFunc) upload_thread, self, error); -} - -/** - * gdata_upload_stream_new: - * @service: a #GDataService - * @domain: (allow-none): the #GDataAuthorizationDomain to authorize the upload, or %NULL - * @method: the HTTP method to use - * @upload_uri: the URI to upload, which must be HTTPS - * @entry: (allow-none): the entry to upload as metadata, or %NULL - * @slug: the file's slug (filename) - * @content_type: the content type of the file being uploaded - * @cancellable: (allow-none): a #GCancellable for the entire upload stream, or %NULL - * - * Creates a new #GDataUploadStream, allowing a file to be uploaded from a GData service using standard #GOutputStream API. - * - * The HTTP method to use should be specified in @method, and will typically be either %SOUP_METHOD_POST (for insertions) or %SOUP_METHOD_PUT - * (for updates), according to the server and the @upload_uri. - * - * If @entry is specified, it will be attached to the upload as the entry to which the file being uploaded belongs. Otherwise, just the file - * written to the stream will be uploaded, and given a default entry as determined by the server. - * - * @slug and @content_type must be specified before the upload begins, as they describe the file being streamed. @slug is the filename given to the - * file, which will typically be stored on the server and made available when downloading the file again. @content_type must be the correct - * content type for the file, and should be in the service's list of acceptable content types. - * - * As well as the standard GIO errors, calls to the #GOutputStream API on a #GDataUploadStream can also return any relevant specific error from - * #GDataServiceError, or %GDATA_SERVICE_ERROR_PROTOCOL_ERROR in the general case. - * - * If a #GCancellable is provided in @cancellable, the upload operation may be cancelled at any time from another thread using g_cancellable_cancel(). - * In this case, any ongoing network activity will be stopped, and any pending or future calls to #GOutputStream API on the #GDataUploadStream will - * return %G_IO_ERROR_CANCELLED. Note that the #GCancellable objects which can be passed to individual #GOutputStream operations will not cancel the - * upload operation proper if cancelled — they will merely cancel that API call. The only way to cancel the upload operation completely is using this - * @cancellable. - * - * Note that network communication won't begin until the first call to g_output_stream_write() on the #GDataUploadStream. - * - * Return value: a new #GOutputStream, or %NULL; unref with g_object_unref() - * - * Since: 0.9.0 - */ -GOutputStream * -gdata_upload_stream_new (GDataService *service, GDataAuthorizationDomain *domain, const gchar *method, const gchar *upload_uri, GDataEntry *entry, - const gchar *slug, const gchar *content_type, GCancellable *cancellable) -{ - g_return_val_if_fail (GDATA_IS_SERVICE (service), NULL); - g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL); - g_return_val_if_fail (method != NULL, NULL); - g_return_val_if_fail (upload_uri != NULL, NULL); - g_return_val_if_fail (entry == NULL || GDATA_IS_ENTRY (entry), NULL); - g_return_val_if_fail (slug != NULL, NULL); - g_return_val_if_fail (content_type != NULL, NULL); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); - - /* Create the upload stream */ - return G_OUTPUT_STREAM (g_object_new (GDATA_TYPE_UPLOAD_STREAM, - "method", method, - "upload-uri", upload_uri, - "service", service, - "authorization-domain", domain, - "entry", entry, - "slug", slug, - "content-type", content_type, - "cancellable", cancellable, - NULL)); -} - -/** - * gdata_upload_stream_new_resumable: - * @service: a #GDataService - * @domain: (allow-none): the #GDataAuthorizationDomain to authorize the upload, or %NULL - * @method: the HTTP method to use - * @upload_uri: the URI to upload - * @entry: (allow-none): the entry to upload as metadata, or %NULL - * @slug: the file's slug (filename) - * @content_type: the content type of the file being uploaded - * @content_length: the size (in bytes) of the file being uploaded - * @cancellable: (allow-none): a #GCancellable for the entire upload stream, or %NULL - * - * Creates a new resumable #GDataUploadStream, allowing a file to be uploaded from a GData service using standard #GOutputStream API. The upload will - * use GData's resumable upload API, so should be more reliable than a normal upload (especially if the file is large). See the - * GData documentation on resumable uploads for more - * information. - * - * The HTTP method to use should be specified in @method, and will typically be either %SOUP_METHOD_POST (for insertions) or %SOUP_METHOD_PUT - * (for updates), according to the server and the @upload_uri. - * - * If @entry is specified, it will be attached to the upload as the entry to which the file being uploaded belongs. Otherwise, just the file - * written to the stream will be uploaded, and given a default entry as determined by the server. - * - * @slug, @content_type and @content_length must be specified before the upload begins, as they describe the file being streamed. @slug is the filename - * given to the file, which will typically be stored on the server and made available when downloading the file again. @content_type must be the - * correct content type for the file, and should be in the service's list of acceptable content types. @content_length must be the size of the file - * being uploaded (not including the XML for any associated #GDataEntry) in bytes. Zero is accepted if a metadata-only upload is being performed. - * - * As well as the standard GIO errors, calls to the #GOutputStream API on a #GDataUploadStream can also return any relevant specific error from - * #GDataServiceError, or %GDATA_SERVICE_ERROR_PROTOCOL_ERROR in the general case. - * - * If a #GCancellable is provided in @cancellable, the upload operation may be cancelled at any time from another thread using g_cancellable_cancel(). - * In this case, any ongoing network activity will be stopped, and any pending or future calls to #GOutputStream API on the #GDataUploadStream will - * return %G_IO_ERROR_CANCELLED. Note that the #GCancellable objects which can be passed to individual #GOutputStream operations will not cancel the - * upload operation proper if cancelled — they will merely cancel that API call. The only way to cancel the upload operation completely is using this - * @cancellable. - * - * Note that network communication won't begin until the first call to g_output_stream_write() on the #GDataUploadStream. - * - * Return value: a new #GOutputStream, or %NULL; unref with g_object_unref() - * - * Since: 0.13.0 - */ -GOutputStream * -gdata_upload_stream_new_resumable (GDataService *service, GDataAuthorizationDomain *domain, const gchar *method, const gchar *upload_uri, - GDataEntry *entry, const gchar *slug, const gchar *content_type, goffset content_length, GCancellable *cancellable) -{ - g_return_val_if_fail (GDATA_IS_SERVICE (service), NULL); - g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL); - g_return_val_if_fail (method != NULL, NULL); - g_return_val_if_fail (upload_uri != NULL, NULL); - g_return_val_if_fail (entry == NULL || GDATA_IS_ENTRY (entry), NULL); - g_return_val_if_fail (slug != NULL, NULL); - g_return_val_if_fail (content_type != NULL, NULL); - g_return_val_if_fail (content_length >= 0, NULL); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); - - /* Create the upload stream */ - return G_OUTPUT_STREAM (g_object_new (GDATA_TYPE_UPLOAD_STREAM, - "method", method, - "upload-uri", upload_uri, - "service", service, - "authorization-domain", domain, - "entry", entry, - "slug", slug, - "content-type", content_type, - "content-length", content_length, - "cancellable", cancellable, - NULL)); -} - -/** - * gdata_upload_stream_get_response: - * @self: a #GDataUploadStream - * @length: (allow-none) (out caller-allocates): return location for the length of the response, or %NULL - * - * Returns the server's response to the upload operation performed by the #GDataUploadStream. If the operation - * is still underway, or the server's response hasn't been received yet, %NULL is returned and @length is set to -1. - * - * If there was an error during the upload operation (but it is complete), %NULL is returned, and @length is set to 0. - * - * While it is safe to call this function from any thread at any time during the network operation, the only way to guarantee that the response has - * been set before calling this function is to have closed the #GDataUploadStream by calling g_output_stream_close() on it, without cancelling - * the close operation. Once the stream has been closed, all network communication is guaranteed to have finished. Note that if a call to - * g_output_stream_close() is cancelled, g_output_stream_is_closed() will immediately start to return %TRUE, even if the #GDataUploadStream is still - * attempting to flush the network buffers asynchronously — consequently, gdata_upload_stream_get_response() may still return %NULL and a @length of - * -1. The only reliable way to determine if the stream has been fully closed in this situation is to check the results - * of gdata_upload_stream_get_response(), rather than g_output_stream_is_closed(). - * - * Return value: the server's response to the upload, or %NULL - * - * Since: 0.5.0 - */ -const gchar * -gdata_upload_stream_get_response (GDataUploadStream *self, gssize *length) -{ - gssize _length; - const gchar *_response; - - g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL); - - g_mutex_lock (&(self->priv->response_mutex)); - - if (self->priv->response_status == SOUP_STATUS_NONE) { - /* We can't touch the message until the network thread has finished using it, since it isn't threadsafe */ - _length = -1; - _response = NULL; - } else if (SOUP_STATUS_IS_SUCCESSFUL (self->priv->response_status) == FALSE) { - /* The response has been received, and was unsuccessful */ - _length = 0; - _response = NULL; - } else { - /* The response has been received, and was successful */ - _length = self->priv->message->response_body->length; - _response = self->priv->message->response_body->data; - } - - g_mutex_unlock (&(self->priv->response_mutex)); - - if (length != NULL) - *length = _length; - return _response; -} - -/** - * gdata_upload_stream_get_service: - * @self: a #GDataUploadStream - * - * Gets the service used to authorize the upload, as passed to gdata_upload_stream_new(). - * - * Return value: (transfer none): the #GDataService used to authorize the upload - * - * Since: 0.5.0 - */ -GDataService * -gdata_upload_stream_get_service (GDataUploadStream *self) -{ - g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL); - return self->priv->service; -} - -/** - * gdata_upload_stream_get_authorization_domain: - * @self: a #GDataUploadStream - * - * Gets the authorization domain used to authorize the upload, as passed to gdata_upload_stream_new(). It may be %NULL if authorization is not - * needed for the upload. - * - * Return value: (transfer none) (allow-none): the #GDataAuthorizationDomain used to authorize the upload, or %NULL - * - * Since: 0.9.0 - */ -GDataAuthorizationDomain * -gdata_upload_stream_get_authorization_domain (GDataUploadStream *self) -{ - g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL); - return self->priv->authorization_domain; -} - -/** - * gdata_upload_stream_get_method: - * @self: a #GDataUploadStream - * - * Gets the HTTP request method being used to upload the file, as passed to gdata_upload_stream_new(). - * - * Return value: the HTTP request method in use - * - * Since: 0.7.0 - */ -const gchar * -gdata_upload_stream_get_method (GDataUploadStream *self) -{ - g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL); - return self->priv->method; -} - -/** - * gdata_upload_stream_get_upload_uri: - * @self: a #GDataUploadStream - * - * Gets the URI the file is being uploaded to, as passed to gdata_upload_stream_new(). - * - * Return value: the URI which the file is being uploaded to - * - * Since: 0.5.0 - */ -const gchar * -gdata_upload_stream_get_upload_uri (GDataUploadStream *self) -{ - g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL); - return self->priv->upload_uri; -} - -/** - * gdata_upload_stream_get_entry: - * @self: a #GDataUploadStream - * - * Gets the entry being used to upload metadata, if one was passed to gdata_upload_stream_new(). - * - * Return value: (transfer none): the entry used for metadata, or %NULL - * - * Since: 0.5.0 - */ -GDataEntry * -gdata_upload_stream_get_entry (GDataUploadStream *self) -{ - g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL); - return self->priv->entry; -} - -/** - * gdata_upload_stream_get_slug: - * @self: a #GDataUploadStream - * - * Gets the slug (filename) of the file being uploaded. - * - * Return value: the slug of the file being uploaded - * - * Since: 0.5.0 - */ -const gchar * -gdata_upload_stream_get_slug (GDataUploadStream *self) -{ - g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL); - return self->priv->slug; -} - -/** - * gdata_upload_stream_get_content_type: - * @self: a #GDataUploadStream - * - * Gets the content type of the file being uploaded. - * - * Return value: the content type of the file being uploaded - * - * Since: 0.5.0 - */ -const gchar * -gdata_upload_stream_get_content_type (GDataUploadStream *self) -{ - g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL); - return self->priv->content_type; -} - -/** - * gdata_upload_stream_get_content_length: - * @self: a #GDataUploadStream - * - * Gets the size (in bytes) of the file being uploaded. This will be -1 for a non-resumable upload, and zero or greater - * for a resumable upload. - * - * Return value: the size of the file being uploaded - * - * Since: 0.13.0 - */ -goffset -gdata_upload_stream_get_content_length (GDataUploadStream *self) -{ - g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), -1); - return self->priv->content_length; -} - -/** - * gdata_upload_stream_get_cancellable: - * @self: a #GDataUploadStream - * - * Gets the #GCancellable for the entire upload operation, #GDataUploadStream:cancellable. - * - * Return value: (transfer none): the #GCancellable for the entire upload operation - * - * Since: 0.8.0 - */ -GCancellable * -gdata_upload_stream_get_cancellable (GDataUploadStream *self) -{ - g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (self), NULL); - g_assert (self->priv->cancellable != NULL); - return self->priv->cancellable; -} diff --git a/gdata/gdata-upload-stream.h b/gdata/gdata-upload-stream.h deleted file mode 100644 index d4a3c7b9..00000000 --- a/gdata/gdata-upload-stream.h +++ /dev/null @@ -1,124 +0,0 @@ -/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ -/* - * GData Client - * Copyright (C) Philip Withnall 2009 - * - * 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 . - */ - -#ifndef GDATA_UPLOAD_STREAM_H -#define GDATA_UPLOAD_STREAM_H - -#include -#include -#include - -#include -#include - -G_BEGIN_DECLS - -/** - * GDATA_LINK_RESUMABLE_CREATE_MEDIA: - * - * The relation type URI of the resumable upload location for resources attached to this resource. - * - * For more information, see the - * GData resumable upload protocol - * specification. - * - * Since: 0.13.0 - */ -#define GDATA_LINK_RESUMABLE_CREATE_MEDIA "http://schemas.google.com/g/2005#resumable-create-media" - -/** - * GDATA_LINK_RESUMABLE_EDIT_MEDIA: - * - * The relation type URI of the resumable update location for resources attached to this resource. - * - * For more information, see the - * GData resumable upload protocol - * specification. - * - * Since: 0.13.0 - */ -#define GDATA_LINK_RESUMABLE_EDIT_MEDIA "http://schemas.google.com/g/2005#resumable-edit-media" - -#define GDATA_TYPE_UPLOAD_STREAM (gdata_upload_stream_get_type ()) -#define GDATA_UPLOAD_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_UPLOAD_STREAM, GDataUploadStream)) -#define GDATA_UPLOAD_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_UPLOAD_STREAM, GDataUploadStreamClass)) -#define GDATA_IS_UPLOAD_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_UPLOAD_STREAM)) -#define GDATA_IS_UPLOAD_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_UPLOAD_STREAM)) -#define GDATA_UPLOAD_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_UPLOAD_STREAM, GDataUploadStreamClass)) - -typedef struct _GDataUploadStreamPrivate GDataUploadStreamPrivate; - -/** - * GDataUploadStream: - * - * All the fields in the #GDataUploadStream structure are private and should never be accessed directly. - * - * Since: 0.5.0 - */ -typedef struct { - GOutputStream parent; - GDataUploadStreamPrivate *priv; -} GDataUploadStream; - -/** - * GDataUploadStreamClass: - * - * All the fields in the #GDataUploadStreamClass structure are private and should never be accessed directly. - * - * Since: 0.5.0 - */ -typedef struct { - /*< private >*/ - GOutputStreamClass parent; - - /*< private >*/ - /* Padding for future expansion */ - void (*_g_reserved0) (void); - void (*_g_reserved1) (void); - void (*_g_reserved2) (void); - void (*_g_reserved3) (void); - void (*_g_reserved4) (void); - void (*_g_reserved5) (void); -} GDataUploadStreamClass; - -GType gdata_upload_stream_get_type (void) G_GNUC_CONST; -G_DEFINE_AUTOPTR_CLEANUP_FUNC (GDataUploadStream, g_object_unref) - -GOutputStream *gdata_upload_stream_new (GDataService *service, GDataAuthorizationDomain *domain, const gchar *method, const gchar *upload_uri, - GDataEntry *entry, const gchar *slug, const gchar *content_type, - GCancellable *cancellable) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; -GOutputStream *gdata_upload_stream_new_resumable (GDataService *service, GDataAuthorizationDomain *domain, const gchar *method, const gchar *upload_uri, - GDataEntry *entry, const gchar *slug, const gchar *content_type, goffset content_length, - GCancellable *cancellable) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; - -const gchar *gdata_upload_stream_get_response (GDataUploadStream *self, gssize *length); - -GDataService *gdata_upload_stream_get_service (GDataUploadStream *self) G_GNUC_PURE; -GDataAuthorizationDomain *gdata_upload_stream_get_authorization_domain (GDataUploadStream *self) G_GNUC_PURE; -const gchar *gdata_upload_stream_get_method (GDataUploadStream *self) G_GNUC_PURE; -const gchar *gdata_upload_stream_get_upload_uri (GDataUploadStream *self) G_GNUC_PURE; -GDataEntry *gdata_upload_stream_get_entry (GDataUploadStream *self) G_GNUC_PURE; -const gchar *gdata_upload_stream_get_slug (GDataUploadStream *self) G_GNUC_PURE; -const gchar *gdata_upload_stream_get_content_type (GDataUploadStream *self) G_GNUC_PURE; -goffset gdata_upload_stream_get_content_length (GDataUploadStream *self) G_GNUC_PURE; -GCancellable *gdata_upload_stream_get_cancellable (GDataUploadStream *self) G_GNUC_PURE; - -G_END_DECLS - -#endif /* !GDATA_UPLOAD_STREAM_H */ diff --git a/gdata/gdata-uploader.c b/gdata/gdata-uploader.c new file mode 100644 index 00000000..88b619a0 --- /dev/null +++ b/gdata/gdata-uploader.c @@ -0,0 +1,1412 @@ +#include +#include +#include +#include + +#include "gdata-uploader.h" +#include "gdata-buffer.h" +#include "gdata-private.h" + +#define BOUNDARY_STRING "0003Z5W789deadbeefRTE456KlemsnoZV" +#define MAX_RESUMABLE_CHUNK_SIZE (512 * 1024) /* bytes = 512 KiB */ + +#define GDATA_TYPE_UPLOADER_INPUT_STREAM (gdata_uploader_input_stream_get_type ()) + +G_DECLARE_FINAL_TYPE (GDataUploaderInputStream, + gdata_uploader_input_stream, + GDATA, UPLOADER_INPUT_STREAM, + GFilterInputStream); + +typedef struct _GDataUploaderInputStream { + GFilterInputStream parent; +} GDataUploaderInputStream; + +enum { + PROP_HEADER = 1, + PROP_MAX_READ, + PROP_TOTAL_WRITTEN, +}; + +typedef struct { + GBytes *header; + GInputStream *header_stream; + gsize header_left; + gint64 read_left; + gsize *total_written; +} GDataUploaderInputStreamPrivate; + +static void gdata_uploader_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface, gpointer interface_data); + +G_DEFINE_TYPE_WITH_CODE (GDataUploaderInputStream, gdata_uploader_input_stream, G_TYPE_FILTER_INPUT_STREAM, + G_ADD_PRIVATE (GDataUploaderInputStream) + G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_INPUT_STREAM, + gdata_uploader_input_stream_pollable_init)) + +static void +gdata_uploader_input_stream_init (GDataUploaderInputStream *stream) +{ +} + +static void +gdata_uploader_input_stream_finalize (GObject *object) +{ + GDataUploaderInputStream *stream = GDATA_UPLOADER_INPUT_STREAM (object); + GDataUploaderInputStreamPrivate *priv = gdata_uploader_input_stream_get_instance_private (stream); + + if (priv->header_stream) { + g_input_stream_close (priv->header_stream, NULL, NULL); + g_object_unref (priv->header_stream); + } + g_clear_pointer (&priv->header, g_bytes_unref); + + G_OBJECT_CLASS (gdata_uploader_input_stream_parent_class)->finalize (object); +} + +static void +gdata_uploader_input_stream_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + GDataUploaderInputStream *stream = GDATA_UPLOADER_INPUT_STREAM (object); + GDataUploaderInputStreamPrivate *priv = gdata_uploader_input_stream_get_instance_private (stream); + + switch (prop_id) { + case PROP_HEADER: + if (!g_value_get_pointer (value)) { + priv->header = NULL; + priv->header_stream = NULL; + priv->header_left = 0; + break; + } + priv->header = g_bytes_ref (g_value_get_pointer (value)); + priv->header_stream = g_memory_input_stream_new_from_bytes (priv->header); + priv->header_left = g_bytes_get_size (priv->header); + break; + case PROP_MAX_READ: + priv->read_left = g_value_get_int64 (value); + break; + case PROP_TOTAL_WRITTEN: + priv->total_written = g_value_get_pointer (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdata_uploader_input_stream_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + GDataUploaderInputStream *stream = GDATA_UPLOADER_INPUT_STREAM (object); + GDataUploaderInputStreamPrivate *priv = gdata_uploader_input_stream_get_instance_private (stream); + + switch (prop_id) { + case PROP_HEADER: + g_value_set_pointer (value, g_bytes_ref (priv->header)); + break; + case PROP_MAX_READ: + g_value_set_int64 (value, priv->read_left); + break; + case PROP_TOTAL_WRITTEN: + g_value_set_pointer (value, priv->total_written); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gssize +read_internal (GInputStream *stream, + void *buffer, + gsize count, + gboolean blocking, + GCancellable *cancellable, + GError **error) +{ + GDataUploaderInputStream *istream = GDATA_UPLOADER_INPUT_STREAM (stream); + GDataUploaderInputStreamPrivate *priv = gdata_uploader_input_stream_get_instance_private (istream); + GError *rerror = NULL; + gssize nread = 0; + gssize sread; + gsize rcount; + char *tbuf = buffer; + + if (priv->read_left == 0) + return 0; + + /* there are bytes left in the header stream */ + if (priv->header_left > 0) { + nread = g_pollable_stream_read (priv->header_stream, buffer, count, + blocking, cancellable, error); + /* error: bail out */ + if (nread < 0) + return nread; + + priv->header_left -= nread; + + /* we've read less than was left, since this is a memory stream this + * indicates that we actually wanted less, so just return for now + */ + if (priv->header_left > 0) + return nread; + + g_input_stream_close (priv->header_stream, NULL, NULL); + g_object_unref (priv->header_stream); + priv->header_stream = NULL; + } + + rcount = count - nread; + if (priv->read_left > 0 && priv->read_left < rcount) + rcount = priv->read_left; + + sread = g_pollable_stream_read (G_FILTER_INPUT_STREAM (stream)->base_stream, + tbuf + nread, rcount, blocking, + cancellable, (nread > 0) ? &rerror : error); + + if (sread < 0) { + /* ensure proper behavior for partial reads */ + if (nread > 0) { + if (g_error_matches (rerror, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_cancellable_reset (cancellable); + g_clear_error (error); + return nread; + } else if (!blocking && g_error_matches (rerror, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { + g_clear_error (error); + return nread; + } + } + if (nread < 0) { + if (error) + *error = rerror; + else + g_clear_error (&rerror); + } + return sread; + } else if (priv->total_written) + *priv->total_written += sread; + + if (priv->read_left > 0) + priv->read_left -= sread; + + return nread + sread; +} + +static gssize +gdata_uploader_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + return read_internal (stream, buffer, count, TRUE, cancellable, error); +} + +static gssize +gdata_uploader_input_stream_skip (GInputStream *stream, + gsize count, + GCancellable *cancellable, + GError **error) +{ + GDataUploaderInputStream *istream = GDATA_UPLOADER_INPUT_STREAM (stream); + GDataUploaderInputStreamPrivate *priv = gdata_uploader_input_stream_get_instance_private (istream); + GError *rerror = NULL; + gssize nread = 0; + gssize sread; + gsize rcount; + + if (priv->read_left == 0) + return 0; + + /* there are bytes left in the header stream */ + if (priv->header_left > 0) { + nread = g_input_stream_skip (priv->header_stream, + count, cancellable, error); + /* error: bail out */ + if (nread < 0) + return nread; + + priv->header_left -= nread; + + /* we've read less than was left, since this is a memory stream this + * indicates that we actually wanted less, so just return for now + */ + if (priv->header_left > 0) + return nread; + + g_input_stream_close (priv->header_stream, NULL, NULL); + g_object_unref (priv->header_stream); + priv->header_stream = NULL; + } + + rcount = count - nread; + if (priv->read_left > 0 && priv->read_left < rcount) + rcount = priv->read_left; + + /* try reading full or partial count from the actual stream */ + sread = g_input_stream_skip (G_FILTER_INPUT_STREAM (stream)->base_stream, + rcount, cancellable, + (nread > 0) ? &rerror : error); + if (sread < 0) { + /* ensure proper behavior for partial reads */ + if (nread > 0 && g_error_matches (rerror, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_cancellable_reset (cancellable); + g_clear_error (error); + return nread; + } + if (!nread) { + if (error) + *error = rerror; + else + g_clear_error (&rerror); + } + return sread; + } else if (priv->total_written) + *priv->total_written += sread; + + if (priv->read_left > 0) + priv->read_left -= sread; + + return nread + sread; +} + +static void +gdata_uploader_input_stream_class_init (GDataUploaderInputStreamClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GInputStreamClass *input_stream_class = G_INPUT_STREAM_CLASS (klass); + + object_class->finalize = gdata_uploader_input_stream_finalize; + object_class->set_property = gdata_uploader_input_stream_set_property; + object_class->get_property = gdata_uploader_input_stream_get_property; + + input_stream_class->read_fn = gdata_uploader_input_stream_read; + input_stream_class->skip = gdata_uploader_input_stream_skip; + + g_object_class_install_property ( + object_class, PROP_HEADER, + g_param_spec_pointer ("header", + "Header", + "The stream's header bytes", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property ( + object_class, PROP_MAX_READ, + g_param_spec_int64 ("max-read", + "Max read", + "Maximum amount to read", + -1, G_MAXINT64, -1, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property ( + object_class, PROP_TOTAL_WRITTEN, + g_param_spec_pointer ("total-written", + "Total written", + "Total data bytes written", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} + +static gboolean +gdata_uploader_input_stream_can_poll (GPollableInputStream *stream) +{ + GInputStream *base_stream = G_FILTER_INPUT_STREAM (stream)->base_stream; + + return G_IS_POLLABLE_INPUT_STREAM (base_stream) && + g_pollable_input_stream_can_poll (G_POLLABLE_INPUT_STREAM (base_stream)); +} + +static gboolean +gdata_uploader_input_stream_is_readable (GPollableInputStream *stream) +{ + /* the memory stream is always readable, so we don't care about that one, + * instead we check the real stream always... + */ + return g_pollable_input_stream_is_readable (G_POLLABLE_INPUT_STREAM (G_FILTER_INPUT_STREAM (stream)->base_stream)); +} + +static gssize +gdata_uploader_input_stream_read_nonblocking (GPollableInputStream *stream, + void *buffer, + gsize count, + GError **error) +{ + return read_internal (G_INPUT_STREAM (stream), + buffer, count, FALSE, NULL, error); +} + +static GSource * +gdata_uploader_input_stream_create_source (GPollableInputStream *stream, + GCancellable *cancellable) +{ + GSource *base_source, *pollable_source; + + base_source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (G_FILTER_INPUT_STREAM (stream)->base_stream), + cancellable); + + g_source_set_dummy_callback (base_source); + pollable_source = g_pollable_source_new (G_OBJECT (stream)); + g_source_add_child_source (pollable_source, base_source); + g_source_unref (base_source); + + return pollable_source; +} + +static void +gdata_uploader_input_stream_pollable_init (GPollableInputStreamInterface *iface, + gpointer iface_data) +{ + iface->can_poll = gdata_uploader_input_stream_can_poll; + iface->is_readable = gdata_uploader_input_stream_is_readable; + iface->read_nonblocking = gdata_uploader_input_stream_read_nonblocking; + iface->create_source = gdata_uploader_input_stream_create_source; +} + +static void gdata_uploader_constructed (GObject *object); +static void gdata_uploader_dispose (GObject *object); +static void gdata_uploader_finalize (GObject *object); +static void gdata_uploader_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); +static void gdata_uploader_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); + +struct _GDataUploaderPrivate { + gchar *method; + gchar *upload_uri; + GDataService *service; + GDataAuthorizationDomain *authorization_domain; + GDataEntry *entry; + gchar *slug; + gchar *content_type; + goffset content_length; /* -1 for non-resumable uploads; 0 or greater for resumable ones */ + SoupSession *session; + SoupMessage *message; + GBytes *header; + GInputStream *body_stream; + gsize total_written; +}; + +enum { + PROP_SERVICE = 1, + PROP_UPLOAD_URI, + PROP_ENTRY, + PROP_SLUG, + PROP_CONTENT_TYPE, + PROP_METHOD, + PROP_AUTHORIZATION_DOMAIN, + PROP_CONTENT_LENGTH, +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GDataUploader, gdata_uploader, G_TYPE_OBJECT) + +static void +gdata_uploader_class_init (GDataUploaderClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->constructed = gdata_uploader_constructed; + gobject_class->dispose = gdata_uploader_dispose; + gobject_class->finalize = gdata_uploader_finalize; + gobject_class->get_property = gdata_uploader_get_property; + gobject_class->set_property = gdata_uploader_set_property; + + /** + * GDataUploader:service: + * + * The service which is used to authorize the upload, and to which the upload relates. + * + * Since: 0.5.0 + */ + g_object_class_install_property (gobject_class, PROP_SERVICE, + g_param_spec_object ("service", + "Service", "The service which is used to authorize the upload.", + GDATA_TYPE_SERVICE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GDataUploader:authorization-domain: + * + * The authorization domain for the upload, against which the #GDataService:authorizer for the #GDataDownloader:service should be + * authorized. This may be %NULL if authorization is not needed for the upload. + * + * Since: 0.9.0 + */ + g_object_class_install_property (gobject_class, PROP_AUTHORIZATION_DOMAIN, + g_param_spec_object ("authorization-domain", + "Authorization domain", "The authorization domain for the upload.", + GDATA_TYPE_AUTHORIZATION_DOMAIN, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GDataUploader:method: + * + * The HTTP request method to use when uploading the file. + * + * Since: 0.7.0 + */ + g_object_class_install_property (gobject_class, PROP_METHOD, + g_param_spec_string ("method", + "Method", "The HTTP request method to use when uploading the file.", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GDataUploader:upload-uri: + * + * The URI to upload the data and metadata to. This must be HTTPS. + * + * Since: 0.5.0 + */ + g_object_class_install_property (gobject_class, PROP_UPLOAD_URI, + g_param_spec_string ("upload-uri", + "Upload URI", "The URI to upload the data and metadata to.", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GDataUploader:entry: + * + * The entry used for metadata to upload. + * + * Since: 0.5.0 + */ + g_object_class_install_property (gobject_class, PROP_ENTRY, + g_param_spec_object ("entry", + "Entry", "The entry used for metadata to upload.", + GDATA_TYPE_ENTRY, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GDataUploader:slug: + * + * The slug of the file being uploaded. This is usually the display name of the file (i.e. as returned by g_file_info_get_display_name()). + * + * Since: 0.5.0 + */ + g_object_class_install_property (gobject_class, PROP_SLUG, + g_param_spec_string ("slug", + "Slug", "The slug of the file being uploaded.", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GDataUploader:content-length: + * + * The content length (in bytes) of the file being uploaded (i.e. as returned by g_file_info_get_size()). Note that this does not include the + * length of the XML serialisation of #GDataUploader:entry, if set. + * + * If this is -1 the upload will be non-resumable; if it is non-negative, the upload will be resumable. + * + * Since: 0.13.0 + */ + g_object_class_install_property (gobject_class, PROP_CONTENT_LENGTH, + g_param_spec_int64 ("content-length", + "Content length", "The content length (in bytes) of the file being uploaded.", + -1, G_MAXINT64, -1, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GDataUploader:content-type: + * + * The content type of the file being uploaded (i.e. as returned by g_file_info_get_content_type()). + * + * Since: 0.5.0 + */ + g_object_class_install_property (gobject_class, PROP_CONTENT_TYPE, + g_param_spec_string ("content-type", + "Content type", "The content type of the file being uploaded.", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +gdata_uploader_init (GDataUploader *self) +{ + self->priv = gdata_uploader_get_instance_private (self); +} + +static SoupMessage * +build_message (GDataUploader *self, GDataService *service, const gchar *method, const gchar *upload_uri) +{ + SoupMessage *new_message; + GUri *_uri, *_uri_parsed; + + /* Build the message */ + _uri_parsed = g_uri_parse (upload_uri, SOUP_HTTP_URI_FLAGS, NULL); + _uri = soup_uri_copy (_uri_parsed, SOUP_URI_PORT, + _gdata_service_get_https_port (), SOUP_URI_NONE); + g_uri_unref (_uri_parsed); + new_message = _gdata_service_new_message_from_uri (service, method, _uri); + g_uri_unref (_uri); + + return new_message; +} + +static void +gdata_uploader_constructed (GObject *object) +{ + GDataUploaderPrivate *priv; + GDataServiceClass *service_klass; + GUri *uri = NULL; + + /* Chain up to the parent class */ + G_OBJECT_CLASS (gdata_uploader_parent_class)->constructed (object); + priv = GDATA_UPLOADER (object)->priv; + + /* The upload URI must be HTTPS. */ + uri = g_uri_parse (priv->upload_uri, SOUP_HTTP_URI_FLAGS, NULL); + g_assert_cmpstr (g_uri_get_scheme (uri), ==, "https"); + g_uri_unref (uri); + + /* Build the message */ + priv->message = build_message (GDATA_UPLOADER (object), priv->service, priv->method, priv->upload_uri); + + if (priv->slug != NULL) + soup_message_headers_append (soup_message_get_request_headers (priv->message), "Slug", priv->slug); + + if (priv->content_length == -1) { + /* Non-resumable upload */ + soup_message_headers_set_encoding (soup_message_get_request_headers (priv->message), SOUP_ENCODING_CHUNKED); + + /* The Content-Type should be multipart/related if we're also uploading the metadata (entry != NULL), + * and the given content_type otherwise. */ + if (priv->entry != NULL) { + gchar *first_part_header, *upload_data; + gchar *second_part_header; + GDataParsableClass *parsable_klass; + GByteArray *body; + + parsable_klass = GDATA_PARSABLE_GET_CLASS (priv->entry); + g_assert (parsable_klass->get_content_type != NULL); + + soup_message_headers_set_content_type (soup_message_get_request_headers (priv->message), "multipart/related; boundary=" BOUNDARY_STRING, NULL); + + if (g_strcmp0 (parsable_klass->get_content_type (), "application/json") == 0) { + upload_data = gdata_parsable_get_json (GDATA_PARSABLE (priv->entry)); + } else { + upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (priv->entry)); + } + + body = g_byte_array_new (); + + /* Start by writing out the entry; then the thread has something to write to the network when it's created */ + first_part_header = g_strdup_printf ("--" BOUNDARY_STRING "\n" + "Content-Type: %s; charset=UTF-8\n\n", + parsable_klass->get_content_type ()); + second_part_header = g_strdup_printf ("\n--" BOUNDARY_STRING "\n" + "Content-Type: %s\n" + "Content-Transfer-Encoding: binary\n\n", + priv->content_type); + + /* Push the message parts onto the message body; we can skip the buffer, since the network thread hasn't yet been created, + * so we're the sole thread accessing the SoupMessage. */ + g_byte_array_append (body, (const guint8 *)first_part_header, strlen (first_part_header)); + g_byte_array_append (body, (const guint8 *)upload_data, strlen (upload_data)); + g_byte_array_append (body, (const guint8 *)second_part_header, strlen (second_part_header)); + + g_free (first_part_header); + g_free (second_part_header); + g_free (upload_data); + + priv->header = g_byte_array_free_to_bytes (body); + } else { + soup_message_headers_set_content_type (soup_message_get_request_headers (priv->message), priv->content_type, NULL); + priv->header = NULL; + } + } else { + gchar *content_length_str; + + /* Resumable upload's initial request */ + soup_message_headers_set_encoding (soup_message_get_request_headers (priv->message), SOUP_ENCODING_CONTENT_LENGTH); + soup_message_headers_replace (soup_message_get_request_headers (priv->message), "X-Upload-Content-Type", priv->content_type); + + content_length_str = g_strdup_printf ("%" G_GOFFSET_FORMAT, priv->content_length); + soup_message_headers_replace (soup_message_get_request_headers (priv->message), "X-Upload-Content-Length", content_length_str); + g_free (content_length_str); + + if (priv->entry != NULL) { + GDataParsableClass *parsable_klass; + gchar *content_type, *upload_data; + + parsable_klass = GDATA_PARSABLE_GET_CLASS (priv->entry); + g_assert (parsable_klass->get_content_type != NULL); + + if (g_strcmp0 (parsable_klass->get_content_type (), "application/json") == 0) { + upload_data = gdata_parsable_get_json (GDATA_PARSABLE (priv->entry)); + } else { + upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (priv->entry)); + } + + content_type = g_strdup_printf ("%s; charset=UTF-8", + parsable_klass->get_content_type ()); + soup_message_headers_set_content_type (soup_message_get_request_headers (priv->message), + content_type, + NULL); + g_free (content_type); + + priv->header = g_bytes_new_take (upload_data, strlen (upload_data)); + } else { + soup_message_headers_set_content_length (soup_message_get_request_headers (priv->message), 0); + } + } + + /* Make sure the headers are set. HACK: This should actually be in build_message(), but we have to work around + * http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3033 in GDataDocumentsService's append_query_headers(). */ + service_klass = GDATA_SERVICE_GET_CLASS (priv->service); + if (service_klass->append_query_headers != NULL) { + service_klass->append_query_headers (priv->service, priv->authorization_domain, priv->message); + } + + /* If the entry exists and has an ETag, we assume we're updating the entry, so we can set the If-Match header */ + if (priv->entry != NULL && gdata_entry_get_etag (priv->entry) != NULL) + soup_message_headers_append (soup_message_get_request_headers (priv->message), "If-Match", gdata_entry_get_etag (priv->entry)); + + /* Uploading doesn't actually start until the first call to write() */ +} + +static void +gdata_uploader_dispose (GObject *object) +{ + GDataUploaderPrivate *priv = GDATA_UPLOADER (object)->priv; + + g_clear_object (&priv->service); + g_clear_object (&priv->authorization_domain); + g_clear_object (&priv->message); + g_clear_object (&priv->entry); + g_clear_pointer (&priv->header, g_bytes_unref); + + /* Chain up to the parent class */ + G_OBJECT_CLASS (gdata_uploader_parent_class)->dispose (object); +} + +static void +gdata_uploader_finalize (GObject *object) +{ + GDataUploaderPrivate *priv = GDATA_UPLOADER (object)->priv; + + g_free (priv->upload_uri); + g_free (priv->method); + g_free (priv->slug); + g_free (priv->content_type); + + /* Chain up to the parent class */ + G_OBJECT_CLASS (gdata_uploader_parent_class)->finalize (object); +} + +static void +gdata_uploader_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) +{ + GDataUploaderPrivate *priv = GDATA_UPLOADER (object)->priv; + + switch (property_id) { + case PROP_SERVICE: + g_value_set_object (value, priv->service); + break; + case PROP_AUTHORIZATION_DOMAIN: + g_value_set_object (value, priv->authorization_domain); + break; + case PROP_METHOD: + g_value_set_string (value, priv->method); + break; + case PROP_UPLOAD_URI: + g_value_set_string (value, priv->upload_uri); + break; + case PROP_ENTRY: + g_value_set_object (value, priv->entry); + break; + case PROP_SLUG: + g_value_set_string (value, priv->slug); + break; + case PROP_CONTENT_TYPE: + g_value_set_string (value, priv->content_type); + break; + case PROP_CONTENT_LENGTH: + g_value_set_int64 (value, priv->content_length); + break; + default: + /* We don't have any other property... */ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gdata_uploader_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) +{ + GDataUploaderPrivate *priv = GDATA_UPLOADER (object)->priv; + + switch (property_id) { + case PROP_SERVICE: + priv->service = g_value_dup_object (value); + priv->session = _gdata_service_get_session (priv->service); + break; + case PROP_AUTHORIZATION_DOMAIN: + priv->authorization_domain = g_value_dup_object (value); + break; + case PROP_METHOD: + priv->method = g_value_dup_string (value); + break; + case PROP_UPLOAD_URI: + priv->upload_uri = g_value_dup_string (value); + break; + case PROP_ENTRY: + priv->entry = g_value_dup_object (value); + break; + case PROP_SLUG: + priv->slug = g_value_dup_string (value); + break; + case PROP_CONTENT_TYPE: + priv->content_type = g_value_dup_string (value); + break; + case PROP_CONTENT_LENGTH: + priv->content_length = g_value_get_int64 (value); + break; + default: + /* We don't have any other property... */ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +GDataUploader * +gdata_uploader_new (GDataService *service, GDataAuthorizationDomain *domain, const gchar *method, const gchar *upload_uri, GDataEntry *entry, + const gchar *slug, const gchar *content_type) +{ + g_return_val_if_fail (GDATA_IS_SERVICE (service), NULL); + g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL); + g_return_val_if_fail (method != NULL, NULL); + g_return_val_if_fail (upload_uri != NULL, NULL); + g_return_val_if_fail (entry == NULL || GDATA_IS_ENTRY (entry), NULL); + g_return_val_if_fail (slug != NULL, NULL); + g_return_val_if_fail (content_type != NULL, NULL); + + return GDATA_UPLOADER (g_object_new (GDATA_TYPE_UPLOADER, + "method", method, + "upload-uri", upload_uri, + "service", service, + "authorization-domain", domain, + "entry", entry, + "slug", slug, + "content-type", content_type, + NULL)); +} + +GDataUploader * +gdata_uploader_new_resumable (GDataService *service, GDataAuthorizationDomain *domain, const gchar *method, const gchar *upload_uri, + GDataEntry *entry, const gchar *slug, const gchar *content_type, goffset content_length) +{ + g_return_val_if_fail (GDATA_IS_SERVICE (service), NULL); + g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL); + g_return_val_if_fail (method != NULL, NULL); + g_return_val_if_fail (upload_uri != NULL, NULL); + g_return_val_if_fail (entry == NULL || GDATA_IS_ENTRY (entry), NULL); + g_return_val_if_fail (slug != NULL, NULL); + g_return_val_if_fail (content_type != NULL, NULL); + g_return_val_if_fail (content_length >= 0, NULL); + + return GDATA_UPLOADER (g_object_new (GDATA_TYPE_UPLOADER, + "method", method, + "upload-uri", upload_uri, + "service", service, + "authorization-domain", domain, + "entry", entry, + "slug", slug, + "content-type", content_type, + "content-length", content_length, + NULL)); +} + +void +gdata_uploader_set_input (GDataUploader *self, GInputStream *stream) +{ + self->priv->body_stream = g_object_ref (stream); +} + +void +gdata_uploader_set_input_from_bytes (GDataUploader *self, GBytes *bytes) +{ + gdata_uploader_set_input (self, + g_memory_input_stream_new_from_bytes (bytes)); +} + +static void +wrote_body_data_cb (SoupMessage *message, guint chunk_size, + gsize *total_written) +{ + *total_written += chunk_size; +} + +static GError * +upload_status_error (GDataUploaderPrivate *priv, SoupStatus status, + GBytes *response) +{ + GError *error = NULL; + GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (priv->service); + klass->parse_error_response (priv->service, GDATA_OPERATION_UPLOAD, + status, + soup_message_get_reason_phrase (priv->message), + g_bytes_get_data (response, NULL), + g_bytes_get_size (response), + &error); + return error; + +} + +GBytes * +gdata_uploader_send (GDataUploader *self, GCancellable *cancellable, + gsize *total_written, GError **error) +{ + GDataUploaderPrivate *priv = self->priv; + GDataAuthorizer *authorizer; + GInputStream *mbody; + g_autoptr(GBytes) response = NULL; + SoupStatus status; + + priv->total_written = 0; + + if (total_written) + *total_written = 0; + + /* FIXME: Refresh authorization before sending message in order to prevent authorization errors during transfer. + * See: https://gitlab.gnome.org/GNOME/libgdata/issues/23 */ + authorizer = gdata_service_get_authorizer (priv->service); + if (authorizer) { + g_autoptr(GError) error = NULL; + + gdata_authorizer_refresh_authorization (authorizer, cancellable, + &error); + if (error != NULL) + g_debug ("Error returned when refreshing authorization: %s", + error->message); + else + gdata_authorizer_process_request (authorizer, + priv->authorization_domain, + priv->message); + } + + if (priv->content_length == -1) { + /* do a non-resumable upload */ + if (priv->header) { + /* metadata plus content */ + mbody = g_object_new (GDATA_TYPE_UPLOADER_INPUT_STREAM, + "base-stream", priv->body_stream, + "header", priv->header, + "total-written", total_written, NULL); + soup_message_set_request_body (priv->message, NULL, + mbody, -1); + g_object_unref (mbody); + } else { + /* content-only upload */ + soup_message_set_request_body (priv->message, NULL, + priv->body_stream, -1); + if (total_written) + g_signal_connect (priv->message, "wrote-body-data", + G_CALLBACK (wrote_body_data_cb), + total_written); + } + + return soup_session_send_and_read (priv->session, + priv->message, + cancellable, + error); + } + + /* resumable upload needs multiple requests */ + if (priv->header) { + soup_message_set_request_body_from_bytes (priv->message, NULL, + priv->header); + } + + /* send initial request */ + response = soup_session_send_and_read (priv->session, priv->message, + cancellable, error); + if (!response) + return NULL; + + status = soup_message_get_status (priv->message); + + /* unsuccessful upload */ + if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { + if (error) + *error = upload_status_error (priv, status, response); + return NULL; + } + + /* metadata-only upload */ + if (priv->content_length == 0 && status == SOUP_STATUS_CREATED) + return g_bytes_ref (response); + + for (;;) { + GDataServiceClass *klass; + gchar *new_uri; + SoupMessage *new_message; + gsize next_chunk_length; + + next_chunk_length = MIN (priv->content_length - priv->total_written, + MAX_RESUMABLE_CHUNK_SIZE); + + new_uri = g_strdup (soup_message_headers_get_one (soup_message_get_response_headers (priv->message), "Location")); + if (new_uri == NULL) { + new_uri = g_uri_to_string_partial (soup_message_get_uri (priv->message), G_URI_HIDE_PASSWORD); + } + + new_message = build_message (self, priv->service, + SOUP_METHOD_PUT, new_uri); + + g_signal_connect (new_message, "wrote-body-data", + G_CALLBACK (wrote_body_data_cb), &priv->total_written); + + g_free (new_uri); + + mbody = g_object_new (GDATA_TYPE_UPLOADER_INPUT_STREAM, + "base-stream", priv->body_stream, + "close-base-stream", FALSE, + "max-read", (gint64)next_chunk_length, NULL); + + soup_message_set_request_body (new_message, priv->content_type, + mbody, next_chunk_length); + + g_object_unref (mbody); + + soup_message_headers_set_content_range (soup_message_get_request_headers (new_message), + priv->total_written, + priv->total_written + next_chunk_length - 1, + priv->content_length); + + /* Make sure the headers are set. HACK: This should actually be in build_message(), but we have to work around + * http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3033 in GDataDocumentsService's append_query_headers(). */ + klass = GDATA_SERVICE_GET_CLASS (priv->service); + if (klass->append_query_headers != NULL) { + klass->append_query_headers (priv->service, priv->authorization_domain, new_message); + } + + g_object_unref (priv->message); + priv->message = new_message; + + g_bytes_unref (response); + response = soup_session_send_and_read (priv->session, + priv->message, + cancellable, + error); + if (!response) { + /* error */ + return NULL; + } + + status = soup_message_get_status (priv->message); + + if (status == 308) { + /* continuation */ + continue; + } + + /* completed */ + if (SOUP_STATUS_IS_SUCCESSFUL (status)) { + if (total_written) + *total_written = priv->total_written; + return g_bytes_ref (response); + } + + /* error */ + if (error) + *error = upload_status_error (priv, status, response); + return NULL; + } +} + +static void +non_resumable_cb (GObject *object, GAsyncResult *result, gpointer user_data) +{ + SoupSession *session = SOUP_SESSION (object); + g_autoptr(GTask) task = user_data; + GError *error = NULL; + GBytes *body; + + body = soup_session_send_and_read_finish (session, result, &error); + if (body) + g_task_return_pointer (task, body, (GDestroyNotify) g_bytes_unref); + else + g_task_return_error (task, error); +} + +static void try_send_chunk (GTask *task); + +static void +send_chunk_cb (GObject *object, GAsyncResult *result, gpointer user_data) +{ + SoupSession *session = SOUP_SESSION (object); + g_autoptr(GTask) task = user_data; + GDataUploader *self = g_task_get_task_data (task); + GDataUploaderPrivate *priv = self->priv; + GError *error = NULL; + guint status; + g_autoptr(GBytes) body = NULL; + + body = soup_session_send_and_read_finish (session, result, &error); + if (!body) { + /* error */ + g_task_return_error (task, error); + return; + } + + status = soup_message_get_status (soup_session_get_async_result_message (session, result)); + + if (status == 308) { + /* continuation */ + try_send_chunk (g_object_ref (task)); + return; + } + + /* completed */ + if (SOUP_STATUS_IS_SUCCESSFUL (status)) { + g_task_return_pointer (task, body, (GDestroyNotify) g_bytes_unref); + return; + } + + /* error */ + error = upload_status_error (priv, status, body); + g_task_return_error (task, error); +} + +static void +try_send_chunk (GTask *task) +{ + GDataUploader *self = g_task_get_task_data (task); + GDataUploaderPrivate *priv = self->priv; + GDataServiceClass *klass; + GInputStream *mbody; + gchar *new_uri; + SoupMessage *new_message; + gsize next_chunk_length; + + next_chunk_length = MIN (priv->content_length - priv->total_written, + MAX_RESUMABLE_CHUNK_SIZE); + + new_uri = g_strdup (soup_message_headers_get_one (soup_message_get_response_headers (priv->message), "Location")); + if (new_uri == NULL) { + new_uri = g_uri_to_string_partial (soup_message_get_uri (priv->message), G_URI_HIDE_PASSWORD); + } + + new_message = build_message (self, priv->service, + SOUP_METHOD_PUT, new_uri); + + g_signal_connect (new_message, "wrote-body-data", + G_CALLBACK (wrote_body_data_cb), &priv->total_written); + + g_free (new_uri); + + mbody = g_object_new (GDATA_TYPE_UPLOADER_INPUT_STREAM, + "base-stream", priv->body_stream, + "close-base-stream", FALSE, + "max-read", (gint64)next_chunk_length, NULL); + + soup_message_set_request_body (new_message, priv->content_type, + mbody, next_chunk_length); + + g_object_unref (mbody); + + soup_message_headers_set_content_range (soup_message_get_request_headers (new_message), + priv->total_written, + priv->total_written + next_chunk_length - 1, + priv->content_length); + + /* Make sure the headers are set. HACK: This should actually be in build_message(), but we have to work around + * http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3033 in GDataDocumentsService's append_query_headers(). */ + klass = GDATA_SERVICE_GET_CLASS (priv->service); + if (klass->append_query_headers != NULL) { + klass->append_query_headers (priv->service, priv->authorization_domain, new_message); + } + + g_object_unref (priv->message); + priv->message = new_message; + + soup_session_send_and_read_async (priv->session, priv->message, + G_PRIORITY_DEFAULT, + g_task_get_cancellable (task), + send_chunk_cb, + task); +} + +static void +resumable_cb (GObject *object, GAsyncResult *result, gpointer user_data) +{ + SoupSession *session = SOUP_SESSION (object); + g_autoptr(GTask) task = user_data; + GDataUploader *self = g_task_get_task_data (task); + GDataUploaderPrivate *priv = self->priv; + GError *error = NULL; + g_autoptr(GBytes) body = NULL; + guint status; + + body = soup_session_send_and_read_finish (session, result, &error); + if (!body) { + g_task_return_error (task, error); + return; + } + + status = soup_message_get_status (priv->message); + + /* unsuccessful upload */ + if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { + error = upload_status_error (priv, status, body); + g_task_return_error (task, error); + return; + } + + /* metadata-only upload */ + if (priv->content_length == 0 && status == SOUP_STATUS_CREATED) { + g_task_return_pointer (task, body, (GDestroyNotify) g_bytes_unref); + } + + /* otherwise proceed */ + try_send_chunk (g_object_ref (task)); +} + +static void +send_async_cb (GTask *task) +{ + GDataUploader *self = g_task_get_task_data (task); + GDataUploaderPrivate *priv = self->priv; + + if (priv->content_length == -1) { + /* do a non-resumable upload */ + if (priv->header) { + g_autoptr(GInputStream) actual_body = NULL; + /* metadata plus content */ + actual_body = g_object_new (GDATA_TYPE_UPLOADER_INPUT_STREAM, + "base-stream", priv->body_stream, + "header", priv->header, + "total-written", &priv->total_written, NULL); + soup_message_set_request_body (priv->message, NULL, + actual_body, -1); + } else { + /* content-only upload */ + soup_message_set_request_body (priv->message, NULL, + priv->body_stream, -1); + g_signal_connect (priv->message, "wrote-body-data", + G_CALLBACK (wrote_body_data_cb), + &priv->total_written); + } + + soup_session_send_and_read_async (priv->session, + priv->message, + G_PRIORITY_DEFAULT, + g_task_get_cancellable (task), + non_resumable_cb, + task); + return; + } + + /* resumable upload needs multiple requests */ + if (priv->header) { + soup_message_set_request_body_from_bytes (priv->message, NULL, + priv->header); + } + + soup_session_send_and_read_async (priv->session, + priv->message, + G_PRIORITY_DEFAULT, + g_task_get_cancellable (task), + resumable_cb, + task); +} + +static void +refresh_auth_cb (GObject *object, GAsyncResult *result, gpointer user_data) +{ + GDataAuthorizer *authorizer = GDATA_AUTHORIZER (object); + GError *error = NULL; + GTask *task = user_data; + GDataUploader *self = g_task_get_task_data (task); + GDataUploaderPrivate *priv = self->priv; + + gdata_authorizer_refresh_authorization_finish (authorizer, result, &error); + if (error) { + g_debug ("Error returned when refreshing authorization: %s", + error->message); + g_error_free (error); + } else + gdata_authorizer_process_request (authorizer, + priv->authorization_domain, + priv->message); + + send_async_cb (task); +} + +void +gdata_uploader_send_async (GDataUploader *self, GCancellable *cancellable, + GAsyncReadyCallback callback, gpointer user_data) +{ + GDataAuthorizer *authorizer; + GTask *task; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, gdata_uploader_send_async); + g_task_set_task_data (task, g_object_ref (self), g_object_unref); + + self->priv->total_written = 0; + + authorizer = gdata_service_get_authorizer (self->priv->service); + if (authorizer) { + g_autoptr(GError) error = NULL; + + gdata_authorizer_refresh_authorization_async (authorizer, g_task_get_cancellable (task), refresh_auth_cb, task); + return; + } + + send_async_cb (task); +} + +GBytes * +gdata_uploader_send_finish (GDataUploader *self, GAsyncResult *result, + gsize *total_written, GError **error) +{ + g_return_val_if_fail (GDATA_IS_UPLOADER (self), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + g_return_val_if_fail (g_task_is_valid (result, self), NULL); + g_return_val_if_fail (g_async_result_is_tagged (result, gdata_uploader_send_async), NULL); + + if (total_written) + *total_written = GDATA_UPLOADER (g_task_get_task_data (G_TASK (result)))->priv->total_written; + + return g_task_propagate_pointer (G_TASK (result), error); +} + +/** + * gdata_uploader_get_service: + * @self: a #GDataUploader + * + * Gets the service used to authorize the upload, as passed to gdata_uploader_new(). + * + * Return value: (transfer none): the #GDataService used to authorize the upload + * + * Since: 0.5.0 + */ +GDataService * +gdata_uploader_get_service (GDataUploader *self) +{ + g_return_val_if_fail (GDATA_IS_UPLOADER (self), NULL); + return self->priv->service; +} + +/** + * gdata_uploader_get_authorization_domain: + * @self: a #GDataUploader + * + * Gets the authorization domain used to authorize the upload, as passed to gdata_uploader_new(). It may be %NULL if authorization is not + * needed for the upload. + * + * Return value: (transfer none) (allow-none): the #GDataAuthorizationDomain used to authorize the upload, or %NULL + * + * Since: 0.9.0 + */ +GDataAuthorizationDomain * +gdata_uploader_get_authorization_domain (GDataUploader *self) +{ + g_return_val_if_fail (GDATA_IS_UPLOADER (self), NULL); + return self->priv->authorization_domain; +} + +/** + * gdata_uploader_get_method: + * @self: a #GDataUploader + * + * Gets the HTTP request method being used to upload the file, as passed to gdata_uploader_new(). + * + * Return value: the HTTP request method in use + * + * Since: 0.7.0 + */ +const gchar * +gdata_uploader_get_method (GDataUploader *self) +{ + g_return_val_if_fail (GDATA_IS_UPLOADER (self), NULL); + return self->priv->method; +} + +/** + * gdata_uploader_get_upload_uri: + * @self: a #GDataUploader + * + * Gets the URI the file is being uploaded to, as passed to gdata_uploader_new(). + * + * Return value: the URI which the file is being uploaded to + * + * Since: 0.5.0 + */ +const gchar * +gdata_uploader_get_upload_uri (GDataUploader *self) +{ + g_return_val_if_fail (GDATA_IS_UPLOADER (self), NULL); + return self->priv->upload_uri; +} + +/** + * gdata_uploader_get_entry: + * @self: a #GDataUploader + * + * Gets the entry being used to upload metadata, if one was passed to gdata_uploader_new(). + * + * Return value: (transfer none): the entry used for metadata, or %NULL + * + * Since: 0.5.0 + */ +GDataEntry * +gdata_uploader_get_entry (GDataUploader *self) +{ + g_return_val_if_fail (GDATA_IS_UPLOADER (self), NULL); + return self->priv->entry; +} + +/** + * gdata_uploader_get_slug: + * @self: a #GDataUploader + * + * Gets the slug (filename) of the file being uploaded. + * + * Return value: the slug of the file being uploaded + * + * Since: 0.5.0 + */ +const gchar * +gdata_uploader_get_slug (GDataUploader *self) +{ + g_return_val_if_fail (GDATA_IS_UPLOADER (self), NULL); + return self->priv->slug; +} + +/** + * gdata_uploader_get_content_type: + * @self: a #GDataUploader + * + * Gets the content type of the file being uploaded. + * + * Return value: the content type of the file being uploaded + * + * Since: 0.5.0 + */ +const gchar * +gdata_uploader_get_content_type (GDataUploader *self) +{ + g_return_val_if_fail (GDATA_IS_UPLOADER (self), NULL); + return self->priv->content_type; +} + +/** + * gdata_uploader_get_content_length: + * @self: a #GDataUploader + * + * Gets the size (in bytes) of the file being uploaded. This will be -1 for a non-resumable upload, and zero or greater + * for a resumable upload. + * + * Return value: the size of the file being uploaded + * + * Since: 0.13.0 + */ +goffset +gdata_uploader_get_content_length (GDataUploader *self) +{ + g_return_val_if_fail (GDATA_IS_UPLOADER (self), -1); + return self->priv->content_length; +} diff --git a/gdata/gdata-uploader.h b/gdata/gdata-uploader.h new file mode 100644 index 00000000..aa00c45e --- /dev/null +++ b/gdata/gdata-uploader.h @@ -0,0 +1,87 @@ +#ifndef GDATA_UPLOADER_H +#define GDATA_UPLOADER_H + +#include +#include +#include + +#include +#include + +G_BEGIN_DECLS + +/** + * GDATA_LINK_RESUMABLE_CREATE_MEDIA: + * + * The relation type URI of the resumable upload location for resources attached to this resource. + * + * For more information, see the + * GData resumable upload protocol + * specification. + * + * Since: 0.13.0 + */ +#define GDATA_LINK_RESUMABLE_CREATE_MEDIA "http://schemas.google.com/g/2005#resumable-create-media" + +/** + * GDATA_LINK_RESUMABLE_EDIT_MEDIA: + * + * The relation type URI of the resumable update location for resources attached to this resource. + * + * For more information, see the + * GData resumable upload protocol + * specification. + * + * Since: 0.13.0 + */ +#define GDATA_LINK_RESUMABLE_EDIT_MEDIA "http://schemas.google.com/g/2005#resumable-edit-media" + +#define GDATA_TYPE_UPLOADER (gdata_uploader_get_type ()) + +G_DECLARE_FINAL_TYPE (GDataUploader, gdata_uploader, GDATA, UPLOADER, GObject); + +typedef struct _GDataUploaderPrivate GDataUploaderPrivate; + +struct _GDataUploader { + GObject parent; + GDataUploaderPrivate *priv; +}; + +struct _GDataUploaderClass { + /*< private >*/ + GObjectClass parent; + + /*< private >*/ + /* Padding for future expansion */ + void (*_g_reserved0) (void); + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); +}; + +GDataUploader *gdata_uploader_new (GDataService *service, GDataAuthorizationDomain *domain, const gchar *method, const gchar *upload_uri, + GDataEntry *entry, const gchar *slug, const gchar *content_type) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; +GDataUploader *gdata_uploader_new_resumable (GDataService *service, GDataAuthorizationDomain *domain, const gchar *method, const gchar *upload_uri, + GDataEntry *entry, const gchar *slug, const gchar *content_type, goffset content_length) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; + +void gdata_uploader_set_input (GDataUploader *self, GInputStream *stream); +void gdata_uploader_set_input_from_bytes (GDataUploader *self, GBytes *bytes); + +GBytes *gdata_uploader_send (GDataUploader *self, GCancellable *cancellable, gsize *total_written, GError **error); +void gdata_uploader_send_async (GDataUploader *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); +GBytes *gdata_uploader_send_finish (GDataUploader *self, GAsyncResult *result, gsize *total_written, GError **error); + +GDataService *gdata_uploader_get_service (GDataUploader *self) G_GNUC_PURE; +GDataAuthorizationDomain *gdata_uploader_get_authorization_domain (GDataUploader *self) G_GNUC_PURE; +const gchar *gdata_uploader_get_method (GDataUploader *self) G_GNUC_PURE; +const gchar *gdata_uploader_get_upload_uri (GDataUploader *self) G_GNUC_PURE; +GDataEntry *gdata_uploader_get_entry (GDataUploader *self) G_GNUC_PURE; +const gchar *gdata_uploader_get_slug (GDataUploader *self) G_GNUC_PURE; +const gchar *gdata_uploader_get_content_type (GDataUploader *self) G_GNUC_PURE; +goffset gdata_uploader_get_content_length (GDataUploader *self) G_GNUC_PURE; + +G_END_DECLS + +#endif /* !GDATA_UPLOAD_STREAM_H */ diff --git a/gdata/gdata.h b/gdata/gdata.h index 3c4b3eb9..5b821337 100644 --- a/gdata/gdata.h +++ b/gdata/gdata.h @@ -31,7 +31,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/gdata/meson.build b/gdata/meson.build index 728bfc1f..6acc32c5 100644 --- a/gdata/meson.build +++ b/gdata/meson.build @@ -38,7 +38,7 @@ headers = files( 'gdata-query.h', 'gdata-service.h', 'gdata-types.h', - 'gdata-upload-stream.h', + 'gdata-uploader.h', ) sources += files( @@ -62,7 +62,7 @@ sources += files( 'gdata-query.c', 'gdata-service.c', 'gdata-types.c', - 'gdata-upload-stream.c', + 'gdata-uploader.c', ) if enable_goa diff --git a/gdata/services/documents/gdata-documents-service.c b/gdata/services/documents/gdata-documents-service.c index 54015f70..39aa39ee 100644 --- a/gdata/services/documents/gdata-documents-service.c +++ b/gdata/services/documents/gdata-documents-service.c @@ -265,7 +265,7 @@ #include "gdata-batchable.h" #include "gdata-service.h" #include "gdata-private.h" -#include "gdata-upload-stream.h" +#include "gdata-uploader.h" GQuark gdata_documents_service_error_quark (void) diff --git a/gdata/services/documents/gdata-documents-service.h b/gdata/services/documents/gdata-documents-service.h index da173ad0..c16a623c 100644 --- a/gdata/services/documents/gdata-documents-service.h +++ b/gdata/services/documents/gdata-documents-service.h @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/gdata/services/documents/gdata-documents-upload-query.c b/gdata/services/documents/gdata-documents-upload-query.c index e88bf8cb..c242abcd 100644 --- a/gdata/services/documents/gdata-documents-upload-query.c +++ b/gdata/services/documents/gdata-documents-upload-query.c @@ -141,7 +141,7 @@ #include "gdata-documents-upload-query.h" #include "gdata-private.h" -#include "gdata-upload-stream.h" +#include "gdata-uploader.h" static void gdata_documents_upload_query_dispose (GObject *object); static void gdata_documents_upload_query_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); diff --git a/gdata/services/picasaweb/gdata-picasaweb-service.c b/gdata/services/picasaweb/gdata-picasaweb-service.c index 1a9dc8ce..f9d5715d 100644 --- a/gdata/services/picasaweb/gdata-picasaweb-service.c +++ b/gdata/services/picasaweb/gdata-picasaweb-service.c @@ -117,7 +117,7 @@ #include "gdata-private.h" #include "gdata-parser.h" #include "atom/gdata-link.h" -#include "gdata-upload-stream.h" +#include "gdata-uploader.h" #include "gdata-picasaweb-feed.h" static GList *get_authorization_domains (void); diff --git a/gdata/services/picasaweb/gdata-picasaweb-service.h b/gdata/services/picasaweb/gdata-picasaweb-service.h index f144edbf..c5f31082 100644 --- a/gdata/services/picasaweb/gdata-picasaweb-service.h +++ b/gdata/services/picasaweb/gdata-picasaweb-service.h @@ -25,7 +25,7 @@ #include #include -#include +#include #include #include diff --git a/gdata/services/youtube/gdata-youtube-service.c b/gdata/services/youtube/gdata-youtube-service.c index e5edc7b1..d4c08de4 100644 --- a/gdata/services/youtube/gdata-youtube-service.c +++ b/gdata/services/youtube/gdata-youtube-service.c @@ -256,7 +256,7 @@ #include "gdata-private.h" #include "gdata-parser.h" #include "atom/gdata-link.h" -#include "gdata-upload-stream.h" +#include "gdata-uploader.h" #include "gdata-youtube-category.h" #include "gdata-batchable.h" diff --git a/gdata/services/youtube/gdata-youtube-service.h b/gdata/services/youtube/gdata-youtube-service.h index 45403a95..dfedb753 100644 --- a/gdata/services/youtube/gdata-youtube-service.h +++ b/gdata/services/youtube/gdata-youtube-service.h @@ -25,7 +25,7 @@ #include #include -#include +#include #include #include diff --git a/gdata/symbol.map b/gdata/symbol.map index 8625c8f6..42ccfcb3 100644 --- a/gdata/symbol.map +++ b/gdata/symbol.map @@ -789,18 +789,22 @@ global: gdata_tasks_task_set_status; gdata_tasks_tasklist_get_type; gdata_tasks_tasklist_new; - gdata_upload_stream_get_authorization_domain; - gdata_upload_stream_get_cancellable; - gdata_upload_stream_get_content_length; - gdata_upload_stream_get_content_type; - gdata_upload_stream_get_entry; - gdata_upload_stream_get_method; - gdata_upload_stream_get_service; - gdata_upload_stream_get_slug; - gdata_upload_stream_get_type; - gdata_upload_stream_get_upload_uri; - gdata_upload_stream_new; - gdata_upload_stream_new_resumable; + gdata_uploader_get_authorization_domain; + gdata_uploader_get_content_length; + gdata_uploader_get_content_type; + gdata_uploader_get_entry; + gdata_uploader_get_method; + gdata_uploader_get_service; + gdata_uploader_get_slug; + gdata_uploader_get_type; + gdata_uploader_get_upload_uri; + gdata_uploader_new; + gdata_uploader_new_resumable; + gdata_uploader_send; + gdata_uploader_send_async; + gdata_uploader_send_finish; + gdata_uploader_set_input; + gdata_uploader_set_input_from_bytes; gdata_youtube_age_get_type; gdata_youtube_category_get_type; gdata_youtube_category_is_assignable; -- GitLab From 7554bfc9bc59c2ea66790e4ae68e42e40ce9a463 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:11 +0100 Subject: [PATCH 06/45] soup3: port gdata-service --- gdata/gdata-private.h | 15 +- gdata/gdata-service.c | 1186 ++++++++++++++++++++++++++++------------- gdata/gdata-service.h | 1 + 3 files changed, 830 insertions(+), 372 deletions(-) diff --git a/gdata/gdata-private.h b/gdata/gdata-private.h index 90b055c8..79fab4f5 100644 --- a/gdata/gdata-private.h +++ b/gdata/gdata-private.h @@ -52,16 +52,25 @@ typedef enum { G_GNUC_INTERNAL SoupSession *_gdata_service_get_session (GDataService *self) G_GNUC_PURE; G_GNUC_INTERNAL SoupMessage *_gdata_service_build_message (GDataService *self, GDataAuthorizationDomain *domain, const gchar *method, const gchar *uri, const gchar *etag, gboolean etag_if_match); -G_GNUC_INTERNAL void _gdata_service_actually_send_message (SoupSession *session, SoupMessage *message, GCancellable *cancellable, GError **error); -G_GNUC_INTERNAL guint _gdata_service_send_message (GDataService *self, SoupMessage *message, GCancellable *cancellable, GError **error); +G_GNUC_INTERNAL SoupMessage * _gdata_service_new_message (GDataService *self, const char *method, const char *uri_string); +G_GNUC_INTERNAL SoupMessage * _gdata_service_new_message_from_uri (GDataService *self, const char *method, GUri *uri); +G_GNUC_INTERNAL void _gdata_service_parse_soup_error (GDataService *self, GError **error); +G_GNUC_INTERNAL GBytes *_gdata_service_send (GDataService *self, SoupMessage *message, GCancellable *cancellable, GError **error); +G_GNUC_INTERNAL void _gdata_service_send_async (GDataService *self, SoupMessage *message, GCancellable *cancellable, + GAsyncReadyCallback callback, gpointer user_data); +G_GNUC_INTERNAL GBytes *_gdata_service_send_finish (GDataService *self, GAsyncResult *result, GError **error); G_GNUC_INTERNAL SoupMessage *_gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query, - GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; + GCancellable *cancellable, GBytes **body, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; +G_GNUC_INTERNAL void _gdata_service_query_async (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query, + GAsyncReadyCallback cb, GTask *task, SoupMessage **message); +G_GNUC_INTERNAL gboolean _gdata_service_query_async_check (GDataService *self, GTask *task, SoupMessage *message, GBytes *body, GError **error, GDestroyNotify destroy_cb, gpointer user_data); G_GNUC_INTERNAL const gchar *_gdata_service_get_scheme (void) G_GNUC_CONST; G_GNUC_INTERNAL gchar *_gdata_service_build_uri (const gchar *format, ...) G_GNUC_PRINTF (1, 2) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; G_GNUC_INTERNAL guint _gdata_service_get_https_port (void); G_GNUC_INTERNAL gchar *_gdata_service_fix_uri_scheme (const gchar *uri) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; G_GNUC_INTERNAL GDataLogLevel _gdata_service_get_log_level (void) G_GNUC_CONST; G_GNUC_INTERNAL SoupSession *_gdata_service_build_session (void) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; +G_GNUC_INTERNAL gboolean _gdata_service_accept_certificate_cb (SoupMessage *msg, GTlsCertificate *tls_cert, GTlsCertificateFlags tls_errors, gpointer session); typedef gchar *GDataSecureString; typedef const gchar *GDataConstSecureString; diff --git a/gdata/gdata-service.c b/gdata/gdata-service.c index 532f00fc..24785986 100644 --- a/gdata/gdata-service.c +++ b/gdata/gdata-service.c @@ -73,6 +73,7 @@ real_parse_feed (GDataService *self, GDataQuery *query, GType entry_type, SoupMessage *message, + GBytes *body, GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, @@ -81,10 +82,6 @@ static void notify_timeout_cb (GObject *gobject, GParamSpec *pspec, GObject *sel static void debug_handler (const char *log_domain, GLogLevelFlags log_level, const char *message, gpointer user_data); static void soup_log_printer (SoupLogger *logger, SoupLoggerLogLevel level, char direction, const char *data, gpointer user_data); -static GDataFeed *__gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query, - GType entry_type, GCancellable *cancellable, GDataQueryProgressCallback progress_callback, - gpointer progress_user_data, GError **error); - struct _GDataServicePrivate { SoupSession *session; gchar *locale; @@ -298,7 +295,7 @@ real_append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, if (domain != NULL) { /* Store the authorisation domain on the message so that we can access it again after refreshing authorisation if necessary. - * See _gdata_service_send_message(). */ + * See _gdata_service_send(). */ g_object_set_data_full (G_OBJECT (message), "gdata-authorization-domain", g_object_ref (domain), (GDestroyNotify) g_object_unref); } @@ -322,19 +319,6 @@ real_parse_error_response (GDataService *self, GDataOperationType operation_type /* See: http://code.google.com/apis/gdata/docs/2.0/reference.html#HTTPStatusCodes */ switch (status) { - case SOUP_STATUS_CANT_RESOLVE: - case SOUP_STATUS_CANT_CONNECT: - case SOUP_STATUS_SSL_FAILED: - case SOUP_STATUS_IO_ERROR: - g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NETWORK_ERROR, - _("Cannot connect to the service’s server.")); - return; - case SOUP_STATUS_CANT_RESOLVE_PROXY: - case SOUP_STATUS_CANT_CONNECT_PROXY: - g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROXY_ERROR, - _("Cannot connect to the proxy server.")); - return; - case SOUP_STATUS_MALFORMED: case SOUP_STATUS_BAD_REQUEST: /* 400 */ g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, /* Translators: the parameter is an error message returned by the server. */ @@ -419,6 +403,46 @@ real_parse_error_response (GDataService *self, GDataOperationType operation_type } } +void +_gdata_service_parse_soup_error (GDataService *self, GError **error) +{ + GError *inerr; + + if (!error || !*error) + return; + + inerr = *error; + *error = NULL; + + if (inerr->domain == SOUP_SESSION_ERROR) { + switch (inerr->code) { + case SOUP_SESSION_ERROR_PARSING: + case SOUP_SESSION_ERROR_ENCODING: { + real_parse_error_response (self, 0, SOUP_STATUS_BAD_REQUEST, + inerr->message, NULL, 0, error); + g_error_free (inerr); + return; + } + } + } + + if (inerr->domain == G_IO_ERROR) { + switch (inerr->code) { + case G_IO_ERROR_PROXY_FAILED: + case G_IO_ERROR_PROXY_NOT_ALLOWED: + case G_IO_ERROR_PROXY_NEED_AUTH: + g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROXY_ERROR, + _("Cannot connect to the proxy server.")); + g_error_free (inerr); + return; + } + } + + g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NETWORK_ERROR, + _("Cannot connect to the service’s server.")); + g_error_free (inerr); +} + /** * gdata_service_is_authorized: * @self: a #GDataService @@ -542,21 +566,62 @@ gdata_service_get_authorization_domains (GType service_type) return domains; } +gboolean +_gdata_service_accept_certificate_cb (SoupMessage *msg, + GTlsCertificate *tls_cert, + GTlsCertificateFlags tls_errors, + gpointer session) +{ + return !!g_object_get_data (session, "gdata-lax-ssl"); +} + +SoupMessage * +_gdata_service_new_message (GDataService *self, const char *method, + const char *uri_string) +{ + SoupMessage *ret; + + ret = soup_message_new (method, uri_string); + + g_signal_connect (ret, "accept-certificate", + G_CALLBACK (_gdata_service_accept_certificate_cb), + self->priv->session); + + return ret; +} + +SoupMessage * +_gdata_service_new_message_from_uri (GDataService *self, const char *method, + GUri *uri) +{ + SoupMessage *ret; + + ret = soup_message_new_from_uri (method, uri); + + g_signal_connect (ret, "accept-certificate", + G_CALLBACK (_gdata_service_accept_certificate_cb), + self->priv->session); + + return ret; +} + SoupMessage * _gdata_service_build_message (GDataService *self, GDataAuthorizationDomain *domain, const gchar *method, const gchar *uri, const gchar *etag, gboolean etag_if_match) { SoupMessage *message; GDataServiceClass *klass; - SoupURI *_uri; + GUri *_uri, *_uri_parsed; /* Create the message. Allow changing the HTTPS port just for testing, * but require that the URI is always HTTPS for privacy. */ - _uri = soup_uri_new (uri); - soup_uri_set_port (_uri, _gdata_service_get_https_port ()); - g_assert_cmpstr (soup_uri_get_scheme (_uri), ==, SOUP_URI_SCHEME_HTTPS); - message = soup_message_new_from_uri (method, _uri); - soup_uri_free (_uri); + _uri_parsed = g_uri_parse (uri, SOUP_HTTP_URI_FLAGS, NULL); + _uri = soup_uri_copy (_uri_parsed, SOUP_URI_PORT, + _gdata_service_get_https_port (), SOUP_URI_NONE); + g_uri_unref (_uri_parsed); + g_assert_cmpstr (g_uri_get_scheme (_uri), ==, "https"); + message = _gdata_service_new_message_from_uri (self, method, _uri); + g_uri_unref (_uri); /* Make sure subclasses set their headers */ klass = GDATA_SERVICE_GET_CLASS (self); @@ -570,167 +635,149 @@ _gdata_service_build_message (GDataService *self, GDataAuthorizationDomain *doma return message; } -typedef struct { - GMutex mutex; /* mutex to prevent cancellation before the message has been added to the session's message queue */ - SoupSession *session; - SoupMessage *message; -} MessageData; - -static void -message_cancel_cb (GCancellable *cancellable, MessageData *data) +GBytes * +_gdata_service_send (GDataService *self, SoupMessage *message, GCancellable *cancellable, GError **error) { - g_mutex_lock (&(data->mutex)); - soup_session_cancel_message (data->session, data->message, SOUP_STATUS_CANCELLED); - g_mutex_unlock (&(data->mutex)); -} + GBytes *ret; + guint status; -static void -message_request_queued_cb (SoupSession *session, SoupMessage *message, MessageData *data) -{ - if (message == data->message) { - g_mutex_unlock (&(data->mutex)); - } -} + soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT); + ret = soup_session_send_and_read (self->priv->session, message, cancellable, error); + soup_message_set_flags (message, 0); -/* Synchronously send @message via @service, handling asynchronous cancellation as best we can. If @cancellable has been cancelled before we start - * network activity, return without doing any network activity. Otherwise, if @cancellable is cancelled (from another thread) after network activity - * has started, we wait until the message has been queued by the session, then cancel the network activity and return as soon as possible. - * - * If cancellation has been handled, @error is guaranteed to be set to %G_IO_ERROR_CANCELLED. Otherwise, @error is guaranteed to be unset. */ -void -_gdata_service_actually_send_message (SoupSession *session, SoupMessage *message, GCancellable *cancellable, GError **error) -{ - MessageData data; - gulong cancel_signal = 0, request_queued_signal = 0; - guint status; + /* Handle redirections specially so we don't lose our custom headers when making the second request */ + if (SOUP_STATUS_IS_REDIRECTION (soup_message_get_status (message))) { + GUri *new_uri, *tmp_uri; + const gchar *new_location; - /* Hold references to the session and message so they can't be freed by other threads. For example, if the SoupSession was freed by another - * thread while we were making a request, the request would be unexpectedly cancelled. See bgo#650835 for an example of this breaking things. - */ - g_object_ref (session); - g_object_ref (message); - - /* Listen for cancellation */ - if (cancellable != NULL) { - g_mutex_init (&(data.mutex)); - data.session = session; - data.message = message; - - cancel_signal = g_cancellable_connect (cancellable, (GCallback) message_cancel_cb, &data, NULL); - request_queued_signal = g_signal_connect (session, "request-queued", (GCallback) message_request_queued_cb, &data); - - /* We lock this mutex until the message has been queued by the session (i.e. it's unlocked in the request-queued callback), and require - * the mutex to be held to cancel the message. Consequently, if the message is cancelled (in another thread) any time between this lock - * and the request being queued, the cancellation will wait until the request has been queued before taking effect. - * This is a little ugly, but is the only way I can think of to avoid a race condition between calling soup_session_cancel_message() - * and soup_session_send_message(), as the former doesn't have any effect until the request has been queued, and once the latter has - * returned, all network activity has been finished so cancellation is pointless. */ - g_mutex_lock (&(data.mutex)); - } + g_clear_pointer (&ret, g_bytes_unref); - /* Only send the message if it hasn't already been cancelled. There is no race condition here for the above reasons: if the cancellable has - * been cancelled, it's because it was cancelled before we called g_cancellable_connect(). - * - * Otherwise, manually set the message's status code to SOUP_STATUS_CANCELLED, as the message was cancelled before even being queued to be - * sent. */ - if (cancellable == NULL || g_cancellable_is_cancelled (cancellable) == FALSE) - soup_session_send_message (session, message); - else { - if (cancellable != NULL) { - g_mutex_unlock (&data.mutex); - } + new_location = soup_message_headers_get_one (soup_message_get_response_headers (message), "Location"); - soup_message_set_status (message, SOUP_STATUS_CANCELLED); - } + new_uri = g_uri_parse_relative (soup_message_get_uri (message), + new_location, SOUP_HTTP_URI_FLAGS, NULL); + if (new_uri == NULL) { + g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, + /* Translators: the parameter is the URI which is invalid. */ + _("Invalid redirect URI: %s"), new_location); + return NULL; + } - /* Clean up the cancellation code */ - if (cancellable != NULL) { - g_signal_handler_disconnect (session, request_queued_signal); + /* Allow overriding the URI for testing. */ + tmp_uri = soup_uri_copy (new_uri, SOUP_URI_PORT, + _gdata_service_get_https_port (), SOUP_URI_NONE); + g_uri_unref (new_uri); - if (cancel_signal != 0) - g_cancellable_disconnect (cancellable, cancel_signal); + soup_message_set_uri (message, tmp_uri); + g_uri_unref (tmp_uri); - g_mutex_clear (&(data.mutex)); + /* Send the message again */ + ret = soup_session_send_and_read (self->priv->session, message, cancellable, error); } - /* Set the cancellation error if applicable. We can't assume that our GCancellable has been cancelled just because the message has; - * libsoup may internally cancel messages if, for example, the proxy URI of the SoupSession is changed. - * libsoup also sometimes seems to return a SOUP_STATUS_IO_ERROR when we cancel a message, even though we've specified SOUP_STATUS_CANCELLED - * at cancellation time. Ho Hum. */ + if (!ret) + return NULL; + status = soup_message_get_status (message); - g_assert (status != SOUP_STATUS_NONE); - - if (status == SOUP_STATUS_CANCELLED || - ((status == SOUP_STATUS_IO_ERROR || status == SOUP_STATUS_SSL_FAILED || - status == SOUP_STATUS_CANT_CONNECT || status == SOUP_STATUS_CANT_RESOLVE) && - cancellable != NULL && g_cancellable_is_cancelled (cancellable) == TRUE)) { - /* We hackily create and cancel a new GCancellable so that we can set the error using it and therefore save ourselves a translatable - * string and the associated maintenance. */ - GCancellable *error_cancellable = g_cancellable_new (); - g_cancellable_cancel (error_cancellable); - g_assert (g_cancellable_set_error_if_cancelled (error_cancellable, error) == TRUE); - g_object_unref (error_cancellable); - - /* As per the above comment, force the status to be SOUP_STATUS_CANCELLED. */ - soup_message_set_status (message, SOUP_STATUS_CANCELLED); + + if (status == SOUP_STATUS_UNAUTHORIZED || + status == SOUP_STATUS_FORBIDDEN || + status == SOUP_STATUS_NOT_FOUND) { + GDataAuthorizer *authorizer = self->priv->authorizer; + + if (authorizer != NULL && gdata_authorizer_refresh_authorization (authorizer, cancellable, NULL) == TRUE) { + GDataAuthorizationDomain *domain; + + /* Re-process the request */ + domain = g_object_get_data (G_OBJECT (message), "gdata-authorization-domain"); + g_assert (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain)); + + gdata_authorizer_process_request (authorizer, domain, message); + + /* Send the message again */ + g_clear_error (error); + g_bytes_unref (ret); + ret = soup_session_send_and_read (self->priv->session, message, cancellable, error); + } } - /* Free things */ - g_object_unref (message); - g_object_unref (session); + return ret; } -guint -_gdata_service_send_message (GDataService *self, SoupMessage *message, GCancellable *cancellable, GError **error) +typedef struct { + GDataService *self; + gboolean second_try; +} MessageData; + +static void task_data_free (MessageData *data) { - /* Based on code from evolution-data-server's libgdata: - * Ebby Wiselyn - * Jason Willis - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - */ + g_slice_free (MessageData, data); +} + +static void +msg_send_callback (GObject *object, GAsyncResult *result, gpointer user_data) +{ + GTask *task = user_data; + SoupSession *session = SOUP_SESSION (object); + SoupMessage *message = soup_session_get_async_result_message (session, result); + MessageData *data = g_task_get_task_data (task); + GCancellable *cancellable; + GBytes *ret; + GError *error = NULL; + guint status; + + ret = soup_session_send_and_read_finish (session, result, &error); - soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT); - _gdata_service_actually_send_message (self->priv->session, message, cancellable, error); soup_message_set_flags (message, 0); + cancellable = g_task_get_cancellable (task); + /* Handle redirections specially so we don't lose our custom headers when making the second request */ if (SOUP_STATUS_IS_REDIRECTION (soup_message_get_status (message))) { - SoupURI *new_uri; + GUri *new_uri, *tmp_uri; const gchar *new_location; + g_clear_pointer (&ret, g_bytes_unref); + new_location = soup_message_headers_get_one (soup_message_get_response_headers (message), "Location"); - g_return_val_if_fail (new_location != NULL, SOUP_STATUS_NONE); - new_uri = soup_uri_new_with_base (soup_message_get_uri (message), new_location); + new_uri = g_uri_parse_relative (soup_message_get_uri (message), + new_location, SOUP_HTTP_URI_FLAGS, NULL); if (new_uri == NULL) { - gchar *uri_string = soup_uri_to_string (new_uri, FALSE); - g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, - /* Translators: the parameter is the URI which is invalid. */ - _("Invalid redirect URI: %s"), uri_string); - g_free (uri_string); - return SOUP_STATUS_NONE; + error = g_error_new (GDATA_SERVICE_ERROR, + GDATA_SERVICE_ERROR_PROTOCOL_ERROR, + /* Translators: the parameter is the URI which is invalid. */ + _("Invalid redirect URI: %s"), new_location); + goto done; } /* Allow overriding the URI for testing. */ - soup_uri_set_port (new_uri, _gdata_service_get_https_port ()); + tmp_uri = soup_uri_copy (new_uri, SOUP_URI_PORT, + _gdata_service_get_https_port (), SOUP_URI_NONE); + g_uri_unref (new_uri); - soup_message_set_uri (message, new_uri); - soup_uri_free (new_uri); + soup_message_set_uri (message, tmp_uri); + g_uri_unref (tmp_uri); /* Send the message again */ - _gdata_service_actually_send_message (self->priv->session, message, cancellable, error); + soup_session_send_and_read_async (session, message, + g_task_get_priority (task), + cancellable, + msg_send_callback, task); + /* we're done here, the next send will handle things */ + return; } - /* Not authorised, or authorisation has expired. If we were authorised in the first place, attempt to refresh the authorisation and - * try sending the message again (but only once, so we don't get caught in an infinite loop of denied authorisation errors). - * - * Note that we have to re-process the message with the authoriser so that its authorisation headers get updated after the refresh - * (bgo#653535). */ - if (soup_message_get_status (message) == SOUP_STATUS_UNAUTHORIZED || - soup_message_get_status (message) == SOUP_STATUS_FORBIDDEN || - soup_message_get_status (message) == SOUP_STATUS_NOT_FOUND) { - GDataAuthorizer *authorizer = self->priv->authorizer; + if (!ret) { + goto done; + } + + status = soup_message_get_status (message); + + if ((status == SOUP_STATUS_UNAUTHORIZED || + status == SOUP_STATUS_FORBIDDEN || + status == SOUP_STATUS_NOT_FOUND) && !data->second_try) { + GDataAuthorizer *authorizer = data->self->priv->authorizer; if (authorizer != NULL && gdata_authorizer_refresh_authorization (authorizer, cancellable, NULL) == TRUE) { GDataAuthorizationDomain *domain; @@ -742,19 +789,60 @@ _gdata_service_send_message (GDataService *self, SoupMessage *message, GCancella gdata_authorizer_process_request (authorizer, domain, message); /* Send the message again */ - g_clear_error (error); - _gdata_service_actually_send_message (self->priv->session, message, cancellable, error); + g_clear_error (&error); + g_bytes_unref (ret); + data->second_try = TRUE; + soup_session_send_and_read_async (data->self->priv->session, message, + g_task_get_priority (task), + cancellable, + msg_send_callback, g_object_ref (task)); } } - return soup_message_get_status (message); +done: + if (error) { + g_task_return_error (task, error); + } else { + g_task_return_pointer (task, ret, (GDestroyNotify)g_bytes_unref); + } + + g_clear_object (&task); +} + +void +_gdata_service_send_async (GDataService *self, SoupMessage *message, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) +{ + GTask *task; + MessageData *data = g_slice_new (MessageData); + + data->self = self; + data->second_try = FALSE; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, data, (GDestroyNotify)task_data_free); + + soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT); + + soup_session_send_and_read_async (self->priv->session, message, + g_task_get_priority (task), + g_task_get_cancellable (task), + msg_send_callback, task); +} + +GBytes *_gdata_service_send_finish (GDataService *self, GAsyncResult *result, GError **error) +{ + GTask *task = G_TASK (result); + + return g_task_propagate_pointer (task, error); } typedef struct { + SoupMessage *message; + /* Input */ GDataAuthorizationDomain *domain; - gchar *feed_uri; GDataQuery *query; + gchar *entry_id; GType entry_type; /* Output */ @@ -766,35 +854,122 @@ typedef struct { static void query_async_data_free (QueryAsyncData *self) { - if (self->domain != NULL) - g_object_unref (self->domain); - - g_free (self->feed_uri); - if (self->query) - g_object_unref (self->query); - + g_clear_object (&self->domain); + g_clear_object (&self->message); + g_clear_object (&self->query); + g_free (self->entry_id); g_slice_free (QueryAsyncData, self); } static void -query_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) -{ - GDataService *service = GDATA_SERVICE (source_object); - g_autoptr(GError) error = NULL; - QueryAsyncData *data = task_data; - g_autoptr(GDataFeed) feed = NULL; - - /* Execute the query and return */ - feed = __gdata_service_query (service, data->domain, data->feed_uri, data->query, data->entry_type, cancellable, - data->progress_callback, data->progress_user_data, &error); - if (feed == NULL && error != NULL) - g_task_return_error (task, g_steal_pointer (&error)); - else - g_task_return_pointer (task, g_steal_pointer (&feed), g_object_unref); +query_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) +{ + GDataService *self = GDATA_SERVICE (object); + GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self); + GDataFeed *feed; + GTask *task = user_data; + QueryAsyncData *data = g_task_get_task_data (task); + SoupMessage *message = data->message; + GError *error = NULL; + GBytes *body; + + body = _gdata_service_send_finish (self, result, &error); + + if (!_gdata_service_query_async_check (self, task, message, body, &error, data->destroy_progress_user_data, data->progress_user_data)) + return; + + g_assert (klass->parse_feed != NULL); + + /* Parse the response. */ + feed = klass->parse_feed (self, data->domain, data->query, data->entry_type, + message, body, g_task_get_cancellable (task), + data->progress_callback, + data->progress_user_data, &error); + + g_bytes_unref (body); if (data->destroy_progress_user_data != NULL) { data->destroy_progress_user_data (data->progress_user_data); } + + if (error) + g_task_return_error (task, error); + else + g_task_return_pointer (task, feed, g_object_unref); + + g_object_unref (task); +} + +void +_gdata_service_query_async (GDataService *self, + GDataAuthorizationDomain *domain, + const gchar *feed_uri, + GDataQuery *query, + GAsyncReadyCallback cb, + GTask *task, + SoupMessage **message) +{ + const gchar *etag = NULL; + + /* Append the ETag header if possible */ + if (query != NULL) + etag = gdata_query_get_etag (query); + + /* Build the message */ + if (query != NULL) { + gchar *query_uri = gdata_query_get_query_uri (query, feed_uri); + *message = _gdata_service_build_message (self, domain, SOUP_METHOD_GET, query_uri, etag, FALSE); + g_free (query_uri); + } else { + *message = _gdata_service_build_message (self, domain, SOUP_METHOD_GET, feed_uri, etag, FALSE); + } + + if (!g_task_get_task_data (task)) + g_task_set_task_data (task, *message, g_object_unref); + + /* Note that cancellation only applies to network activity; not to the processing done afterwards */ + _gdata_service_send_async (self, *message, g_task_get_cancellable (task), cb, task); +} + +gboolean +_gdata_service_query_async_check (GDataService *self, + GTask *task, + SoupMessage *message, + GBytes *body, + GError **error, + GDestroyNotify destroy_cb, + gpointer user_data) +{ + guint status = body ? soup_message_get_status (message) : 0; + + if (!body || status == SOUP_STATUS_NOT_MODIFIED) { + _gdata_service_parse_soup_error (self, error); + /* Not modified (ETag has worked), or cancelled (in which case the error has been set) */ + g_clear_pointer (&body, g_bytes_unref); + if (destroy_cb) + destroy_cb (user_data); + if (*error) + g_task_return_error (task, *error); + else + g_task_return_pointer (task, NULL, NULL); + g_object_unref (task); + return FALSE; + } else if (status != SOUP_STATUS_OK) { + /* Error */ + GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self); + g_assert (klass->parse_error_response != NULL); + klass->parse_error_response (self, GDATA_OPERATION_QUERY, status, + soup_message_get_reason_phrase (message), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), error); + g_bytes_unref (body); + if (destroy_cb) + destroy_cb (user_data); + g_task_return_error (task, *error); + g_object_unref (task); + return FALSE; + } + return TRUE; } /** @@ -827,8 +1002,9 @@ gdata_service_query_async (GDataService *self, GDataAuthorizationDomain *domain, GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GDestroyNotify destroy_progress_user_data, GAsyncReadyCallback callback, gpointer user_data) { - g_autoptr(GTask) task = NULL; + GTask *task = NULL; QueryAsyncData *data; + GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self); g_return_if_fail (GDATA_IS_SERVICE (self)); g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain)); @@ -839,8 +1015,8 @@ gdata_service_query_async (GDataService *self, GDataAuthorizationDomain *domain, data = g_slice_new (QueryAsyncData); data->domain = (domain != NULL) ? g_object_ref (domain) : NULL; - data->feed_uri = g_strdup (feed_uri); data->query = (query != NULL) ? g_object_ref (query) : NULL; + data->entry_id = NULL; data->entry_type = entry_type; data->progress_callback = progress_callback; data->progress_user_data = progress_user_data; @@ -848,8 +1024,26 @@ gdata_service_query_async (GDataService *self, GDataAuthorizationDomain *domain, task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, gdata_service_query_async); - g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) query_async_data_free); - g_task_run_in_thread (task, query_thread); + g_task_set_task_data (task, data, (GDestroyNotify) query_async_data_free); + + /* Are we off the end of the final page? */ + if (data->query != NULL && _gdata_query_is_finished (data->query)) { + /* Build an empty dummy feed to signify the end of the list. */ + GDataFeed *feed = _gdata_feed_new (klass->feed_type, "Empty feed", + "feed1", g_get_real_time () / G_USEC_PER_SEC); + + if (data->destroy_progress_user_data != NULL) { + data->destroy_progress_user_data (data->progress_user_data); + } + + g_task_return_pointer (task, feed, g_object_unref); + + g_object_unref (task); + return; + } + + _gdata_service_query_async (self, domain, feed_uri, query, + query_async_cb, task, &data->message); } /** @@ -878,9 +1072,10 @@ gdata_service_query_finish (GDataService *self, GAsyncResult *async_result, GErr * gdata_service_query_single_entry()) only return a single entry, and thus need special parsing code. */ SoupMessage * _gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query, - GCancellable *cancellable, GError **error) + GCancellable *cancellable, GBytes **body, GError **error) { SoupMessage *message; + GBytes *ret; guint status; const gchar *etag = NULL; @@ -898,11 +1093,14 @@ _gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, cons } /* Note that cancellation only applies to network activity; not to the processing done afterwards */ - status = _gdata_service_send_message (self, message, cancellable, error); + ret = _gdata_service_send (self, message, cancellable, error); + status = soup_message_get_status (message); - if (status == SOUP_STATUS_NOT_MODIFIED || status == SOUP_STATUS_CANCELLED) { + if (!ret || status == SOUP_STATUS_NOT_MODIFIED) { + _gdata_service_parse_soup_error (self, error); /* Not modified (ETag has worked), or cancelled (in which case the error has been set) */ g_object_unref (message); + g_clear_pointer (&ret, g_bytes_unref); return NULL; } else if (status != SOUP_STATUS_OK) { /* Error */ @@ -910,56 +1108,24 @@ _gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, cons g_assert (klass->parse_error_response != NULL); klass->parse_error_response (self, GDATA_OPERATION_QUERY, status, soup_message_get_reason_phrase (message), - message->response_body->data, - message->response_body->length, error); + g_bytes_get_data (ret, NULL), + g_bytes_get_size (ret), error); + g_bytes_unref (ret); g_object_unref (message); return NULL; } + *body = ret; return message; } -static GDataFeed * -__gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query, GType entry_type, - GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error) -{ - GDataServiceClass *klass; - SoupMessage *message; - GDataFeed *feed; - - klass = GDATA_SERVICE_GET_CLASS (self); - - /* Are we off the end of the final page? */ - if (query != NULL && _gdata_query_is_finished (query)) { - /* Build an empty dummy feed to signify the end of the list. */ - return _gdata_feed_new (klass->feed_type, "Empty feed", "feed1", - g_get_real_time () / G_USEC_PER_SEC); - } - - /* Send the request. */ - message = _gdata_service_query (self, domain, feed_uri, query, cancellable, error); - if (message == NULL) - return NULL; - - g_assert (message->response_body->data != NULL); - g_assert (klass->parse_feed != NULL); - - /* Parse the response. */ - feed = klass->parse_feed (self, domain, query, entry_type, - message, cancellable, progress_callback, - progress_user_data, error); - - g_object_unref (message); - - return feed; -} - static GDataFeed * real_parse_feed (GDataService *self, GDataAuthorizationDomain *domain, GDataQuery *query, GType entry_type, SoupMessage *message, + GBytes *body, GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, @@ -977,13 +1143,13 @@ real_parse_feed (GDataService *self, if (content_type != NULL && strcmp (content_type, "application/json") == 0) { /* Definitely JSON. */ g_debug("JSON content type detected."); - feed = _gdata_feed_new_from_json (klass->feed_type, message->response_body->data, message->response_body->length, entry_type, + feed = _gdata_feed_new_from_json (klass->feed_type, g_bytes_get_data (body, NULL), g_bytes_get_size (body), entry_type, progress_callback, progress_user_data, error); } else { /* Potentially XML. Don't bother checking the Content-Type, since the parser * will fail gracefully if the response body is not valid XML. */ g_debug("XML content type detected."); - feed = _gdata_feed_new_from_xml (klass->feed_type, message->response_body->data, message->response_body->length, entry_type, + feed = _gdata_feed_new_from_xml (klass->feed_type, g_bytes_get_data (body, NULL), g_bytes_get_size (body), entry_type, progress_callback, progress_user_data, error); } @@ -1056,6 +1222,11 @@ GDataFeed * gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query, GType entry_type, GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error) { + GDataServiceClass *klass; + SoupMessage *message; + GDataFeed *feed; + GBytes *body = NULL; + g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL); g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL); g_return_val_if_fail (feed_uri != NULL, NULL); @@ -1063,7 +1234,32 @@ gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - return __gdata_service_query (self, domain, feed_uri, query, entry_type, cancellable, progress_callback, progress_user_data, error); + klass = GDATA_SERVICE_GET_CLASS (self); + + /* Are we off the end of the final page? */ + if (query != NULL && _gdata_query_is_finished (query)) { + /* Build an empty dummy feed to signify the end of the list. */ + return _gdata_feed_new (klass->feed_type, "Empty feed", "feed1", + g_get_real_time () / G_USEC_PER_SEC); + } + + /* Send the request. */ + message = _gdata_service_query (self, domain, feed_uri, query, cancellable, &body, error); + if (message == NULL) + return NULL; + + g_assert (body != NULL); + g_assert (klass->parse_feed != NULL); + + /* Parse the response. */ + feed = klass->parse_feed (self, domain, query, entry_type, + message, body, cancellable, progress_callback, + progress_user_data, error); + + g_bytes_unref (body); + g_object_unref (message); + + return feed; } /** @@ -1098,6 +1294,7 @@ gdata_service_query_single_entry (GDataService *self, GDataAuthorizationDomain * gchar *entry_uri; SoupMessage *message; SoupMessageHeaders *headers; + GBytes *body = NULL; const gchar *content_type; g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL); @@ -1113,7 +1310,7 @@ gdata_service_query_single_entry (GDataService *self, GDataAuthorizationDomain * g_assert (klass->get_entry_uri != NULL); entry_uri = klass->get_entry_uri (entry_id); - message = _gdata_service_query (GDATA_SERVICE (self), domain, entry_uri, query, cancellable, error); + message = _gdata_service_query (GDATA_SERVICE (self), domain, entry_uri, query, cancellable, &body, error); g_free (entry_uri); if (message == NULL) { @@ -1121,56 +1318,66 @@ gdata_service_query_single_entry (GDataService *self, GDataAuthorizationDomain * return NULL; } - g_assert (message->response_body->data != NULL); + g_assert (body && g_bytes_get_data (body, NULL)); headers = soup_message_get_response_headers (message); content_type = soup_message_headers_get_content_type (headers, NULL); if (g_strcmp0 (content_type, "application/json") == 0) { - entry = GDATA_ENTRY (gdata_parsable_new_from_json (entry_type, message->response_body->data, message->response_body->length, error)); + entry = GDATA_ENTRY (gdata_parsable_new_from_json (entry_type, g_bytes_get_data (body, NULL), g_bytes_get_size (body), error)); } else { - entry = GDATA_ENTRY (gdata_parsable_new_from_xml (entry_type, message->response_body->data, message->response_body->length, error)); + entry = GDATA_ENTRY (gdata_parsable_new_from_xml (entry_type, g_bytes_get_data (body, NULL), g_bytes_get_size (body), error)); } + g_bytes_unref (body); g_object_unref (message); g_type_class_unref (klass); return entry; } -typedef struct { - GDataAuthorizationDomain *domain; - gchar *entry_id; - GDataQuery *query; - GType entry_type; -} QuerySingleEntryAsyncData; - static void -query_single_entry_async_data_free (QuerySingleEntryAsyncData *data) +query_single_entry_async_cb (GObject *object, GAsyncResult *result, + gpointer user_data) { - if (data->domain != NULL) - g_object_unref (data->domain); + GDataService *self = GDATA_SERVICE (object); + GTask *task = user_data; + QueryAsyncData *data = g_task_get_task_data (task); + SoupMessage *message = data->message; + SoupMessageHeaders *headers; + GError *error = NULL; + GBytes *body; + GDataEntry *entry; + const gchar *content_type; - g_free (data->entry_id); - if (data->query != NULL) - g_object_unref (data->query); - g_slice_free (QuerySingleEntryAsyncData, data); -} + body = _gdata_service_send_finish (self, result, &error); -static void -query_single_entry_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) -{ - GDataService *service = GDATA_SERVICE (source_object); - g_autoptr(GDataEntry) entry = NULL; - g_autoptr(GError) error = NULL; - QuerySingleEntryAsyncData *data = task_data; + if (!_gdata_service_query_async_check (self, task, message, body, &error, NULL, NULL)) + return; - /* Execute the query and return */ - entry = gdata_service_query_single_entry (service, data->domain, data->entry_id, data->query, data->entry_type, cancellable, &error); - if (entry == NULL && error != NULL) - g_task_return_error (task, g_steal_pointer (&error)); + headers = soup_message_get_response_headers (message); + content_type = soup_message_headers_get_content_type (headers, NULL); + + if (g_strcmp0 (content_type, "application/json") == 0) { + entry = GDATA_ENTRY (gdata_parsable_new_from_json (data->entry_type, + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), + &error)); + } else { + entry = GDATA_ENTRY (gdata_parsable_new_from_xml (data->entry_type, + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), + &error)); + } + + g_bytes_unref (body); + + if (error) + g_task_return_error (task, error); else - g_task_return_pointer (task, g_steal_pointer (&entry), g_object_unref); + g_task_return_pointer (task, entry, g_object_unref); + + g_object_unref (task); } /** @@ -1199,8 +1406,9 @@ void gdata_service_query_single_entry_async (GDataService *self, GDataAuthorizationDomain *domain, const gchar *entry_id, GDataQuery *query, GType entry_type, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { - g_autoptr(GTask) task = NULL; - QuerySingleEntryAsyncData *data; + GTask *task; + QueryAsyncData *data; + GDataEntryClass *klass; g_return_if_fail (GDATA_IS_SERVICE (self)); g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain)); @@ -1210,16 +1418,28 @@ gdata_service_query_single_entry_async (GDataService *self, GDataAuthorizationDo g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (callback != NULL); - data = g_slice_new (QuerySingleEntryAsyncData); + data = g_slice_new (QueryAsyncData); data->domain = (domain != NULL) ? g_object_ref (domain) : NULL; data->query = (query != NULL) ? g_object_ref (query) : NULL; data->entry_id = g_strdup (entry_id); data->entry_type = entry_type; + data->progress_callback = NULL; + data->progress_user_data = NULL; + data->destroy_progress_user_data = NULL; task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, gdata_service_query_single_entry_async); - g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) query_single_entry_async_data_free); - g_task_run_in_thread (task, query_single_entry_thread); + g_task_set_task_data (task, data, (GDestroyNotify) query_async_data_free); + + klass = GDATA_ENTRY_CLASS (g_type_class_ref (data->entry_type)); + g_assert (klass->get_entry_uri != NULL); + + _gdata_service_query_async (self, domain, + klass->get_entry_uri (data->entry_id), + query, query_single_entry_async_cb, + task, &data->message); + + g_type_class_unref (klass); } /** @@ -1246,39 +1466,94 @@ gdata_service_query_single_entry_finish (GDataService *self, GAsyncResult *async return g_task_propagate_pointer (G_TASK (async_result), error); } +static void +entry_message_starting (SoupMessage *msg, gpointer body) +{ + soup_message_set_request_body_from_bytes (msg, NULL, body); +} + +static void +entry_message_finished (SoupMessage *msg, gpointer body) +{ + g_bytes_unref (body); +} + typedef struct { + SoupMessage *message; GDataAuthorizationDomain *domain; - gchar *upload_uri; GDataEntry *entry; } InsertEntryAsyncData; static void insert_entry_async_data_free (InsertEntryAsyncData *self) { - if (self->domain != NULL) - g_object_unref (self->domain); - - g_free (self->upload_uri); - if (self->entry) - g_object_unref (self->entry); - + g_clear_object (&self->domain); + g_clear_object (&self->entry); + g_clear_object (&self->message); g_slice_free (InsertEntryAsyncData, self); } -static void -insert_entry_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) +static void insert_entry_async_cb (GObject *object, GAsyncResult *result, + gpointer user_data) { - GDataService *service = GDATA_SERVICE (source_object); - g_autoptr(GDataEntry) updated_entry = NULL; - g_autoptr(GError) error = NULL; - InsertEntryAsyncData *data = task_data; + GDataService *self = GDATA_SERVICE (object); + GTask *task = user_data; + InsertEntryAsyncData *data = g_task_get_task_data (task); + SoupMessage *message = data->message; + GDataParsableClass *klass; + guint status; + GBytes *body; + GError *error = NULL; + GDataEntry *entry = data->entry; + GDataEntry *updated_entry; - /* Insert the entry and return */ - updated_entry = gdata_service_insert_entry (service, data->domain, data->upload_uri, data->entry, cancellable, &error); - if (updated_entry == NULL) - g_task_return_error (task, g_steal_pointer (&error)); + body = _gdata_service_send_finish (self, result, &error); + status = soup_message_get_status (message); + + if (!body || status == SOUP_STATUS_NONE) { + /* Redirect error or cancelled */ + _gdata_service_parse_soup_error (self, &error); + g_clear_pointer (&body, g_bytes_unref); + if (error) + g_task_return_error (task, error); + else + g_task_return_pointer (task, NULL, NULL); + g_object_unref (task); + return; + } else if (status != SOUP_STATUS_CREATED && status != SOUP_STATUS_OK) { + /* Error: for XML APIs Google returns CREATED and for JSON it returns OK. */ + GDataServiceClass *service_klass = GDATA_SERVICE_GET_CLASS (self); + g_assert (service_klass->parse_error_response != NULL); + service_klass->parse_error_response (self, GDATA_OPERATION_INSERTION, status, + soup_message_get_reason_phrase (message), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), &error); + g_bytes_unref (body); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + klass = GDATA_PARSABLE_GET_CLASS (data->entry); + + /* Parse the XML or JSON according to GDataEntry type; create and return a new GDataEntry of the same type as @entry */ + if (g_strcmp0 (klass->get_content_type (), "application/json") == 0) { + updated_entry = GDATA_ENTRY (gdata_parsable_new_from_json (G_OBJECT_TYPE (entry), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), &error)); + } else { + updated_entry = GDATA_ENTRY (gdata_parsable_new_from_xml (G_OBJECT_TYPE (entry), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), &error)); + } + g_bytes_unref (body); + + if (error) + g_task_return_error (task, error); else - g_task_return_pointer (task, g_steal_pointer (&updated_entry), g_object_unref); + g_task_return_pointer (task, updated_entry, g_object_unref); + + g_object_unref (task); } /** @@ -1305,8 +1580,11 @@ void gdata_service_insert_entry_async (GDataService *self, GDataAuthorizationDomain *domain, const gchar *upload_uri, GDataEntry *entry, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { - g_autoptr(GTask) task = NULL; + GTask *task = NULL; InsertEntryAsyncData *data; + GDataParsableClass *klass; + gchar *upload_data; + GBytes *body; g_return_if_fail (GDATA_IS_SERVICE (self)); g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain)); @@ -1316,13 +1594,40 @@ gdata_service_insert_entry_async (GDataService *self, GDataAuthorizationDomain * data = g_slice_new (InsertEntryAsyncData); data->domain = (domain != NULL) ? g_object_ref (domain) : NULL; - data->upload_uri = g_strdup (upload_uri); data->entry = g_object_ref (entry); task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, gdata_service_insert_entry_async); - g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) insert_entry_async_data_free); - g_task_run_in_thread (task, insert_entry_thread); + g_task_set_task_data (task, data, (GDestroyNotify) insert_entry_async_data_free); + + if (gdata_entry_is_inserted (entry) == TRUE) { + g_task_return_error (task, g_error_new (GDATA_SERVICE_ERROR, + GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED, + _("The entry has already been inserted."))); + g_object_unref (task); + return; + } + + data->message = _gdata_service_build_message (self, domain, + SOUP_METHOD_POST, upload_uri, + NULL, FALSE); + + klass = GDATA_PARSABLE_GET_CLASS (entry); + g_assert (klass->get_content_type != NULL); + if (g_strcmp0 (klass->get_content_type (), "application/json") == 0) { + soup_message_headers_set_content_type (soup_message_get_request_headers (data->message), "application/json", NULL); + } else { + soup_message_headers_set_content_type (soup_message_get_request_headers (data->message), "application/atom+xml", NULL); + } + upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (entry)); + body = g_bytes_new_take (upload_data, strlen (upload_data)); + + g_signal_connect (data->message, "starting", G_CALLBACK (entry_message_starting), body); + g_signal_connect (data->message, "finished", G_CALLBACK (entry_message_finished), body); + + /* Send the message */ + _gdata_service_send_async (self, data->message, g_task_get_cancellable (task), + insert_entry_async_cb, task); } /** @@ -1389,6 +1694,7 @@ gdata_service_insert_entry (GDataService *self, GDataAuthorizationDomain *domain gchar *upload_data; guint status; GDataParsableClass *klass; + GBytes *body; g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL); g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL); @@ -1409,18 +1715,24 @@ gdata_service_insert_entry (GDataService *self, GDataAuthorizationDomain *domain klass = GDATA_PARSABLE_GET_CLASS (entry); g_assert (klass->get_content_type != NULL); if (g_strcmp0 (klass->get_content_type (), "application/json") == 0) { - upload_data = gdata_parsable_get_json (GDATA_PARSABLE (entry)); - soup_message_set_request (message, "application/json", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data)); + soup_message_headers_set_content_type (soup_message_get_request_headers (message), "application/json", NULL); } else { - upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (entry)); - soup_message_set_request (message, "application/atom+xml", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data)); + soup_message_headers_set_content_type (soup_message_get_request_headers (message), "application/atom+xml", NULL); } + upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (entry)); + body = g_bytes_new_take (upload_data, strlen (upload_data)); + + g_signal_connect (message, "starting", G_CALLBACK (entry_message_starting), body); + g_signal_connect (message, "finished", G_CALLBACK (entry_message_finished), body); /* Send the message */ - status = _gdata_service_send_message (self, message, cancellable, error); + body = _gdata_service_send (self, message, cancellable, error); + status = soup_message_get_status (message); - if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) { + if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ + _gdata_service_parse_soup_error (self, error); + g_clear_pointer (&body, g_bytes_unref); g_object_unref (message); return NULL; } else if (status != SOUP_STATUS_CREATED && status != SOUP_STATUS_OK) { @@ -1429,27 +1741,31 @@ gdata_service_insert_entry (GDataService *self, GDataAuthorizationDomain *domain g_assert (service_klass->parse_error_response != NULL); service_klass->parse_error_response (self, GDATA_OPERATION_INSERTION, status, soup_message_get_reason_phrase (message), - message->response_body->data, - message->response_body->length, error); + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), error); + g_bytes_unref (body); g_object_unref (message); return NULL; } /* Parse the XML or JSON according to GDataEntry type; create and return a new GDataEntry of the same type as @entry */ - g_assert (message->response_body->data != NULL); if (g_strcmp0 (klass->get_content_type (), "application/json") == 0) { - updated_entry = GDATA_ENTRY (gdata_parsable_new_from_json (G_OBJECT_TYPE (entry), message->response_body->data, - message->response_body->length, error)); + updated_entry = GDATA_ENTRY (gdata_parsable_new_from_json (G_OBJECT_TYPE (entry), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), error)); } else { - updated_entry = GDATA_ENTRY (gdata_parsable_new_from_xml (G_OBJECT_TYPE (entry), message->response_body->data, - message->response_body->length, error)); + updated_entry = GDATA_ENTRY (gdata_parsable_new_from_xml (G_OBJECT_TYPE (entry), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), error)); } + g_bytes_unref (body); g_object_unref (message); return updated_entry; } typedef struct { + SoupMessage *message; GDataAuthorizationDomain *domain; GDataEntry *entry; } UpdateEntryAsyncData; @@ -1457,29 +1773,71 @@ typedef struct { static void update_entry_async_data_free (UpdateEntryAsyncData *data) { - if (data->domain != NULL) - g_object_unref (data->domain); - - if (data->entry != NULL) - g_object_unref (data->entry); - + g_clear_object (&data->domain); + g_clear_object (&data->entry); + g_clear_object (&data->message); g_slice_free (UpdateEntryAsyncData, data); } -static void -update_entry_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) +static void update_entry_async_cb (GObject *object, GAsyncResult *result, + gpointer user_data) { - GDataService *service = GDATA_SERVICE (source_object); - g_autoptr(GDataEntry) updated_entry = NULL; - g_autoptr(GError) error = NULL; - UpdateEntryAsyncData *data = task_data; + GDataService *self = GDATA_SERVICE (object); + GTask *task = user_data; + InsertEntryAsyncData *data = g_task_get_task_data (task); + SoupMessage *message = data->message; + GDataParsableClass *klass; + guint status; + GBytes *body; + GError *error = NULL; + GDataEntry *entry = data->entry; + GDataEntry *updated_entry; + + body = _gdata_service_send_finish (self, result, &error); + status = soup_message_get_status (message); - /* Update the entry and return */ - updated_entry = gdata_service_update_entry (service, data->domain, data->entry, cancellable, &error); - if (updated_entry == NULL) - g_task_return_error (task, g_steal_pointer (&error)); + if (!body || status == SOUP_STATUS_NONE) { + /* Redirect error or cancelled */ + _gdata_service_parse_soup_error (self, &error); + g_clear_pointer (&body, g_bytes_unref); + if (error) + g_task_return_error (task, error); + else + g_task_return_pointer (task, NULL, NULL); + g_object_unref (task); + return; + } else if (status != SOUP_STATUS_OK) { + /* Error */ + GDataServiceClass *service_klass = GDATA_SERVICE_GET_CLASS (self); + g_assert (service_klass->parse_error_response != NULL); + service_klass->parse_error_response (self, GDATA_OPERATION_UPDATE, status, + soup_message_get_reason_phrase (message), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), &error); + g_bytes_unref (body); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + klass = GDATA_PARSABLE_GET_CLASS (data->entry); + + /* Parse the XML; create and return a new GDataEntry of the same type as @entry */ + if (g_strcmp0 (klass->get_content_type (), "application/json") == 0) { + updated_entry = GDATA_ENTRY (gdata_parsable_new_from_json (G_OBJECT_TYPE (entry), + g_bytes_get_data (body, NULL), g_bytes_get_size (body), &error)); + } else { + updated_entry = GDATA_ENTRY (gdata_parsable_new_from_xml (G_OBJECT_TYPE (entry), + g_bytes_get_data (body, NULL), g_bytes_get_size (body), &error)); + } + g_bytes_unref (body); + + if (error) + g_task_return_error (task, error); else - g_task_return_pointer (task, g_steal_pointer (&updated_entry), g_object_unref); + g_task_return_pointer (task, updated_entry, g_object_unref); + + g_object_unref (task); } /** @@ -1505,8 +1863,12 @@ void gdata_service_update_entry_async (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { - g_autoptr(GTask) task = NULL; + GTask *task = NULL; UpdateEntryAsyncData *data; + GDataParsableClass *klass; + GDataLink *_link; + gchar *upload_data; + GBytes *body; g_return_if_fail (GDATA_IS_SERVICE (self)); g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain)); @@ -1519,8 +1881,33 @@ gdata_service_update_entry_async (GDataService *self, GDataAuthorizationDomain * task = g_task_new (task, cancellable, callback, user_data); g_task_set_source_tag (task, gdata_service_update_entry_async); - g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) update_entry_async_data_free); - g_task_run_in_thread (task, update_entry_thread); + g_task_set_task_data (task, data, (GDestroyNotify) update_entry_async_data_free); + + /* Append the data */ + klass = GDATA_PARSABLE_GET_CLASS (entry); + g_assert (klass->get_content_type != NULL); + if (g_strcmp0 (klass->get_content_type (), "application/json") == 0) { + /* Get the edit URI */ + _link = gdata_entry_look_up_link (entry, GDATA_LINK_SELF); + g_assert (_link != NULL); + data->message = _gdata_service_build_message (self, domain, SOUP_METHOD_PUT, gdata_link_get_uri (_link), gdata_entry_get_etag (entry), TRUE); + soup_message_headers_set_content_type (soup_message_get_request_headers (data->message), "application/json", NULL); + } else { + /* Get the edit URI */ + _link = gdata_entry_look_up_link (entry, GDATA_LINK_EDIT); + g_assert (_link != NULL); + data->message = _gdata_service_build_message (self, domain, SOUP_METHOD_PUT, gdata_link_get_uri (_link), gdata_entry_get_etag (entry), TRUE); + soup_message_headers_set_content_type (soup_message_get_request_headers (data->message), "application/atom+xml", NULL); + } + upload_data = gdata_parsable_get_json (GDATA_PARSABLE (entry)); + body = g_bytes_new_take (upload_data, strlen (upload_data)); + + g_signal_connect (data->message, "starting", G_CALLBACK (entry_message_starting), body); + g_signal_connect (data->message, "finished", G_CALLBACK (entry_message_finished), body); + + /* Send the message */ + _gdata_service_send_async (self, data->message, g_task_get_cancellable (task), + update_entry_async_cb, task); } /** @@ -1583,6 +1970,7 @@ gdata_service_update_entry (GDataService *self, GDataAuthorizationDomain *domain gchar *upload_data; guint status; GDataParsableClass *klass; + GBytes *body; g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL); g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL); @@ -1598,22 +1986,28 @@ gdata_service_update_entry (GDataService *self, GDataAuthorizationDomain *domain _link = gdata_entry_look_up_link (entry, GDATA_LINK_SELF); g_assert (_link != NULL); message = _gdata_service_build_message (self, domain, SOUP_METHOD_PUT, gdata_link_get_uri (_link), gdata_entry_get_etag (entry), TRUE); - upload_data = gdata_parsable_get_json (GDATA_PARSABLE (entry)); - soup_message_set_request (message, "application/json", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data)); + soup_message_headers_set_content_type (soup_message_get_request_headers (message), "application/json", NULL); } else { /* Get the edit URI */ _link = gdata_entry_look_up_link (entry, GDATA_LINK_EDIT); g_assert (_link != NULL); message = _gdata_service_build_message (self, domain, SOUP_METHOD_PUT, gdata_link_get_uri (_link), gdata_entry_get_etag (entry), TRUE); - upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (entry)); - soup_message_set_request (message, "application/atom+xml", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data)); + soup_message_headers_set_content_type (soup_message_get_request_headers (message), "application/atom+xml", NULL); } + upload_data = gdata_parsable_get_json (GDATA_PARSABLE (entry)); + body = g_bytes_new_take (upload_data, strlen (upload_data)); + + g_signal_connect (message, "starting", G_CALLBACK (entry_message_starting), body); + g_signal_connect (message, "finished", G_CALLBACK (entry_message_finished), body); /* Send the message */ - status = _gdata_service_send_message (self, message, cancellable, error); + body = _gdata_service_send (self, message, cancellable, error); + status = soup_message_get_status (message); - if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) { + if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ + _gdata_service_parse_soup_error (self, error); + g_clear_pointer (&body, g_bytes_unref); g_object_unref (message); return NULL; } else if (status != SOUP_STATUS_OK) { @@ -1622,54 +2016,81 @@ gdata_service_update_entry (GDataService *self, GDataAuthorizationDomain *domain g_assert (service_klass->parse_error_response != NULL); service_klass->parse_error_response (self, GDATA_OPERATION_UPDATE, status, soup_message_get_reason_phrase (message), - message->response_body->data, - message->response_body->length, error); + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), error); + g_bytes_unref (body); g_object_unref (message); return NULL; } /* Parse the XML; create and return a new GDataEntry of the same type as @entry */ if (g_strcmp0 (klass->get_content_type (), "application/json") == 0) { - updated_entry = GDATA_ENTRY (gdata_parsable_new_from_json (G_OBJECT_TYPE (entry), message->response_body->data, - message->response_body->length, error)); + updated_entry = GDATA_ENTRY (gdata_parsable_new_from_json (G_OBJECT_TYPE (entry), + g_bytes_get_data (body, NULL), g_bytes_get_size (body), error)); } else { - updated_entry = GDATA_ENTRY (gdata_parsable_new_from_xml (G_OBJECT_TYPE (entry), message->response_body->data, - message->response_body->length, error)); + updated_entry = GDATA_ENTRY (gdata_parsable_new_from_xml (G_OBJECT_TYPE (entry), + g_bytes_get_data (body, NULL), g_bytes_get_size (body), error)); } + g_bytes_unref (body); g_object_unref (message); return updated_entry; } typedef struct { + SoupMessage *message; GDataAuthorizationDomain *domain; - GDataEntry *entry; } DeleteEntryAsyncData; static void delete_entry_async_data_free (DeleteEntryAsyncData *data) { - if (data->domain != NULL) - g_object_unref (data->domain); - - if (data->entry != NULL) - g_object_unref (data->entry); - + g_clear_object (&data->domain); + g_clear_object (&data->message); g_slice_free (DeleteEntryAsyncData, data); } -static void -delete_entry_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) +static void delete_entry_async_cb (GObject *object, GAsyncResult *result, + gpointer user_data) { - GDataService *service = GDATA_SERVICE (source_object); - g_autoptr(GError) error = NULL; - DeleteEntryAsyncData *data = task_data; + GDataService *self = GDATA_SERVICE (object); + GTask *task = user_data; + InsertEntryAsyncData *data = g_task_get_task_data (task); + SoupMessage *message = data->message; + guint status; + GBytes *body; + GError *error = NULL; - /* Delete the entry and return */ - if (!gdata_service_delete_entry (service, data->domain, data->entry, cancellable, &error)) - g_task_return_error (task, g_steal_pointer (&error)); - else - g_task_return_boolean (task, TRUE); + body = _gdata_service_send_finish (self, result, &error); + status = soup_message_get_status (message); + + if (!body || status == SOUP_STATUS_NONE) { + /* Redirect error or cancelled */ + _gdata_service_parse_soup_error (self, &error); + g_clear_pointer (&body, g_bytes_unref); + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, FALSE); + g_object_unref (task); + return; + } else if (status != SOUP_STATUS_OK && status != SOUP_STATUS_NO_CONTENT) { + /* Error */ + GDataServiceClass *service_klass = GDATA_SERVICE_GET_CLASS (self); + g_assert (service_klass->parse_error_response != NULL); + service_klass->parse_error_response (self, GDATA_OPERATION_DELETION, status, + soup_message_get_reason_phrase (message), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), &error); + g_bytes_unref (body); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + g_bytes_unref (body); + g_task_return_boolean (task, TRUE); + g_object_unref (task); } /** @@ -1695,8 +2116,11 @@ void gdata_service_delete_entry_async (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { - g_autoptr(GTask) task = NULL; + GTask *task = NULL; DeleteEntryAsyncData *data; + GDataParsableClass *klass; + GDataLink *_link; + gchar *fixed_uri; g_return_if_fail (GDATA_IS_SERVICE (self)); g_return_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain)); @@ -1705,12 +2129,28 @@ gdata_service_delete_entry_async (GDataService *self, GDataAuthorizationDomain * data = g_slice_new (DeleteEntryAsyncData); data->domain = (domain != NULL) ? g_object_ref (domain) : NULL; - data->entry = g_object_ref (entry); task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, gdata_service_delete_entry_async); - g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) delete_entry_async_data_free); - g_task_run_in_thread (task, delete_entry_thread); + g_task_set_task_data (task, data, (GDestroyNotify) delete_entry_async_data_free); + + /* Get the edit URI. We have to fix it to always use HTTPS as YouTube videos appear to incorrectly return a HTTP URI as their edit URI. */ + klass = GDATA_PARSABLE_GET_CLASS (entry); + g_assert (klass->get_content_type != NULL); + if (g_strcmp0 (klass->get_content_type (), "application/json") == 0) { + _link = gdata_entry_look_up_link (entry, GDATA_LINK_SELF); + } else { + _link = gdata_entry_look_up_link (entry, GDATA_LINK_EDIT); + } + g_assert (_link != NULL); + + fixed_uri = _gdata_service_fix_uri_scheme (gdata_link_get_uri (_link)); + data->message = _gdata_service_build_message (self, domain, SOUP_METHOD_DELETE, fixed_uri, gdata_entry_get_etag (entry), TRUE); + g_free (fixed_uri); + + /* Send the message */ + _gdata_service_send_async (self, data->message, g_task_get_cancellable (task), + delete_entry_async_cb, task); } /** @@ -1770,6 +2210,7 @@ gdata_service_delete_entry (GDataService *self, GDataAuthorizationDomain *domain guint status; gchar *fixed_uri; GDataParsableClass *klass; + GBytes *body; g_return_val_if_fail (GDATA_IS_SERVICE (self), FALSE); g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), FALSE); @@ -1792,10 +2233,13 @@ gdata_service_delete_entry (GDataService *self, GDataAuthorizationDomain *domain g_free (fixed_uri); /* Send the message */ - status = _gdata_service_send_message (self, message, cancellable, error); + body = _gdata_service_send (self, message, cancellable, error); + status = soup_message_get_status (message); - if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) { + if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ + _gdata_service_parse_soup_error (self, error); + g_clear_pointer (&body, g_bytes_unref); g_object_unref (message); return FALSE; } else if (status != SOUP_STATUS_OK && status != SOUP_STATUS_NO_CONTENT) { @@ -1804,12 +2248,14 @@ gdata_service_delete_entry (GDataService *self, GDataAuthorizationDomain *domain g_assert (service_klass->parse_error_response != NULL); service_klass->parse_error_response (self, GDATA_OPERATION_DELETION, status, soup_message_get_reason_phrase (message), - message->response_body->data, - message->response_body->length, error); + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), error); + g_bytes_unref (body); g_object_unref (message); return FALSE; } + g_bytes_unref (body); g_object_unref (message); return TRUE; @@ -1881,7 +2327,7 @@ gdata_service_get_timeout (GDataService *self) g_return_val_if_fail (GDATA_IS_SERVICE (self), 0); - g_object_get (self->priv->session, SOUP_SESSION_TIMEOUT, &timeout, NULL); + g_object_get (self->priv->session, "timeout", &timeout, NULL); return timeout; } @@ -1903,7 +2349,7 @@ void gdata_service_set_timeout (GDataService *self, guint timeout) { g_return_if_fail (GDATA_IS_SERVICE (self)); - g_object_set (self->priv->session, SOUP_SESSION_TIMEOUT, timeout, NULL); + g_object_set (self->priv->session, "timeout", timeout, NULL); g_object_notify (G_OBJECT (self), "timeout"); } @@ -2101,29 +2547,36 @@ soup_log_printer (SoupLogger *logger, SoupLoggerLogLevel level, char direction, } else if (direction == '<' && g_str_has_prefix (data, "Location: ") == TRUE) { /* Looks like: * "Location: https://www.google.com/calendar/feeds/default/owncalendars/full?gsessionid=sBjmp05m5i67exYA51XjDA". */ - SoupURI *uri; + GUri *uri; gchar *_uri; GHashTable *params; - uri = soup_uri_new (data + strlen ("Location: ")); + uri = g_uri_parse (data + strlen ("Location: "), SOUP_HTTP_URI_FLAGS, NULL); + + if (g_uri_get_query (uri)) { + GUri *tmp_uri; + gchar *query; - if (uri->query != NULL) { - params = soup_form_decode (uri->query); + params = soup_form_decode (g_uri_get_query (uri)); /* strdup()s are necessary because the hash table's set up to free keys. */ if (g_hash_table_lookup (params, "gsessionid") != NULL) { g_hash_table_insert (params, (gpointer) g_strdup ("gsessionid"), (gpointer) ""); } - soup_uri_set_query_from_form (uri, params); + query = soup_form_encode_hash (params); + tmp_uri = uri; + uri = soup_uri_copy (uri, SOUP_URI_QUERY, query, SOUP_URI_NONE); + g_free (query); + g_uri_unref (tmp_uri); g_hash_table_destroy (params); } - _uri = soup_uri_to_string (uri, FALSE); + _uri = g_uri_to_string_partial (uri, G_URI_HIDE_PASSWORD); _data = g_strconcat ("Location: ", _uri, NULL); g_free (_uri); - soup_uri_free (uri); + g_uri_unref (uri); } else if (direction == '<' && g_str_has_prefix (data, "SID=") == TRUE) { _data = g_strdup ("SID="); } else if (direction == '<' && g_str_has_prefix (data, "LSID=") == TRUE) { @@ -2238,24 +2691,19 @@ SoupSession * _gdata_service_build_session (void) { SoupSession *session; - gboolean ssl_strict = TRUE; gchar *user_agent; + session = soup_session_new_with_options ("timeout", 0, NULL); + /* Iff LIBGDATA_LAX_SSL_CERTIFICATES=1, relax SSL certificate validation to allow using invalid/unsigned certificates for testing. */ if (g_strcmp0 (g_getenv ("LIBGDATA_LAX_SSL_CERTIFICATES"), "1") == 0) { - ssl_strict = FALSE; + g_object_set_data (G_OBJECT (session), "gdata-lax-ssl", (gpointer)TRUE); } - session = soup_session_new_with_options ("ssl-strict", ssl_strict, - "timeout", 0, - NULL); - user_agent = build_user_agent (soup_session_has_feature (session, SOUP_TYPE_CONTENT_DECODER)); g_object_set (session, "user-agent", user_agent, NULL); g_free (user_agent); - soup_session_add_feature_by_type (session, SOUP_TYPE_PROXY_RESOLVER_DEFAULT); - /* Log all libsoup traffic if debugging's turned on */ if (_gdata_service_get_log_level () > GDATA_LOG_MESSAGES) { SoupLoggerLogLevel level; @@ -2275,7 +2723,7 @@ _gdata_service_build_session (void) g_assert_not_reached (); } - logger = soup_logger_new (level, -1); + logger = soup_logger_new (level); soup_logger_set_printer (logger, (SoupLoggerPrinter) soup_log_printer, NULL, NULL); soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger)); diff --git a/gdata/gdata-service.h b/gdata/gdata-service.h index a72bc529..d106d8eb 100644 --- a/gdata/gdata-service.h +++ b/gdata/gdata-service.h @@ -162,6 +162,7 @@ typedef struct { GDataQuery *query, GType entry_type, SoupMessage *message, + GBytes *body, GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, -- GitLab From 7233598708afb5009fedc6ce86f30124801003e4 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:12 +0100 Subject: [PATCH 07/45] soup3: replace message->response_body instances --- gdata/gdata-access-handler.c | 13 ++--- gdata/gdata-batch-operation.c | 17 ++++-- gdata/gdata-oauth2-authorizer.c | 32 ++++++------ .../calendar/gdata-calendar-calendar.c | 9 ++-- .../documents/gdata-documents-entry.c | 12 +++-- .../documents/gdata-documents-service.c | 52 ++++++++++++------- .../picasaweb/gdata-picasaweb-service.c | 8 +-- .../services/youtube/gdata-youtube-service.c | 8 +-- 8 files changed, 90 insertions(+), 61 deletions(-) diff --git a/gdata/gdata-access-handler.c b/gdata/gdata-access-handler.c index a754d319..8622ed4b 100644 --- a/gdata/gdata-access-handler.c +++ b/gdata/gdata-access-handler.c @@ -95,6 +95,7 @@ gdata_access_handler_real_get_rules (GDataAccessHandler *self, SoupMessage *message; SoupMessageHeaders *headers; const gchar *content_type; + GBytes *body = NULL; _link = gdata_entry_look_up_link (GDATA_ENTRY (self), GDATA_LINK_ACCESS_CONTROL_LIST); g_assert (_link != NULL); @@ -104,12 +105,12 @@ gdata_access_handler_real_get_rules (GDataAccessHandler *self, domain = iface->get_authorization_domain (self); } - message = _gdata_service_query (service, domain, gdata_link_get_uri (_link), NULL, cancellable, error); + message = _gdata_service_query (service, domain, gdata_link_get_uri (_link), NULL, cancellable, &body, error); if (message == NULL) { return NULL; } - g_assert (message->response_body->data != NULL); + g_assert (body && g_bytes_get_data (body, NULL)); headers = soup_message_get_response_headers (message); content_type = soup_message_headers_get_content_type (headers, NULL); @@ -117,14 +118,14 @@ gdata_access_handler_real_get_rules (GDataAccessHandler *self, if (g_strcmp0 (content_type, "application/json") == 0) { /* Definitely JSON. */ g_debug("JSON content type detected."); - feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, message->response_body->data, message->response_body->length, GDATA_TYPE_ACCESS_RULE, - progress_callback, progress_user_data, error); + feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, g_bytes_get_data (body, NULL), + g_bytes_get_size (body), GDATA_TYPE_ACCESS_RULE, } else { /* Potentially XML. Don't bother checking the Content-Type, since the parser * will fail gracefully if the response body is not valid XML. */ g_debug("XML content type detected."); - feed = _gdata_feed_new_from_xml (GDATA_TYPE_FEED, message->response_body->data, message->response_body->length, GDATA_TYPE_ACCESS_RULE, - progress_callback, progress_user_data, error); + feed = _gdata_feed_new_from_xml (GDATA_TYPE_FEED, g_bytes_get_data (body, NULL), + g_bytes_get_size (body), GDATA_TYPE_ACCESS_RULE, } g_object_unref (message); diff --git a/gdata/gdata-batch-operation.c b/gdata/gdata-batch-operation.c index 06ec4b2f..c7059981 100644 --- a/gdata/gdata-batch-operation.c +++ b/gdata/gdata-batch-operation.c @@ -605,6 +605,7 @@ gdata_batch_operation_run (GDataBatchOperation *self, GCancellable *cancellable, gpointer op_id; BatchOperation *op; GError *child_error = NULL; + GBytes *body; g_return_val_if_fail (GDATA_IS_BATCH_OPERATION (self), FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); @@ -681,7 +682,8 @@ gdata_batch_operation_run (GDataBatchOperation *self, GCancellable *cancellable, priv->has_run = TRUE; /* Send the message */ - status = _gdata_service_send_message (priv->service, message, cancellable, &child_error); + body = _gdata_service_send (priv->service, message, cancellable, &child_error); + status = soup_message_get_status (message); if (status != SOUP_STATUS_OK) { /* Iff status is SOUP_STATUS_NONE or SOUP_STATUS_CANCELLED, child_error has already been set */ @@ -690,18 +692,23 @@ gdata_batch_operation_run (GDataBatchOperation *self, GCancellable *cancellable, GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (priv->service); g_assert (klass->parse_error_response != NULL); klass->parse_error_response (priv->service, GDATA_OPERATION_BATCH, status, - soup_message_get_reaspon_phrase (message), message->response_body->data, - message->response_body->length, &child_error); + soup_message_get_reason_phrase (message), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), &child_error); } + g_clear_pointer (&body, g_bytes_unref); g_object_unref (message); goto error; } /* Parse the XML; GDataBatchFeed will fire off the relevant callbacks */ - g_assert (message->response_body->data != NULL); - feed = GDATA_FEED (_gdata_parsable_new_from_xml (GDATA_TYPE_BATCH_FEED, message->response_body->data, message->response_body->length, + g_assert (body && g_bytes_get_data (body, NULL)); + feed = GDATA_FEED (_gdata_parsable_new_from_xml (GDATA_TYPE_BATCH_FEED, + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), self, &child_error)); + g_bytes_unref (body); g_object_unref (message); if (feed == NULL) diff --git a/gdata/gdata-oauth2-authorizer.c b/gdata/gdata-oauth2-authorizer.c index b56a16dd..73d5868c 100644 --- a/gdata/gdata-oauth2-authorizer.c +++ b/gdata/gdata-oauth2-authorizer.c @@ -642,6 +642,7 @@ refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, gchar *request_body; guint status; GError *child_error = NULL; + GBytes *body; g_return_val_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self), FALSE); @@ -677,8 +678,7 @@ refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, strlen (request_body)); /* Send the message */ - _gdata_service_actually_send_message (priv->session, message, - cancellable, error); + body = soup_session_send_and_read (priv->session, message, cancellable, error); status = soup_message_get_status (message); if (status == SOUP_STATUS_CANCELLED) { @@ -688,22 +688,22 @@ refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, } else if (status != SOUP_STATUS_OK) { parse_grant_error (GDATA_OAUTH2_AUTHORIZER (self), status, soup_message_get_reason_phrase (message), - message->response_body->data, - message->response_body->length, + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), error); + g_bytes_unref (body); g_object_unref (message); return FALSE; } - g_assert (message->response_body->data != NULL); - /* Parse and handle the response */ parse_grant_response (GDATA_OAUTH2_AUTHORIZER (self), status, soup_message_get_reason_phrase (message), - message->response_body->data, - message->response_body->length, &child_error); + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), &child_error); + g_bytes_unref (body); g_object_unref (message); if (child_error != NULL) { @@ -1160,6 +1160,7 @@ gdata_oauth2_authorizer_request_authorization (GDataOAuth2Authorizer *self, gchar *request_body = NULL; /* owned */ guint status; GError *child_error = NULL; + GBytes *body; g_return_val_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self), FALSE); g_return_val_if_fail (authorization_code != NULL && @@ -1193,8 +1194,7 @@ gdata_oauth2_authorizer_request_authorization (GDataOAuth2Authorizer *self, request_body = NULL; /* Send the message */ - _gdata_service_actually_send_message (priv->session, message, - cancellable, error); + body = soup_session_send_and_read (priv->session, message, cancellable, error); status = soup_message_get_status (message); if (status == SOUP_STATUS_CANCELLED) { @@ -1203,21 +1203,21 @@ gdata_oauth2_authorizer_request_authorization (GDataOAuth2Authorizer *self, return FALSE; } else if (status != SOUP_STATUS_OK) { parse_grant_error (self, status, soup_message_get_reason_phrase (message), - message->response_body->data, - message->response_body->length, + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), error); + g_bytes_unref (body); g_object_unref (message); return FALSE; } - g_assert (message->response_body->data != NULL); - /* Parse and handle the response */ parse_grant_response (self, status, soup_message_get_reason_phrase (message), - message->response_body->data, - message->response_body->length, &child_error); + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), &child_error); + g_bytes_unref (body); g_object_unref (message); if (child_error != NULL) { diff --git a/gdata/services/calendar/gdata-calendar-calendar.c b/gdata/services/calendar/gdata-calendar-calendar.c index c667e266..69bca348 100644 --- a/gdata/services/calendar/gdata-calendar-calendar.c +++ b/gdata/services/calendar/gdata-calendar-calendar.c @@ -227,6 +227,7 @@ get_rules (GDataAccessHandler *self, GDataFeed *feed; GDataLink *_link; SoupMessage *message; + GBytes *body = NULL; GList/**/ *rules, *i; const gchar *calendar_id; @@ -241,16 +242,16 @@ get_rules (GDataAccessHandler *self, message = _gdata_service_query (service, domain, gdata_link_get_uri (_link), NULL, - cancellable, error); + cancellable, &body, error); if (message == NULL) { return NULL; } - g_assert (message->response_body->data != NULL); + g_assert (body && g_bytes_get_data (body, NULL)); feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, - message->response_body->data, - message->response_body->length, + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), GDATA_TYPE_CALENDAR_ACCESS_RULE, progress_callback, progress_user_data, error); diff --git a/gdata/services/documents/gdata-documents-entry.c b/gdata/services/documents/gdata-documents-entry.c index 8b727b81..b502253c 100644 --- a/gdata/services/documents/gdata-documents-entry.c +++ b/gdata/services/documents/gdata-documents-entry.c @@ -335,6 +335,7 @@ get_rules (GDataAccessHandler *self, GDataFeed *feed; GDataLink *_link; SoupMessage *message; + GBytes *body = NULL; _link = gdata_entry_look_up_link (GDATA_ENTRY (self), GDATA_LINK_ACCESS_CONTROL_LIST); g_assert (_link != NULL); @@ -344,16 +345,19 @@ get_rules (GDataAccessHandler *self, domain = iface->get_authorization_domain (self); } - message = _gdata_service_query (service, domain, gdata_link_get_uri (_link), NULL, cancellable, error); + message = _gdata_service_query (service, domain, gdata_link_get_uri (_link), NULL, cancellable, &body, error); if (message == NULL) { return NULL; } - g_assert (message->response_body->data != NULL); + g_assert (body && g_bytes_get_data (body, 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); + feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, g_bytes_get_data (body, NULL), + g_bytes_get_size (body), + GDATA_TYPE_DOCUMENTS_ACCESS_RULE, + progress_callback, progress_user_data, error); + g_bytes_unref (body); g_object_unref (message); return feed; diff --git a/gdata/services/documents/gdata-documents-service.c b/gdata/services/documents/gdata-documents-service.c index 39aa39ee..a2ab71c1 100644 --- a/gdata/services/documents/gdata-documents-service.c +++ b/gdata/services/documents/gdata-documents-service.c @@ -432,6 +432,7 @@ gdata_documents_service_get_metadata (GDataDocumentsService *self, GCancellable GDataDocumentsMetadata *metadata; const gchar *uri = "https://www.googleapis.com/drive/v2/about"; SoupMessage *message; + GBytes *body; guint status; g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self), NULL); @@ -441,7 +442,8 @@ gdata_documents_service_get_metadata (GDataDocumentsService *self, GCancellable message = _gdata_service_build_message (GDATA_SERVICE (self), get_documents_authorization_domain (), SOUP_METHOD_GET, uri, NULL, FALSE); /* Send the message */ - status = _gdata_service_send_message (GDATA_SERVICE (self), message, cancellable, error); + body = _gdata_service_send (GDATA_SERVICE (self), message, cancellable, error); + status = soup_message_get_status (message); if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) { /* Redirect error or cancelled */ @@ -453,16 +455,20 @@ gdata_documents_service_get_metadata (GDataDocumentsService *self, GCancellable g_assert (klass->parse_error_response != NULL); klass->parse_error_response (GDATA_SERVICE (self), GDATA_OPERATION_QUERY, status, soup_message_get_reason_phrase (message), - message->response_body->data, - message->response_body->length, error); + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), error); + g_bytes_unref (body); g_object_unref (message); return NULL; } /* Parse the JSON; and update the entry */ - g_assert (message->response_body->data != NULL); - metadata = GDATA_DOCUMENTS_METADATA (gdata_parsable_new_from_json (GDATA_TYPE_DOCUMENTS_METADATA, message->response_body->data, message->response_body->length, - error)); + g_assert (body && g_bytes_get_data (body, NULL)); + metadata = GDATA_DOCUMENTS_METADATA (gdata_parsable_new_from_json (GDATA_TYPE_DOCUMENTS_METADATA, + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), + error)); + g_bytes_unref (body); g_object_unref (message); return metadata; @@ -1292,6 +1298,7 @@ gdata_documents_service_add_entry_to_folder (GDataDocumentsService *self, GDataD gchar *uri; SoupMessage *message; guint status; + GBytes *body; GList *l; g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self), NULL); @@ -1347,7 +1354,8 @@ gdata_documents_service_add_entry_to_folder (GDataDocumentsService *self, GDataD g_object_unref (local_entry); /* Send the message */ - status = _gdata_service_send_message (GDATA_SERVICE (self), message, cancellable, error); + body = _gdata_service_send (GDATA_SERVICE (self), message, cancellable, error); + status = soup_message_get_status (message); if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) { /* Redirect error or cancelled */ @@ -1359,16 +1367,20 @@ gdata_documents_service_add_entry_to_folder (GDataDocumentsService *self, GDataD g_assert (klass->parse_error_response != NULL); klass->parse_error_response (GDATA_SERVICE (self), operation_type, status, soup_message_get_reason_phrase (message), - message->response_body->data, - message->response_body->length, error); + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), error); + g_bytes_unref (body); g_object_unref (message); return NULL; } /* Parse the JSON; and update the entry */ - g_assert (message->response_body->data != NULL); - new_entry = GDATA_DOCUMENTS_ENTRY (gdata_parsable_new_from_json (entry_type, message->response_body->data, message->response_body->length, - error)); + g_assert (body && g_bytes_get_data (body, NULL)); + new_entry = GDATA_DOCUMENTS_ENTRY (gdata_parsable_new_from_json (entry_type, + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), + error)); + g_bytes_unref (body); g_object_unref (message); return new_entry; @@ -1499,6 +1511,7 @@ gdata_documents_service_remove_entry_from_folder (GDataDocumentsService *self, G guint status; gboolean req_status = TRUE; SoupMessage *message; + GBytes *body; g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self), NULL); g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (entry), NULL); @@ -1564,7 +1577,8 @@ gdata_documents_service_remove_entry_from_folder (GDataDocumentsService *self, G g_free (modified_uri); /* Send the message */ - status = _gdata_service_send_message (GDATA_SERVICE (self), message, cancellable, error); + body = _gdata_service_send (GDATA_SERVICE (self), message, cancellable, error); + status = soup_message_get_status (message); if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) { /* Redirect error or cancelled */ @@ -1575,15 +1589,15 @@ gdata_documents_service_remove_entry_from_folder (GDataDocumentsService *self, G GDataServiceClass *service_klass = GDATA_SERVICE_GET_CLASS (self); g_assert (service_klass->parse_error_response != NULL); service_klass->parse_error_response (GDATA_SERVICE (self), - GDATA_OPERATION_DELETION, - status, - soup_message_get_reason_phrase (message), - message->response_body->data, - message->response_body->length, - error); + GDATA_OPERATION_DELETION, + status, + soup_message_get_reason_phrase (message), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), error); req_status = FALSE; } + g_bytes_unref (body); g_object_unref (message); if (req_status) { diff --git a/gdata/services/picasaweb/gdata-picasaweb-service.c b/gdata/services/picasaweb/gdata-picasaweb-service.c index f9d5715d..c98c9a07 100644 --- a/gdata/services/picasaweb/gdata-picasaweb-service.c +++ b/gdata/services/picasaweb/gdata-picasaweb-service.c @@ -232,6 +232,7 @@ gdata_picasaweb_service_get_user (GDataPicasaWebService *self, const gchar *user gchar *uri; GDataParsable *user; SoupMessage *message; + GBytes *body = NULL; g_return_val_if_fail (GDATA_IS_PICASAWEB_SERVICE (self), NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); @@ -244,14 +245,15 @@ gdata_picasaweb_service_get_user (GDataPicasaWebService *self, const gchar *user return NULL; } - message = _gdata_service_query (GDATA_SERVICE (self), get_picasaweb_authorization_domain (), uri, NULL, cancellable, error); + message = _gdata_service_query (GDATA_SERVICE (self), get_picasaweb_authorization_domain (), uri, NULL, cancellable, &body, error); g_free (uri); if (message == NULL) return NULL; - g_assert (message->response_body->data != NULL); - user = gdata_parsable_new_from_xml (GDATA_TYPE_PICASAWEB_USER, message->response_body->data, message->response_body->length, error); + g_assert (body && g_bytes_get_data (body, NULL)); + user = gdata_parsable_new_from_xml (GDATA_TYPE_PICASAWEB_USER, g_bytes_get_data (body, NULL), g_bytes_get_size (body), error); + g_bytes_unref (body); g_object_unref (message); return GDATA_PICASAWEB_USER (user); diff --git a/gdata/services/youtube/gdata-youtube-service.c b/gdata/services/youtube/gdata-youtube-service.c index d4c08de4..b6dd3304 100644 --- a/gdata/services/youtube/gdata-youtube-service.c +++ b/gdata/services/youtube/gdata-youtube-service.c @@ -1096,6 +1096,7 @@ gdata_youtube_service_get_categories (GDataYouTubeService *self, GCancellable *c gchar *uri; SoupMessage *message; GDataAPPCategories *categories; + GBytes *body = NULL; g_return_val_if_fail (GDATA_IS_YOUTUBE_SERVICE (self), NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); @@ -1114,16 +1115,15 @@ gdata_youtube_service_get_categories (GDataYouTubeService *self, GCancellable *c locale); message = _gdata_service_query (GDATA_SERVICE (self), get_youtube_authorization_domain (), - uri, NULL, cancellable, error); + uri, NULL, cancellable, &body, error); g_free (uri); if (message == NULL) return NULL; - g_assert (message->response_body->data != NULL); + g_assert (body && g_bytes_get_data (body, NULL)); categories = GDATA_APP_CATEGORIES (_gdata_parsable_new_from_json (GDATA_TYPE_APP_CATEGORIES, - message->response_body->data, - message->response_body->length, + g_bytes_get_data (body, NULL), g_bytes_get_size (body), GSIZE_TO_POINTER (GDATA_TYPE_YOUTUBE_CATEGORY), error)); g_object_unref (message); -- GitLab From 7796fda1e68b9100b4ba787674afa86c8babd21b Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:12 +0100 Subject: [PATCH 08/45] soup3: implement async for access-handler --- gdata/gdata-access-handler.c | 129 ++++++++++++++++++++++++++--------- gdata/gdata-access-handler.h | 9 +++ 2 files changed, 107 insertions(+), 31 deletions(-) diff --git a/gdata/gdata-access-handler.c b/gdata/gdata-access-handler.c index 8622ed4b..3ae921d9 100644 --- a/gdata/gdata-access-handler.c +++ b/gdata/gdata-access-handler.c @@ -54,6 +54,16 @@ gdata_access_handler_real_get_rules (GDataAccessHandler *self, gpointer progress_user_data, GError **error); +static void +gdata_access_handler_real_get_rules_async (GDataAccessHandler *self, + GDataService *service, + GCancellable *cancellable, + GDataQueryProgressCallback progress_callback, + gpointer progress_user_data, + GDestroyNotify destroy_progress_user_data, + GAsyncReadyCallback callback, + gpointer user_data); + typedef GDataAccessHandlerIface GDataAccessHandlerInterface; G_DEFINE_INTERFACE (GDataAccessHandler, gdata_access_handler, GDATA_TYPE_ENTRY); @@ -62,10 +72,11 @@ static void gdata_access_handler_default_init (GDataAccessHandlerInterface *iface) { iface->get_rules = gdata_access_handler_real_get_rules; + iface->get_rules_async = gdata_access_handler_real_get_rules_async; } typedef struct { - GDataService *service; + SoupMessage *message; GDataQueryProgressCallback progress_callback; gpointer progress_user_data; GDestroyNotify destroy_progress_user_data; @@ -74,9 +85,7 @@ typedef struct { static void get_rules_async_data_free (GetRulesAsyncData *self) { - if (self->service != NULL) - g_object_unref (self->service); - + g_clear_object (&self->message); g_slice_free (GetRulesAsyncData, self); } @@ -120,44 +129,107 @@ gdata_access_handler_real_get_rules (GDataAccessHandler *self, g_debug("JSON content type detected."); feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, g_bytes_get_data (body, NULL), g_bytes_get_size (body), GDATA_TYPE_ACCESS_RULE, + progress_callback, progress_user_data, error); } else { /* Potentially XML. Don't bother checking the Content-Type, since the parser * will fail gracefully if the response body is not valid XML. */ g_debug("XML content type detected."); feed = _gdata_feed_new_from_xml (GDATA_TYPE_FEED, g_bytes_get_data (body, NULL), g_bytes_get_size (body), GDATA_TYPE_ACCESS_RULE, + progress_callback, progress_user_data, error); } + g_bytes_unref (body); g_object_unref (message); return feed; } static void -get_rules_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) +get_rules_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) { - GDataAccessHandler *access_handler = GDATA_ACCESS_HANDLER (source_object); - GDataAccessHandlerIface *iface; - g_autoptr(GError) error = NULL; - g_autoptr(GDataFeed) feed = NULL; - GetRulesAsyncData *data = task_data; + GDataService *service = GDATA_SERVICE (object); + GTask *task = user_data; + GetRulesAsyncData *data = g_task_get_task_data (task); + SoupMessageHeaders *headers; + const gchar *content_type; + GDataFeed *feed; + GBytes *body; + GError *error = NULL; - /* Execute the query and return */ - iface = GDATA_ACCESS_HANDLER_GET_IFACE (access_handler); - g_assert (iface->get_rules != NULL); + body = _gdata_service_send_finish (service, result, &error); - feed = iface->get_rules (access_handler, data->service, - cancellable, data->progress_callback, - data->progress_user_data, &error); + if (!_gdata_service_query_async_check (service, task, data->message, body, &error, data->destroy_progress_user_data, data->progress_user_data)) + return; - if (feed == NULL && error != NULL) - g_task_return_error (task, g_steal_pointer (&error)); - else - g_task_return_pointer (task, g_steal_pointer (&feed), g_object_unref); + headers = soup_message_get_response_headers (data->message); + content_type = soup_message_headers_get_content_type (headers, NULL); + + if (g_strcmp0 (content_type, "application/json") == 0) { + /* Definitely JSON. */ + g_debug("JSON content type detected."); + feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, g_bytes_get_data (body, NULL), + g_bytes_get_size (body), GDATA_TYPE_ACCESS_RULE, + data->progress_callback, data->progress_user_data, &error); + } else { + /* Potentially XML. Don't bother checking the Content-Type, since the parser + * will fail gracefully if the response body is not valid XML. */ + g_debug("XML content type detected."); + feed = _gdata_feed_new_from_xml (GDATA_TYPE_FEED, g_bytes_get_data (body, NULL), + g_bytes_get_size (body), GDATA_TYPE_ACCESS_RULE, + data->progress_callback, data->progress_user_data, &error); + } + + g_bytes_unref (body); if (data->destroy_progress_user_data != NULL) { data->destroy_progress_user_data (data->progress_user_data); } + + if (error) + g_task_return_error (task, error); + else + g_task_return_pointer (task, feed, g_object_unref); + + g_object_unref (task); +} + +static void +gdata_access_handler_real_get_rules_async (GDataAccessHandler *self, + GDataService *service, + GCancellable *cancellable, + GDataQueryProgressCallback progress_callback, + gpointer progress_user_data, + GDestroyNotify destroy_progress_user_data, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task = NULL; + GDataAccessHandlerIface *iface; + GDataAuthorizationDomain *domain = NULL; + GDataLink *_link; + GetRulesAsyncData *data; + + _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); + } + + data = g_slice_new (GetRulesAsyncData); + data->message = NULL; + data->progress_callback = progress_callback; + data->progress_user_data = progress_user_data; + data->destroy_progress_user_data = destroy_progress_user_data; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, gdata_service_query_async); + g_task_set_task_data (task, data, (GDestroyNotify) get_rules_async_data_free); + + _gdata_service_query_async (service, domain, gdata_link_get_uri (_link), + NULL, get_rules_async_cb, task, &data->message); } /** @@ -189,24 +261,19 @@ gdata_access_handler_get_rules_async (GDataAccessHandler *self, GDataService *se GDestroyNotify destroy_progress_user_data, GAsyncReadyCallback callback, gpointer user_data) { - g_autoptr(GTask) task = NULL; - GetRulesAsyncData *data; + GDataAccessHandlerIface *iface; g_return_if_fail (GDATA_IS_ACCESS_HANDLER (self)); g_return_if_fail (GDATA_IS_SERVICE (service)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (callback != NULL); - data = g_slice_new (GetRulesAsyncData); - data->service = g_object_ref (service); - data->progress_callback = progress_callback; - data->progress_user_data = progress_user_data; - data->destroy_progress_user_data = destroy_progress_user_data; + iface = GDATA_ACCESS_HANDLER_GET_IFACE (self); + g_assert (iface->get_rules != NULL); - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, gdata_service_query_async); - g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) get_rules_async_data_free); - g_task_run_in_thread (task, get_rules_thread); + iface->get_rules_async (self, service, cancellable, progress_callback, + progress_user_data, destroy_progress_user_data, + callback, user_data); } /** diff --git a/gdata/gdata-access-handler.h b/gdata/gdata-access-handler.h index eff96c39..a436b46a 100644 --- a/gdata/gdata-access-handler.h +++ b/gdata/gdata-access-handler.h @@ -85,6 +85,15 @@ typedef struct { gpointer progress_user_data, GError **error); + void (*get_rules_async) (GDataAccessHandler *self, + GDataService *service, + GCancellable *cancellable, + GDataQueryProgressCallback progress_callback, + gpointer progress_user_data, + GDestroyNotify destroy_progress_user_data, + GAsyncReadyCallback callback, + gpointer user_data); + /*< private >*/ /* Padding for future expansion */ void (*_g_reserved1) (void); -- GitLab From e7cf7d44e5f92d55ae97c7b6402fe65b04569836 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:13 +0100 Subject: [PATCH 09/45] soup3: remove non-async fallback for async gdata-authorizer We cannot really do threaded stuff with soup3 anymore. --- gdata/gdata-authorizer.c | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/gdata/gdata-authorizer.c b/gdata/gdata-authorizer.c index b51c8950..8fd001cf 100644 --- a/gdata/gdata-authorizer.c +++ b/gdata/gdata-authorizer.c @@ -191,21 +191,6 @@ gdata_authorizer_refresh_authorization (GDataAuthorizer *self, GCancellable *can return iface->refresh_authorization (self, cancellable, error); } -static void -refresh_authorization_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) -{ - GDataAuthorizer *authorizer = GDATA_AUTHORIZER (source_object); - g_autoptr(GError) error = NULL; - - /* Refresh the authorisation and return */ - gdata_authorizer_refresh_authorization (authorizer, cancellable, &error); - - if (error != NULL) - g_task_return_error (task, g_steal_pointer (&error)); - else - g_task_return_boolean (task, TRUE); -} - /** * gdata_authorizer_refresh_authorization_async: * @self: a #GDataAuthorizer @@ -246,20 +231,9 @@ gdata_authorizer_refresh_authorization_async (GDataAuthorizer *self, GCancellabl iface->refresh_authorization_async (self, cancellable, callback, user_data); } else { g_autoptr(GTask) task = NULL; - task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, gdata_authorizer_refresh_authorization_async); - - - if (iface->refresh_authorization != NULL) { - /* If the _async() method isn't implemented, fall back to running the sync method in a thread */ - g_task_run_in_thread (task, refresh_authorization_thread); - } else { - /* If neither are implemented, immediately return FALSE with no error in a callback */ - g_task_return_boolean (task, FALSE); - } - - return; + g_task_return_boolean (task, FALSE); } } -- GitLab From e4cdbceb27196fe8997eec786cb7f9271913e241 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:13 +0100 Subject: [PATCH 10/45] soup3: implement gdata-batch-operation as real async code --- gdata/gdata-batch-operation.c | 218 +++++++++++++++++++++++++--------- 1 file changed, 162 insertions(+), 56 deletions(-) diff --git a/gdata/gdata-batch-operation.c b/gdata/gdata-batch-operation.c index c7059981..44fc814b 100644 --- a/gdata/gdata-batch-operation.c +++ b/gdata/gdata-batch-operation.c @@ -115,6 +115,7 @@ struct _GDataBatchOperationPrivate { GDataAuthorizationDomain *authorization_domain; gchar *feed_uri; GHashTable *operations; + SoupMessage *message; guint next_id; /* next available operation ID */ gboolean has_run; /* TRUE if the operation has been run already (though it does not necessarily have to have finished running) */ gboolean is_async; /* TRUE if the operation was run with *_run_async(); FALSE if run with *_run() */ @@ -569,54 +570,31 @@ gdata_batch_operation_add_deletion (GDataBatchOperation *self, GDataEntry *entry return add_operation (self, GDATA_BATCH_OPERATION_DELETION, entry, callback, user_data); } -/** - * gdata_batch_operation_run: - * @self: a #GDataBatchOperation - * @cancellable: (allow-none): a #GCancellable, or %NULL - * @error: a #GError, or %NULL - * - * Run the #GDataBatchOperation synchronously. This will send all the operations in the batch operation to the server, and call their respective - * callbacks synchronously (i.e. before gdata_batch_operation_run() returns, and in the same thread that called gdata_batch_operation_run()) as the - * server returns results for each operation. - * - * The callbacks for all of the operations in the batch operation are always guaranteed to be called, even if the batch operation as a whole fails. - * Each callback will be called exactly once for each time gdata_batch_operation_run() is called. - * - * The return value of the function indicates whether the overall batch operation was successful, and doesn't indicate the status of any of the - * operations it comprises. gdata_batch_operation_run() could return %TRUE even if all of its operations failed. - * - * @cancellable can be used to cancel the entire batch operation any time before or during the network activity. If @cancellable is cancelled - * after network activity has finished, gdata_batch_operation_run() will continue and finish as normal. - * - * Return value: %TRUE on success, %FALSE otherwise - * - * Since: 0.7.0 - */ -gboolean -gdata_batch_operation_run (GDataBatchOperation *self, GCancellable *cancellable, GError **error) +static void +batch_message_starting (SoupMessage *msg, gpointer body) +{ + soup_message_set_request_body_from_bytes (msg, "application/atom+xml", body); +} + +static void +batch_message_finished (SoupMessage *msg, gpointer body) +{ + g_bytes_unref (body); +} + +static SoupMessage * +batch_message_build (GDataBatchOperation *self, GError **error) { GDataBatchOperationPrivate *priv = self->priv; SoupMessage *message; - GDataFeed *feed; - gint64 updated; - gchar *upload_data; - guint status; GHashTableIter iter; gpointer op_id; BatchOperation *op; - GError *child_error = NULL; + GDataFeed *feed; + gchar *upload_data; + gint64 updated; GBytes *body; - g_return_val_if_fail (GDATA_IS_BATCH_OPERATION (self), FALSE); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - g_return_val_if_fail (priv->has_run == FALSE, FALSE); - - /* Check for early cancellation. */ - if (g_cancellable_set_error_if_cancelled (cancellable, error)) { - return FALSE; - } - /* Check whether the service actually supports these kinds of * operations. */ g_hash_table_iter_init (&iter, priv->operations); @@ -632,7 +610,7 @@ gdata_batch_operation_run (GDataBatchOperation *self, GCancellable *cancellable, GDATA_SERVICE_ERROR_WITH_BATCH_OPERATION, _("Batch operations are unsupported by " "this service.")); - return FALSE; + return NULL; } } @@ -674,10 +652,67 @@ gdata_batch_operation_run (GDataBatchOperation *self, GCancellable *cancellable, } upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (feed)); - soup_message_set_request (message, "application/atom+xml", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data)); + body = g_bytes_new_take (upload_data, strlen(upload_data)); + + g_signal_connect (message, "starting", G_CALLBACK (batch_message_starting), body); + g_signal_connect (message, "finished", G_CALLBACK (batch_message_finished), body); g_object_unref (feed); + return message; +} + +/** + * gdata_batch_operation_run: + * @self: a #GDataBatchOperation + * @cancellable: (allow-none): a #GCancellable, or %NULL + * @error: a #GError, or %NULL + * + * Run the #GDataBatchOperation synchronously. This will send all the operations in the batch operation to the server, and call their respective + * callbacks synchronously (i.e. before gdata_batch_operation_run() returns, and in the same thread that called gdata_batch_operation_run()) as the + * server returns results for each operation. + * + * The callbacks for all of the operations in the batch operation are always guaranteed to be called, even if the batch operation as a whole fails. + * Each callback will be called exactly once for each time gdata_batch_operation_run() is called. + * + * The return value of the function indicates whether the overall batch operation was successful, and doesn't indicate the status of any of the + * operations it comprises. gdata_batch_operation_run() could return %TRUE even if all of its operations failed. + * + * @cancellable can be used to cancel the entire batch operation any time before or during the network activity. If @cancellable is cancelled + * after network activity has finished, gdata_batch_operation_run() will continue and finish as normal. + * + * Return value: %TRUE on success, %FALSE otherwise + * + * Since: 0.7.0 + */ +gboolean +gdata_batch_operation_run (GDataBatchOperation *self, GCancellable *cancellable, GError **error) +{ + GDataBatchOperationPrivate *priv = self->priv; + SoupMessage *message; + guint status; + GHashTableIter iter; + gpointer op_id; + BatchOperation *op; + GError *child_error = NULL; + GBytes *body; + GDataFeed *feed; + + g_return_val_if_fail (GDATA_IS_BATCH_OPERATION (self), FALSE); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_return_val_if_fail (priv->has_run == FALSE, FALSE); + + /* Check for early cancellation. */ + if (g_cancellable_set_error_if_cancelled (cancellable, error)) { + return FALSE; + } + + message = batch_message_build (self, error); + + if (!message) + return FALSE; + /* Ensure that this GDataBatchOperation can't be run again */ priv->has_run = TRUE; @@ -685,9 +720,10 @@ gdata_batch_operation_run (GDataBatchOperation *self, GCancellable *cancellable, body = _gdata_service_send (priv->service, message, cancellable, &child_error); status = soup_message_get_status (message); - if (status != SOUP_STATUS_OK) { - /* Iff status is SOUP_STATUS_NONE or SOUP_STATUS_CANCELLED, child_error has already been set */ - if (status != SOUP_STATUS_NONE && status != SOUP_STATUS_CANCELLED) { + if (!body || status != SOUP_STATUS_OK) { + /* Iff status is SOUP_STATUS_NONE, child_error has already been set */ + _gdata_service_parse_soup_error (priv->service, &child_error); + if (body && (status != SOUP_STATUS_NONE)) { /* Error */ GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (priv->service); g_assert (klass->parse_error_response != NULL); @@ -729,16 +765,74 @@ error: } static void -run_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) +run_cb (GObject *object, GAsyncResult *result, gpointer user_data) { - GDataBatchOperation *operation = GDATA_BATCH_OPERATION (source_object); - g_autoptr(GError) error = NULL; - - /* Run the batch operation and return */ - if (!gdata_batch_operation_run (operation, cancellable, &error)) - g_task_return_error (task, g_steal_pointer (&error)); - else - g_task_return_boolean (task, TRUE); + GTask *task = user_data; + GDataBatchOperation *self = g_task_get_task_data (task); + GDataBatchOperationPrivate *priv = self->priv; + GBytes *body; + GDataFeed *feed; + GError *error = NULL; + guint status; + GHashTableIter iter; + gpointer op_id; + BatchOperation *op; + SoupMessage *message = priv->message; + + /* Check for early cancellation. */ + if (g_cancellable_set_error_if_cancelled (g_task_get_cancellable (task), &error)) { + goto error; + } + + /* Ensure that this GDataBatchOperation can't be run again */ + priv->has_run = TRUE; + + /* Send the message */ + body = _gdata_service_send_finish (priv->service, result, &error); + status = soup_message_get_status (message); + + if (!body || status != SOUP_STATUS_OK) { + /* Iff status is SOUP_STATUS_NONE, child_error has already been set */ + _gdata_service_parse_soup_error (priv->service, &error); + if (body && (status != SOUP_STATUS_NONE)) { + /* Error */ + GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (priv->service); + g_assert (klass->parse_error_response != NULL); + klass->parse_error_response (priv->service, GDATA_OPERATION_BATCH, status, + soup_message_get_reason_phrase (message), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), &error); + } + g_clear_pointer (&body, g_bytes_unref); + goto error; + } + + /* Parse the XML; GDataBatchFeed will fire off the relevant callbacks */ + g_assert (body && g_bytes_get_data (body, NULL)); + feed = GDATA_FEED (_gdata_parsable_new_from_xml (GDATA_TYPE_BATCH_FEED, + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), + self, &error)); + g_bytes_unref (body); + + if (!feed) + goto error; + + g_task_return_boolean (task, TRUE); + g_object_unref (task); + + return; + +error: + g_object_unref (message); + + /* Call the callbacks for each of our operations to notify them of the error */ + g_hash_table_iter_init (&iter, priv->operations); + while (g_hash_table_iter_next (&iter, &op_id, (gpointer*) &op) == TRUE) + _gdata_batch_operation_run_callback (self, op, NULL, g_error_copy (error)); + + g_task_return_error (task, error); + g_object_unref (task); } /** @@ -762,7 +856,9 @@ run_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellabl void gdata_batch_operation_run_async (GDataBatchOperation *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { - g_autoptr(GTask) task = NULL; + GDataBatchOperationPrivate *priv = self->priv; + GTask *task = NULL; + GError *error = NULL; g_return_if_fail (GDATA_IS_BATCH_OPERATION (self)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); @@ -773,7 +869,17 @@ gdata_batch_operation_run_async (GDataBatchOperation *self, GCancellable *cancel task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, gdata_batch_operation_run_async); - g_task_run_in_thread (task, run_thread); + g_task_set_task_data (task, self, NULL); + + priv->message = batch_message_build (self, &error); + + if (!priv->message) { + g_task_return_error (task, error); + return; + } + + _gdata_service_send_async (priv->service, priv->message, + g_task_get_cancellable (task), run_cb, task); } /** -- GitLab From 462f365775d15a9fe035ec3776674700f60e5e27 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:13 +0100 Subject: [PATCH 11/45] soup3: remove obsolete comment in gdata-calendar-query This properaty no longer exists. --- gdata/services/calendar/gdata-calendar-query.c | 1 - 1 file changed, 1 deletion(-) diff --git a/gdata/services/calendar/gdata-calendar-query.c b/gdata/services/calendar/gdata-calendar-query.c index 68c02cdf..5a957c36 100644 --- a/gdata/services/calendar/gdata-calendar-query.c +++ b/gdata/services/calendar/gdata-calendar-query.c @@ -371,7 +371,6 @@ get_query_uri (GDataQuery *self, const gchar *feed_uri, GString *query_uri, gboo order_by_to_v3 (priv->order_by), NULL, FALSE); } - /* Convert the deprecated recurrence-expansion-* properties into single-events. */ APPEND_SEP if (priv->single_events == TRUE) g_string_append (query_uri, "singleEvents=true"); -- GitLab From a4ab30fedfb311b53e68a04dfe186a94f55238e7 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:14 +0100 Subject: [PATCH 12/45] soup3: implement proper async for gdata-documents-entry --- .../documents/gdata-documents-entry.c | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/gdata/services/documents/gdata-documents-entry.c b/gdata/services/documents/gdata-documents-entry.c index b502253c..167e1d99 100644 --- a/gdata/services/documents/gdata-documents-entry.c +++ b/gdata/services/documents/gdata-documents-entry.c @@ -322,6 +322,20 @@ get_authorization_domain (GDataAccessHandler *self) return gdata_documents_service_get_primary_authorization_domain (); } +typedef struct { + SoupMessage *message; + GDataQueryProgressCallback progress_callback; + gpointer progress_user_data; + GDestroyNotify destroy_progress_user_data; +} GetRulesAsyncData; + +static void +get_rules_async_data_free (GetRulesAsyncData *self) +{ + g_clear_object (&self->message); + g_slice_free (GetRulesAsyncData, self); +} + static GDataFeed * get_rules (GDataAccessHandler *self, GDataService *service, @@ -363,12 +377,85 @@ get_rules (GDataAccessHandler *self, return feed; } +static void +get_rules_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) +{ + GDataService *service = GDATA_SERVICE (object); + GTask *task = user_data; + GetRulesAsyncData *data = g_task_get_task_data (task); + GDataFeed *feed; + GBytes *body; + GError *error = NULL; + + body = _gdata_service_send_finish (service, result, &error); + + if (!_gdata_service_query_async_check (service, task, data->message, body, &error, data->destroy_progress_user_data, data->progress_user_data)) + return; + + feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, g_bytes_get_data (body, NULL), + g_bytes_get_size (body), + GDATA_TYPE_DOCUMENTS_ACCESS_RULE, + data->progress_callback, data->progress_user_data, &error); + + g_bytes_unref (body); + + if (data->destroy_progress_user_data != NULL) { + data->destroy_progress_user_data (data->progress_user_data); + } + + if (error) + g_task_return_error (task, error); + else + g_task_return_pointer (task, feed, g_object_unref); + + g_object_unref (task); +} + +static void +get_rules_async (GDataAccessHandler *self, + GDataService *service, + GCancellable *cancellable, + GDataQueryProgressCallback progress_callback, + gpointer progress_user_data, + GDestroyNotify destroy_progress_user_data, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task = NULL; + GDataAccessHandlerIface *iface; + GDataAuthorizationDomain *domain = NULL; + GDataLink *_link; + GetRulesAsyncData *data; + + _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); + } + + data = g_slice_new (GetRulesAsyncData); + data->message = NULL; + data->progress_callback = progress_callback; + data->progress_user_data = progress_user_data; + data->destroy_progress_user_data = destroy_progress_user_data; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, gdata_service_query_async); + g_task_set_task_data (task, data, (GDestroyNotify) get_rules_async_data_free); + + _gdata_service_query_async (service, domain, gdata_link_get_uri (_link), + NULL, get_rules_async_cb, task, &data->message); +} + 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; + iface->get_rules_async = get_rules_async; } static void -- GitLab From fbd2eb5d88b550fcd7522a3f98d8325c80d914fe Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:14 +0100 Subject: [PATCH 13/45] soup3: implement proper async for oauth2-authorizer --- gdata/gdata-oauth2-authorizer.c | 356 ++++++++++++++++++++++---------- 1 file changed, 252 insertions(+), 104 deletions(-) diff --git a/gdata/gdata-oauth2-authorizer.c b/gdata/gdata-oauth2-authorizer.c index 73d5868c..809ff7cf 100644 --- a/gdata/gdata-oauth2-authorizer.c +++ b/gdata/gdata-oauth2-authorizer.c @@ -165,6 +165,13 @@ static gboolean is_authorized_for_domain (GDataAuthorizer *self, static gboolean refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, GError **error); +static void refresh_authorization_async (GDataAuthorizer *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static gboolean refresh_authorization_finish (GDataAuthorizer *self, + GAsyncResult *result, + GError **error); static void parse_grant_response (GDataOAuth2Authorizer *self, guint status, const gchar *reason_phrase, @@ -187,9 +194,6 @@ struct _GDataOAuth2AuthorizerPrivate { gchar *client_secret; /* owned */ gchar *locale; /* owned */ - /* Mutex for access_token, refresh_token and authentication_domains. */ - GMutex mutex; - /* These are both non-NULL when authorised. refresh_token may be * non-NULL if access_token is NULL and refresh_authorization() has not * yet been called on this authorizer. They may be both NULL. */ @@ -390,12 +394,9 @@ authorizer_init (GDataAuthorizerInterface *iface) iface->process_request = process_request; iface->is_authorized_for_domain = is_authorized_for_domain; - /* We only implement the synchronous version, as GDataAuthorizer will - * automatically wrap it in a thread for the asynchronous versions if - * they’re not specifically implemented, which is fine for our needs. We - * couldn’t do any better by implementing the asynchronous versions - * ourselves. */ iface->refresh_authorization = refresh_authorization; + iface->refresh_authorization_async = refresh_authorization_async; + iface->refresh_authorization_finish = refresh_authorization_finish; } static void @@ -403,8 +404,6 @@ gdata_oauth2_authorizer_init (GDataOAuth2Authorizer *self) { self->priv = gdata_oauth2_authorizer_get_instance_private (self); - /* Set up the authorizer's mutex */ - g_mutex_init (&self->priv->mutex); self->priv->authentication_domains = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, @@ -453,7 +452,6 @@ finalize (GObject *object) g_free (priv->refresh_token); g_hash_table_unref (priv->authentication_domains); - g_mutex_clear (&priv->mutex); /* Chain up to the parent class */ G_OBJECT_CLASS (gdata_oauth2_authorizer_parent_class)->finalize (object); @@ -491,9 +489,7 @@ get_property (GObject *object, guint property_id, GValue *value, gdata_oauth2_authorizer_get_proxy_resolver (self)); break; case PROP_REFRESH_TOKEN: - g_mutex_lock (&priv->mutex); g_value_set_string (value, priv->refresh_token); - g_mutex_unlock (&priv->mutex); break; default: /* We don't have any other property... */ @@ -555,9 +551,6 @@ process_request (GDataAuthorizer *self, GDataAuthorizationDomain *domain, priv = GDATA_OAUTH2_AUTHORIZER (self)->priv; - /* Set the authorisation header */ - g_mutex_lock (&priv->mutex); - /* Sanity check */ g_assert ((priv->access_token == NULL) || (priv->refresh_token != NULL)); @@ -568,8 +561,6 @@ process_request (GDataAuthorizer *self, GDataAuthorizationDomain *domain, sign_message_locked (GDATA_OAUTH2_AUTHORIZER (self), message, priv->access_token); } - - g_mutex_unlock (&priv->mutex); } static gboolean @@ -582,10 +573,8 @@ is_authorized_for_domain (GDataAuthorizer *self, priv = GDATA_OAUTH2_AUTHORIZER (self)->priv; - g_mutex_lock (&priv->mutex); access_token = priv->access_token; result = g_hash_table_lookup (priv->authentication_domains, domain); - g_mutex_unlock (&priv->mutex); /* Sanity check */ g_assert (result == NULL || result == domain); @@ -593,17 +582,28 @@ is_authorized_for_domain (GDataAuthorizer *self, return (access_token != NULL && result != NULL); } +static void +authorizer_message_starting (SoupMessage *msg, gpointer body) +{ + soup_message_set_request_body_from_bytes (msg, "application/x-www-form-urlencoded", body); +} + +static void +authorizer_message_finished (SoupMessage *msg, gpointer body) +{ + g_bytes_unref (body); +} + /* Sign the message and add the Authorization header to it containing the * signature. * * Reference: https://developers.google.com/accounts/docs/OAuth2InstalledApp#callinganapi - * - * NOTE: This must be called with the mutex locked. */ + */ static void sign_message_locked (GDataOAuth2Authorizer *self, SoupMessage *message, const gchar *access_token) { - SoupURI *message_uri; /* unowned */ + GUri *message_uri; /* unowned */ gchar *auth_header = NULL; /* owned */ g_return_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self)); @@ -617,7 +617,7 @@ sign_message_locked (GDataOAuth2Authorizer *self, SoupMessage *message, * bad thing to happen. */ message_uri = soup_message_get_uri (message); - if (message_uri->scheme != SOUP_URI_SCHEME_HTTPS) { + if (strcmp (g_uri_get_scheme (message_uri), "https")) { g_warning ("Not authorizing a non-HTTPS message with the " "user’s OAuth 2.0 access token as the connection " "isn’t secure."); @@ -631,6 +631,44 @@ sign_message_locked (GDataOAuth2Authorizer *self, SoupMessage *message, g_free (auth_header); } +static SoupMessage +*build_authorization_message (GDataAuthorizer *self) +{ + GDataOAuth2AuthorizerPrivate *priv; + SoupMessage *message = NULL; /* owned */ + GUri *_uri = NULL; /* owned */ + gchar *request_body; + GBytes *body; + + priv = GDATA_OAUTH2_AUTHORIZER (self)->priv; + + /* Prepare the request */ + request_body = soup_form_encode ("client_id", priv->client_id, + "client_secret", priv->client_secret, + "refresh_token", priv->refresh_token, + "grant_type", "refresh_token", + NULL); + + /* Build the message */ + _uri = g_uri_build (SOUP_HTTP_URI_FLAGS, + "https", NULL, "accounts.google.com", + _gdata_service_get_https_port (), + "/o/oauth2/token", NULL, NULL); + message = soup_message_new_from_uri (SOUP_METHOD_POST, _uri); + g_uri_unref (_uri); + + g_signal_connect (message, "accept-certificate", + G_CALLBACK (_gdata_service_accept_certificate_cb), + priv->session); + + body = g_bytes_new_take (request_body, strlen (request_body)); + + g_signal_connect (message, "starting", G_CALLBACK (authorizer_message_starting), body); + g_signal_connect (message, "finished", G_CALLBACK (authorizer_message_finished), body); + + return message; +} + static gboolean refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, GError **error) @@ -638,8 +676,6 @@ refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, /* See http://code.google.com/apis/accounts/docs/OAuth2.html#IAMoreToken */ GDataOAuth2AuthorizerPrivate *priv; SoupMessage *message = NULL; /* owned */ - SoupURI *_uri = NULL; /* owned */ - gchar *request_body; guint status; GError *child_error = NULL; GBytes *body; @@ -648,41 +684,20 @@ refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, priv = GDATA_OAUTH2_AUTHORIZER (self)->priv; - g_mutex_lock (&priv->mutex); - /* If we don’t have a refresh token, we can’t refresh the * authorisation. Do not set @error, as we haven’t been successfully * authorised previously. */ if (priv->refresh_token == NULL) { - g_mutex_unlock (&priv->mutex); return FALSE; } - /* Prepare the request */ - request_body = soup_form_encode ("client_id", priv->client_id, - "client_secret", priv->client_secret, - "refresh_token", priv->refresh_token, - "grant_type", "refresh_token", - NULL); - - g_mutex_unlock (&priv->mutex); - - /* Build the message */ - _uri = soup_uri_new ("https://accounts.google.com/o/oauth2/token"); - soup_uri_set_port (_uri, _gdata_service_get_https_port ()); - message = soup_message_new_from_uri (SOUP_METHOD_POST, _uri); - soup_uri_free (_uri); - - soup_message_set_request (message, "application/x-www-form-urlencoded", - SOUP_MEMORY_TAKE, request_body, - strlen (request_body)); + message = build_authorization_message (self); /* Send the message */ body = soup_session_send_and_read (priv->session, message, cancellable, error); status = soup_message_get_status (message); - if (status == SOUP_STATUS_CANCELLED) { - /* Cancelled (the error has already been set) */ + if (!body) { g_object_unref (message); return FALSE; } else if (status != SOUP_STATUS_OK) { @@ -714,6 +729,88 @@ refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, return TRUE; } +static void +refresh_authorization_cb (GObject *object, GAsyncResult *result, gpointer user_data) +{ + SoupSession *session = SOUP_SESSION (object); + SoupMessage *message = soup_session_get_async_result_message (session, result); + GTask *task = user_data; + ;GDataAuthorizer *self = g_task_get_task_data (task); + GBytes *body; + guint status; + GError *error = NULL; + + body = soup_session_send_and_read_finish (session, result, &error); + status = soup_message_get_status (message); + + if (!body) { + g_object_unref (message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } else if (status != SOUP_STATUS_OK) { + parse_grant_error (GDATA_OAUTH2_AUTHORIZER (self), + status, soup_message_get_reason_phrase (message), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), + &error); + g_bytes_unref (body); + g_object_unref (message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Parse and handle the response */ + parse_grant_response (GDATA_OAUTH2_AUTHORIZER (self), + status, soup_message_get_reason_phrase (message), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), &error); + + g_bytes_unref (body); + g_object_unref (message); + + if (error != NULL) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +refresh_authorization_async (GDataAuthorizer *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) +{ + GDataOAuth2AuthorizerPrivate *priv; + GTask *task = NULL; + + priv = GDATA_OAUTH2_AUTHORIZER (self)->priv; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, refresh_authorization_async); + g_task_set_task_data (task, g_object_ref (self), g_object_unref); + + if (priv->refresh_token == NULL) { + g_task_return_boolean (task, FALSE); + g_object_unref (task); + return; + } + + soup_session_send_and_read_async (priv->session, build_authorization_message (self), G_PRIORITY_DEFAULT, g_task_get_cancellable (task), refresh_authorization_cb, task); +} + +static gboolean +refresh_authorization_finish (GDataAuthorizer *self, GAsyncResult *result, GError **error) +{ + g_assert (GDATA_IS_OAUTH2_AUTHORIZER (self)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (error == NULL || *error == NULL); + g_assert (g_task_is_valid (result, self)); + g_assert (g_async_result_is_tagged (result, refresh_authorization_async)); + + return g_task_propagate_boolean (G_TASK (result), error); +} + /** * gdata_oauth2_authorizer_new: * @client_id: your application’s client ID @@ -804,8 +901,6 @@ gdata_oauth2_authorizer_new_for_authorization_domains (const gchar *client_id, g_return_val_if_fail (GDATA_IS_AUTHORIZATION_DOMAIN (i->data), NULL); - /* We don’t have to lock the authoriser’s mutex here as no other - * code has seen the authoriser yet */ domain = GDATA_AUTHORIZATION_DOMAIN (i->data); g_hash_table_insert (authorizer->priv->authentication_domains, g_object_ref (domain), domain); @@ -864,8 +959,6 @@ gdata_oauth2_authorizer_build_authentication_uri (GDataOAuth2Authorizer *self, priv = self->priv; - g_mutex_lock (&priv->mutex); - /* Build and memoise the URI. * * Reference: https://developers.google.com/accounts/docs/OAuth2InstalledApp#formingtheurl @@ -913,15 +1006,10 @@ gdata_oauth2_authorizer_build_authentication_uri (GDataOAuth2Authorizer *self, g_string_append (uri, "&include_granted_scopes=false"); } - g_mutex_unlock (&priv->mutex); - return g_string_free (uri, FALSE); } -/* NOTE: This has to be thread safe, as it can be called from - * refresh_authorization() at any time. - * - * Reference: https://developers.google.com/accounts/docs/OAuth2InstalledApp#handlingtheresponse +/* Reference: https://developers.google.com/accounts/docs/OAuth2InstalledApp#handlingtheresponse */ static void parse_grant_response (GDataOAuth2Authorizer *self, guint status, @@ -1006,9 +1094,6 @@ done: g_assert ((refresh_token == NULL) || (access_token != NULL)); g_assert ((child_error != NULL) == (access_token == NULL)); - /* Update state. */ - g_mutex_lock (&priv->mutex); - g_free (priv->access_token); priv->access_token = g_strdup (access_token); @@ -1017,8 +1102,6 @@ done: priv->refresh_token = g_strdup (refresh_token); } - g_mutex_unlock (&priv->mutex); - if (child_error != NULL) { g_propagate_error (error, child_error); } @@ -1026,10 +1109,7 @@ done: g_object_unref (parser); } -/* NOTE: This has to be thread safe, as it can be called from - * refresh_authorization() at any time. - * - * There is no reference for this, because Google apparently don’t deem it +/* There is no reference for this, because Google apparently don’t deem it * necessary to document. * * Example response: @@ -1156,7 +1236,7 @@ gdata_oauth2_authorizer_request_authorization (GDataOAuth2Authorizer *self, { GDataOAuth2AuthorizerPrivate *priv; SoupMessage *message = NULL; /* owned */ - SoupURI *_uri = NULL; /* owned */ + GUri *_uri = NULL; /* owned */ gchar *request_body = NULL; /* owned */ guint status; GError *child_error = NULL; @@ -1183,21 +1263,29 @@ gdata_oauth2_authorizer_request_authorization (GDataOAuth2Authorizer *self, NULL); /* Build the message */ - _uri = soup_uri_new ("https://accounts.google.com/o/oauth2/token"); - soup_uri_set_port (_uri, _gdata_service_get_https_port ()); + _uri = g_uri_build (SOUP_HTTP_URI_FLAGS, + "https", NULL, "accounts.google.com", + _gdata_service_get_https_port (), + "/o/oauth2/token", NULL, NULL); message = soup_message_new_from_uri (SOUP_METHOD_POST, _uri); - soup_uri_free (_uri); + g_uri_unref (_uri); + + g_signal_connect (message, "accept-certificate", + G_CALLBACK (_gdata_service_accept_certificate_cb), + priv->session); + + body = g_bytes_new_take (request_body, strlen (request_body)); + + g_signal_connect (message, "starting", G_CALLBACK (authorizer_message_starting), body); + g_signal_connect (message, "finished", G_CALLBACK (authorizer_message_finished), body); - soup_message_set_request (message, "application/x-www-form-urlencoded", - SOUP_MEMORY_TAKE, request_body, - strlen (request_body)); request_body = NULL; /* Send the message */ body = soup_session_send_and_read (priv->session, message, cancellable, error); status = soup_message_get_status (message); - if (status == SOUP_STATUS_CANCELLED) { + if (!body) { /* Cancelled (the error has already been set) */ g_object_unref (message); return FALSE; @@ -1229,22 +1317,57 @@ gdata_oauth2_authorizer_request_authorization (GDataOAuth2Authorizer *self, } static void -request_authorization_thread (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) +request_authorization_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) { - GDataOAuth2Authorizer *authorizer = GDATA_OAUTH2_AUTHORIZER (source_object); - g_autoptr(GError) error = NULL; - const gchar *authorization_code = task_data; - - if (!gdata_oauth2_authorizer_request_authorization (authorizer, - authorization_code, - cancellable, - &error)) - g_task_return_error (task, g_steal_pointer (&error)); - else - g_task_return_boolean (task, TRUE); + SoupSession *session = SOUP_SESSION (object); + GTask *task = user_data; + GBytes *body; + GError *error = NULL; + SoupMessage *message = soup_session_get_async_result_message (session, result); + GDataOAuth2Authorizer *self = g_task_get_task_data (task); + guint status; + + body = soup_session_send_and_read_finish (session, result, &error); + status = soup_message_get_status (message); + + if (!body) { + /* Cancelled (the error has already been set) */ + g_object_unref (message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } else if (status != SOUP_STATUS_OK) { + parse_grant_error (self, status, soup_message_get_reason_phrase (message), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), + &error); + g_bytes_unref (body); + g_object_unref (message); + if (error) { + g_task_return_error (task, error); + } else { + g_task_return_boolean (task, FALSE); + } + g_object_unref (task); + return; + } + + /* Parse and handle the response */ + parse_grant_response (self, status, soup_message_get_reason_phrase (message), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), &error); + + g_bytes_unref (body); + g_object_unref (message); + + if (error != NULL) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); } /** @@ -1266,7 +1389,11 @@ gdata_oauth2_authorizer_request_authorization_async (GDataOAuth2Authorizer *self GAsyncReadyCallback callback, gpointer user_data) { - g_autoptr(GTask) task = NULL; + GTask *task = NULL; + SoupMessage *message = NULL; + GUri *_uri = NULL; /* owned */ + gchar *request_body = NULL; /* owned */ + GBytes *body = NULL; g_return_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self)); g_return_if_fail (authorization_code != NULL && @@ -1276,9 +1403,37 @@ gdata_oauth2_authorizer_request_authorization_async (GDataOAuth2Authorizer *self task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, gdata_oauth2_authorizer_request_authorization_async); - g_task_set_task_data (task, g_strdup (authorization_code), - (GDestroyNotify) g_free); - g_task_run_in_thread (task, request_authorization_thread); + g_task_set_task_data (task, g_object_ref (self), g_object_unref); + + request_body = soup_form_encode ("client_id", self->priv->client_id, + "client_secret", self->priv->client_secret, + "code", authorization_code, + "redirect_uri", self->priv->redirect_uri, + "grant_type", "authorization_code", + NULL); + + /* Build the message */ + _uri = g_uri_build (SOUP_HTTP_URI_FLAGS, + "https", NULL, "accounts.google.com", + _gdata_service_get_https_port (), + "/o/oauth2/token", NULL, NULL); + message = soup_message_new_from_uri (SOUP_METHOD_POST, _uri); + g_uri_unref (_uri); + + g_signal_connect (message, "accept-certificate", + G_CALLBACK (_gdata_service_accept_certificate_cb), + self->priv->session); + + body = g_bytes_new_take (request_body, strlen (request_body)); + + g_signal_connect (message, "starting", G_CALLBACK (authorizer_message_starting), body); + g_signal_connect (message, "finished", G_CALLBACK (authorizer_message_finished), body); + + soup_session_send_and_read_async (self->priv->session, message, + G_PRIORITY_DEFAULT, + g_task_get_cancellable (task), + request_authorization_async_cb, + task); } /** @@ -1384,9 +1539,7 @@ gdata_oauth2_authorizer_dup_refresh_token (GDataOAuth2Authorizer *self) priv = self->priv; - g_mutex_lock (&priv->mutex); refresh_token = g_strdup (priv->refresh_token); - g_mutex_unlock (&priv->mutex); return refresh_token; } @@ -1413,10 +1566,7 @@ gdata_oauth2_authorizer_set_refresh_token (GDataOAuth2Authorizer *self, priv = self->priv; - g_mutex_lock (&priv->mutex); - if (g_strcmp0 (priv->refresh_token, refresh_token) == 0) { - g_mutex_unlock (&priv->mutex); return; } @@ -1430,8 +1580,6 @@ gdata_oauth2_authorizer_set_refresh_token (GDataOAuth2Authorizer *self, g_free (priv->refresh_token); priv->refresh_token = g_strdup (refresh_token); - g_mutex_unlock (&priv->mutex); - g_object_notify (G_OBJECT (self), "refresh-token"); } @@ -1511,7 +1659,7 @@ gdata_oauth2_authorizer_get_timeout (GDataOAuth2Authorizer *self) g_return_val_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self), 0); g_object_get (self->priv->session, - SOUP_SESSION_TIMEOUT, &timeout, + "timeout", &timeout, NULL); return timeout; @@ -1539,7 +1687,7 @@ gdata_oauth2_authorizer_set_timeout (GDataOAuth2Authorizer *self, guint timeout) return; } - g_object_set (self->priv->session, SOUP_SESSION_TIMEOUT, timeout, NULL); + g_object_set (self->priv->session, "timeout", timeout, NULL); } /** -- GitLab From aa90ae56c4fffae0aa98d0eb4089b12a4f68d17b Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:15 +0100 Subject: [PATCH 14/45] soup3: port gdata-download-stream --- gdata/gdata-download-stream.c | 731 ++++++++++++++++++---------------- 1 file changed, 377 insertions(+), 354 deletions(-) diff --git a/gdata/gdata-download-stream.c b/gdata/gdata-download-stream.c index b9870ed2..ce31a8df 100644 --- a/gdata/gdata-download-stream.c +++ b/gdata/gdata-download-stream.c @@ -105,7 +105,6 @@ #include #include "gdata-download-stream.h" -#include "gdata-buffer.h" #include "gdata-private.h" static void gdata_download_stream_seekable_iface_init (GSeekableIface *seekable_iface); @@ -118,35 +117,33 @@ static void gdata_download_stream_set_property (GObject *object, guint property_ static gssize gdata_download_stream_read (GInputStream *stream, void *buffer, gsize count, GCancellable *cancellable, GError **error); static gboolean gdata_download_stream_close (GInputStream *stream, GCancellable *cancellable, GError **error); +static void gdata_download_stream_read_async (GInputStream *stream, void *buffer, gsize count, int io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); +static gssize gdata_download_stream_read_finish (GInputStream *stream, GAsyncResult *result, GError **error); +static void gdata_download_stream_close_async (GInputStream *stream, int io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); +static gboolean gdata_download_stream_close_finish (GInputStream *stream, GAsyncResult *result, GError **error); + static goffset gdata_download_stream_tell (GSeekable *seekable); static gboolean gdata_download_stream_can_seek (GSeekable *seekable); static gboolean gdata_download_stream_seek (GSeekable *seekable, goffset offset, GSeekType type, GCancellable *cancellable, GError **error); static gboolean gdata_download_stream_can_truncate (GSeekable *seekable); static gboolean gdata_download_stream_truncate (GSeekable *seekable, goffset offset, GCancellable *cancellable, GError **error); -static void create_network_thread (GDataDownloadStream *self, GError **error); -static void reset_network_thread (GDataDownloadStream *self); - /* * The GDataDownloadStream can be in one of several states: - * 1. Pre-network activity. This is the state that the stream is created in. @network_thread and @cancellable are both %NULL, and @finished is %FALSE. + * 1. Pre-network activity. This is the state that the stream is created in. @body_stream and @cancellable are both %NULL. * The stream will remain in this state until gdata_download_stream_read() or gdata_download_stream_seek() are called for the first time. * @content_type and @content_length are at their default values (NULL and -1, respectively). - * 2. Network activity. This state is entered when gdata_download_stream_read() is called for the first time. - * @network_thread, @buffer and @cancellable are created, while @finished remains %FALSE. - * As soon as the headers are downloaded, which is guaranteed to be before the first call to gdata_download_stream_read() returns, @content_type - * and @content_length are set from the headers. From this point onwards, they are immutable. + * 2. Network activity. This state is entered when gdata_download_stream_read() is called for the first time. @body_stream and @cancellable + * are created. As soon as the headers are downloaded, which is guaranteed to be before the first call to gdata_download_stream_read() returns, + * @content_type and @content_length are set from the headers. From this point onwards, they are immutable. * 3. Reset network activity. This state is entered only if case 3 is encountered in a call to gdata_download_stream_seek(): a seek to an offset which - * has already been read out of the buffer. In this state, @buffer is freed and set to %NULL, @network_thread is cancelled (then set to %NULL), - * and @offset is set to the seeked-to offset. @finished remains at %FALSE. + * has already been read out of @body_stream. In this state, @body_stream is freed and set to %NULL and @offset is set to the seeked-to offset. * When the next call to gdata_download_stream_read() is made, the download stream will go back to state 2 as if this was the first call to * gdata_download_stream_read(). - * 4. Post-network activity. This state is reached once the download thread finishes downloading, due to having downloaded everything. - * @buffer is non-%NULL, @network_thread is non-%NULL, but meaningless; @cancellable is still a valid #GCancellable instance; and @finished is set - * to %TRUE. At the same time, @finished_cond is signalled. - * This state can be exited either by making a call to gdata_download_stream_seek(), in which case the stream will go back to state 3; or by - * calling gdata_download_stream_close(), in which case the stream will return errors for all operations, as the underlying %GInputStream will be - * marked as closed. + * 4. Post-network activity. This state is reached once all the I/O has finished, having downloaded everything. The @cancellable is still a valid + * #GCancellable instance (it's disposed of when the stream is closed). You can exit this state by seeking (in which case it goes back to 3) + * or by calling gdata_download_stream_close(), n which case the stream will return errors for all operations, as the underlying #GInputStream + * will be marked as closed. */ struct _GDataDownloadStreamPrivate { gchar *download_uri; @@ -154,22 +151,24 @@ struct _GDataDownloadStreamPrivate { GDataAuthorizationDomain *authorization_domain; SoupSession *session; SoupMessage *message; - GDataBuffer *buffer; + GInputStream *body_stream; goffset offset; /* current position in the stream */ - GThread *network_thread; GCancellable *cancellable; GCancellable *network_cancellable; /* see the comment in gdata_download_stream_constructor() about the relationship between these two */ gulong network_cancellable_id; - gboolean finished; - GCond finished_cond; - GMutex finished_mutex; /* mutex for ->finished, protected by ->finished_cond */ - /* Cached data from the SoupMessage */ gchar *content_type; gssize content_length; - GMutex content_mutex; /* mutex to protect them */ + + /* data stored mainly for async */ + GCancellable *cb_cancellable; + GCancellable *child_cancellable; + gulong global_cancelled_signal; + gulong cancelled_signal; + gpointer buffer; + gsize count; }; enum { @@ -197,10 +196,12 @@ gdata_download_stream_class_init (GDataDownloadStreamClass *klass) gobject_class->get_property = gdata_download_stream_get_property; gobject_class->set_property = gdata_download_stream_set_property; - /* We use the default implementations of the async functions, which just run - * our implementation of the sync function in a thread. */ stream_class->read_fn = gdata_download_stream_read; stream_class->close_fn = gdata_download_stream_close; + stream_class->read_async = gdata_download_stream_read_async; + stream_class->read_finish = gdata_download_stream_read_finish; + stream_class->close_async = gdata_download_stream_close_async; + stream_class->close_finish = gdata_download_stream_close_finish; /** * GDataDownloadStream:service: @@ -248,10 +249,6 @@ gdata_download_stream_class_init (GDataDownloadStreamClass *klass) * The content type of the file being downloaded. This will initially be %NULL, and will be populated as soon as the appropriate header is * received from the server. Its value will never change after this. * - * Note that change notifications for this property (#GObject::notify emissions) may be emitted in threads other than the one which created - * the #GDataDownloadStream. It is the client's responsibility to ensure that any notification signal handlers are either multi-thread safe - * or marshal the notification to the thread which owns the #GDataDownloadStream as appropriate. - * * Since: 0.5.0 */ g_object_class_install_property (gobject_class, PROP_CONTENT_TYPE, @@ -266,10 +263,6 @@ gdata_download_stream_class_init (GDataDownloadStreamClass *klass) * The length (in bytes) of the file being downloaded. This will initially be -1, and will be populated as soon as * the appropriate header is received from the server. Its value will never change after this. * - * Note that change notifications for this property (#GObject::notify emissions) may be emitted in threads other than the one which created - * the #GDataDownloadStream. It is the client's responsibility to ensure that any notification signal handlers are either multi-thread safe - * or marshal the notification to the thread which owns the #GDataDownloadStream as appropriate. - * * Since: 0.5.0 */ g_object_class_install_property (gobject_class, PROP_CONTENT_LENGTH, @@ -314,15 +307,8 @@ static void gdata_download_stream_init (GDataDownloadStream *self) { self->priv = gdata_download_stream_get_instance_private (self); - self->priv->buffer = NULL; /* created when the network thread is started and destroyed when the stream is closed */ - - self->priv->finished = FALSE; - g_cond_init (&(self->priv->finished_cond)); - g_mutex_init (&(self->priv->finished_mutex)); - self->priv->content_type = NULL; self->priv->content_length = -1; - g_mutex_init (&(self->priv->content_mutex)); } static void @@ -331,13 +317,41 @@ cancellable_cancel_cb (GCancellable *cancellable, GCancellable *network_cancella g_cancellable_cancel (network_cancellable); } +static void +got_headers_cb (SoupMessage *message, GDataDownloadStream *self) +{ + goffset end; + goffset start; + goffset total_length; + SoupMessageHeaders *response_headers; + + /* Don't get the client's hopes up by setting the Content-Type or -Length if the response + * is actually unsuccessful. */ + if (SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (message)) == FALSE) + return; + + response_headers = soup_message_get_response_headers (message); + g_clear_pointer (&self->priv->content_type, g_free); + self->priv->content_type = g_strdup (soup_message_headers_get_content_type (response_headers, NULL)); + self->priv->content_length = soup_message_headers_get_content_length (response_headers); + if (soup_message_headers_get_content_range (response_headers, &start, &end, &total_length)) { + self->priv->content_length = (gssize) total_length; + } + + /* Emit the notifications for the Content-Length and -Type properties */ + g_object_freeze_notify (G_OBJECT (self)); + g_object_notify (G_OBJECT (self), "content-length"); + g_object_notify (G_OBJECT (self), "content-type"); + g_object_thaw_notify (G_OBJECT (self)); +} + static GObject * gdata_download_stream_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params) { GDataDownloadStreamPrivate *priv; GDataServiceClass *klass; GObject *object; - SoupURI *_uri; + GUri *_uri, *_uri_parsed; /* Chain up to the parent class */ object = G_OBJECT_CLASS (gdata_download_stream_parent_class)->constructor (type, n_construct_params, construct_params); @@ -355,11 +369,15 @@ gdata_download_stream_constructor (GType type, guint n_construct_params, GObject priv->network_cancellable_id = g_cancellable_connect (priv->cancellable, (GCallback) cancellable_cancel_cb, priv->network_cancellable, NULL); /* Build the message. The URI must be HTTPS. */ - _uri = soup_uri_new (priv->download_uri); - soup_uri_set_port (_uri, _gdata_service_get_https_port ()); - g_assert_cmpstr (soup_uri_get_scheme (_uri), ==, SOUP_URI_SCHEME_HTTPS); - priv->message = soup_message_new_from_uri (SOUP_METHOD_GET, _uri); - soup_uri_free (_uri); + _uri_parsed = g_uri_parse (priv->download_uri, SOUP_HTTP_URI_FLAGS, NULL); + _uri = soup_uri_copy (_uri_parsed, SOUP_URI_PORT, + _gdata_service_get_https_port (), SOUP_URI_NONE); + g_uri_unref (_uri_parsed); + g_assert_cmpstr (g_uri_get_scheme (_uri), ==, "https"); + priv->message = _gdata_service_new_message_from_uri (priv->service, SOUP_METHOD_GET, _uri); + g_uri_unref (_uri); + + g_signal_connect (priv->message, "got-headers", G_CALLBACK (got_headers_cb), object); /* Make sure the headers are set */ klass = GDATA_SERVICE_GET_CLASS (priv->service); @@ -367,9 +385,6 @@ gdata_download_stream_constructor (GType type, guint n_construct_params, GObject klass->append_query_headers (priv->service, priv->authorization_domain, priv->message); } - /* We don't want to accumulate chunks */ - soup_message_body_set_accumulate (priv->message->request_body, FALSE); - /* Downloading doesn't actually start until the first call to read() */ return object; @@ -394,21 +409,11 @@ gdata_download_stream_dispose (GObject *object) priv->network_cancellable_id = 0; priv->cancellable = NULL; - if (priv->network_cancellable != NULL) - g_object_unref (priv->network_cancellable); - priv->network_cancellable = NULL; - - if (priv->authorization_domain != NULL) - g_object_unref (priv->authorization_domain); - priv->authorization_domain = NULL; - - if (priv->service != NULL) - g_object_unref (priv->service); - priv->service = NULL; - - if (priv->message != NULL) - g_object_unref (priv->message); - priv->message = NULL; + g_clear_object (&priv->network_cancellable); + g_clear_object (&priv->authorization_domain); + g_clear_object (&priv->service); + g_clear_object (&priv->body_stream); + g_clear_object (&priv->message); /* Chain up to the parent class */ G_OBJECT_CLASS (gdata_download_stream_parent_class)->dispose (object); @@ -419,13 +424,6 @@ gdata_download_stream_finalize (GObject *object) { GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_STREAM (object)->priv; - reset_network_thread (GDATA_DOWNLOAD_STREAM (object)); - - g_cond_clear (&(priv->finished_cond)); - g_mutex_clear (&(priv->finished_mutex)); - - g_mutex_clear (&(priv->content_mutex)); - g_free (priv->download_uri); g_free (priv->content_type); @@ -449,14 +447,10 @@ gdata_download_stream_get_property (GObject *object, guint property_id, GValue * g_value_set_string (value, priv->download_uri); break; case PROP_CONTENT_TYPE: - g_mutex_lock (&(priv->content_mutex)); g_value_set_string (value, priv->content_type); - g_mutex_unlock (&(priv->content_mutex)); break; case PROP_CONTENT_LENGTH: - g_mutex_lock (&(priv->content_mutex)); g_value_set_long (value, priv->content_length); - g_mutex_unlock (&(priv->content_mutex)); break; case PROP_CANCELLABLE: g_value_set_object (value, priv->cancellable); @@ -501,47 +495,87 @@ read_cancelled_cb (GCancellable *cancellable, GCancellable *child_cancellable) g_cancellable_cancel (child_cancellable); } +static void +setup_child_cancellable(GDataDownloadStreamPrivate *priv, GCancellable *cancellable) +{ + priv->cancelled_signal = priv->global_cancelled_signal = 0; + priv->cb_cancellable = cancellable ? g_object_ref(cancellable) : NULL; + + /* Listen for cancellation from either @cancellable or @priv->cancellable. We have to multiplex cancellation signals from the two sources + * into a single #GCancellabel, @child_cancellable. */ + priv->child_cancellable = g_cancellable_new (); + + priv->global_cancelled_signal = g_cancellable_connect (priv->cancellable, (GCallback) read_cancelled_cb, priv->child_cancellable, NULL); + + if (priv->cb_cancellable != NULL) + priv->cancelled_signal = g_cancellable_connect (priv->cb_cancellable, (GCallback) read_cancelled_cb, priv->child_cancellable, NULL); +} + +static void +clear_child_cancellable(GDataDownloadStreamPrivate *priv) +{ + /* Disconnect from the cancelled signals. */ + if (priv->cancelled_signal != 0) + g_cancellable_disconnect (priv->cb_cancellable, priv->cancelled_signal); + if (priv->global_cancelled_signal != 0) + g_cancellable_disconnect (priv->cancellable, priv->global_cancelled_signal); + + g_object_unref (priv->child_cancellable); + g_clear_object (&priv->cb_cancellable); +} + static gssize gdata_download_stream_read (GInputStream *stream, void *buffer, gsize count, GCancellable *cancellable, GError **error) { GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_STREAM (stream)->priv; gssize length_read = -1; - gboolean reached_eof = FALSE; - gulong cancelled_signal = 0, global_cancelled_signal = 0; - GCancellable *child_cancellable; GError *child_error = NULL; - /* Listen for cancellation from either @cancellable or @priv->cancellable. We have to multiplex cancellation signals from the two sources - * into a single #GCancellabel, @child_cancellable. */ - child_cancellable = g_cancellable_new (); + setup_child_cancellable (priv, cancellable); - global_cancelled_signal = g_cancellable_connect (priv->cancellable, (GCallback) read_cancelled_cb, child_cancellable, NULL); + /* We're lazy about starting the network operation */ + if (priv->body_stream == NULL) { + GDataAuthorizer *authorizer; - if (cancellable != NULL) - cancelled_signal = g_cancellable_connect (cancellable, (GCallback) read_cancelled_cb, child_cancellable, NULL); - - /* We're lazy about starting the network operation so we don't end up with a massive buffer */ - if (priv->network_thread == NULL) { - /* Handle early cancellation so that we don't create the network thread unnecessarily */ - if (g_cancellable_set_error_if_cancelled (child_cancellable, &child_error) == TRUE) { + /* Handle early cancellation so that we don't send the message unnecessarily */ + if (g_cancellable_set_error_if_cancelled (priv->child_cancellable, &child_error) == TRUE) { length_read = -1; goto done; } - /* Create the network thread */ - create_network_thread (GDATA_DOWNLOAD_STREAM (stream), &child_error); - if (priv->network_thread == NULL) { + g_assert (priv->network_cancellable != NULL); + + /* FIXME: Refresh authorization before sending message in order to prevent authorization errors during transfer. + * See: https://gitlab.gnome.org/GNOME/libgdata/issues/23 */ + authorizer = gdata_service_get_authorizer (priv->service); + if (authorizer) { + g_autoptr(GError) error = NULL; + + gdata_authorizer_refresh_authorization (authorizer, priv->cancellable, &error); + if (error != NULL) + g_debug ("Error returned when refreshing authorization: %s", error->message); + else + gdata_authorizer_process_request (authorizer, priv->authorization_domain, priv->message); + } + + /* Set a Range header if our starting offset is non-zero */ + if (priv->offset > 0) { + soup_message_headers_set_range (soup_message_get_request_headers (priv->message), priv->offset, -1); + } else { + soup_message_headers_remove (soup_message_get_request_headers (priv->message), "Range"); + } + + priv->body_stream = soup_session_send (priv->session, priv->message, priv->network_cancellable, &child_error); + + if (!priv->body_stream) { length_read = -1; goto done; } } - /* Read the data off the buffer. If the operation is cancelled, it'll probably still return a positive number of bytes read — if it does, we - * can return without error. Iff it returns a non-positive number of bytes should we return an error. */ - g_assert (priv->buffer != NULL); - length_read = (gssize) gdata_buffer_pop_data (priv->buffer, buffer, count, &reached_eof, child_cancellable); + length_read = g_input_stream_read (priv->body_stream, buffer, count, priv->child_cancellable, &child_error); - if (length_read < 1 && g_cancellable_set_error_if_cancelled (child_cancellable, &child_error) == TRUE) { + if (length_read < 1 && g_cancellable_set_error_if_cancelled (priv->child_cancellable, &child_error) == TRUE) { /* Handle cancellation */ length_read = -1; @@ -557,20 +591,15 @@ gdata_download_stream_read (GInputStream *stream, void *buffer, gsize count, GCa NULL, 0, &child_error); length_read = -1; + g_clear_object (&priv->body_stream); + goto done; } done: - /* Disconnect from the cancelled signals. */ - if (cancelled_signal != 0) - g_cancellable_disconnect (cancellable, cancelled_signal); - if (global_cancelled_signal != 0) - g_cancellable_disconnect (priv->cancellable, global_cancelled_signal); - - g_object_unref (child_cancellable); + clear_child_cancellable (priv); - g_assert ((reached_eof == FALSE && length_read > 0 && length_read <= (gssize) count && child_error == NULL) || - (reached_eof == TRUE && length_read >= 0 && length_read <= (gssize) count && child_error == NULL) || + g_assert ((length_read >= 0 && length_read <= (gssize) count && child_error == NULL) || (length_read == -1 && child_error != NULL)); if (child_error != NULL) @@ -584,106 +613,256 @@ done: return length_read; } -typedef struct { - GDataDownloadStream *download_stream; - gboolean *cancelled; -} CancelledData; +static void +read_cb (GObject *source, GAsyncResult *result, gpointer user_data) +{ + GInputStream *body_stream = G_INPUT_STREAM (source); + GTask *task = user_data; + GError *error = NULL; + GError *child_error = NULL; + GDataDownloadStreamPrivate *priv = g_task_get_task_data (task); + + gssize length_read; + + length_read = g_input_stream_read_finish (body_stream, result, &error); + + if (length_read < 1 && g_cancellable_set_error_if_cancelled (priv->child_cancellable, &child_error) == TRUE) { + /* Handle cancellation */ + length_read = -1; + + goto done; + } else if (SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (priv->message)) == FALSE) { + GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (priv->service); + + /* Set an appropriate error */ + g_assert (klass->parse_error_response != NULL); + klass->parse_error_response (priv->service, GDATA_OPERATION_DOWNLOAD, + soup_message_get_status (priv->message), + soup_message_get_reason_phrase (priv->message), + NULL, 0, &child_error); + length_read = -1; + + g_clear_object (&priv->body_stream); + + goto done; + } + +done: + clear_child_cancellable (priv); + + if (child_error != NULL) { + g_clear_error (&error); + g_task_return_error (task, child_error); + g_object_unref (task); + return; + } + + /* Update our internal offset */ + if (length_read > 0) { + priv->offset += length_read; + } + + g_task_return_int (task, length_read); + g_object_unref (task); +} + +static void +setup_body_stream_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + SoupSession *session = SOUP_SESSION (source); + GTask *task = user_data; + GDataDownloadStreamPrivate *priv = g_task_get_task_data (task); + GError *error = NULL; + + priv->body_stream = soup_session_send_finish (session, result, &error); + + if (!priv->body_stream) { + clear_child_cancellable (priv); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + g_input_stream_read_async (priv->body_stream, priv->buffer, priv->count, + g_task_get_priority (task), + g_task_get_cancellable (task), + read_cb, task); +} + +static void +setup_body_stream(GTask *task) +{ + GDataDownloadStreamPrivate *priv = g_task_get_task_data (task); + + /* Set a Range header if our starting offset is non-zero */ + if (priv->offset > 0) { + soup_message_headers_set_range (soup_message_get_request_headers (priv->message), priv->offset, -1); + } else { + soup_message_headers_remove (soup_message_get_request_headers (priv->message), "Range"); + } + + soup_session_send_async (priv->session, priv->message, + g_task_get_priority (task), + priv->network_cancellable, + setup_body_stream_cb, + task); +} static void -close_cancelled_cb (GCancellable *cancellable, CancelledData *data) +refresh_auth_cb(GObject *source, GAsyncResult *result, gpointer user_data) { - GDataDownloadStreamPrivate *priv = data->download_stream->priv; + GDataAuthorizer *authorizer = GDATA_AUTHORIZER (source); + GTask *task = user_data; + GError *error = NULL; + GDataDownloadStreamPrivate *priv = g_task_get_task_data (task); - g_mutex_lock (&(priv->finished_mutex)); - *(data->cancelled) = TRUE; - g_cond_signal (&(priv->finished_cond)); - g_mutex_unlock (&(priv->finished_mutex)); + gdata_authorizer_refresh_authorization_finish (authorizer, result, &error); + + if (error != NULL) + g_debug ("Error returned when refreshing authorization: %s", error->message); + else + gdata_authorizer_process_request (authorizer, priv->authorization_domain, + priv->message); + + g_clear_error (&error); + + /* Set a Range header if our starting offset is non-zero */ + if (priv->offset > 0) { + soup_message_headers_set_range (soup_message_get_request_headers (priv->message), priv->offset, -1); + } else { + soup_message_headers_remove (soup_message_get_request_headers (priv->message), "Range"); + } + + setup_body_stream (task); } +static void gdata_download_stream_read_async (GInputStream *stream, void *buffer, gsize count, int io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) +{ + GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_STREAM (stream)->priv; + GTask *task; + GError *error = NULL; + GDataAuthorizer *authorizer; + + setup_child_cancellable (priv, cancellable); + + task = g_task_new (stream, priv->child_cancellable, callback, user_data); + + g_task_set_priority (task, io_priority); + g_task_set_task_data (task, priv, NULL); + + priv->buffer = buffer; + priv->count = count; + + if (priv->body_stream != NULL) { + g_input_stream_read_async (priv->body_stream, priv->buffer, + priv->count, + g_task_get_priority (task), + g_task_get_cancellable (task), + read_cb, task); + return; + } + + /* We're lazy about starting the network operation */ + + /* Handle early cancellation so that we don't send the message unnecessarily */ + if (g_cancellable_set_error_if_cancelled (priv->child_cancellable, &error) == TRUE) { + clear_child_cancellable (priv); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + g_assert (priv->network_cancellable != NULL); + + /* FIXME: Refresh authorization before sending message in order to prevent authorization errors during transfer. + * See: https://gitlab.gnome.org/GNOME/libgdata/issues/23 */ + authorizer = gdata_service_get_authorizer (priv->service); + if (authorizer) { + gdata_authorizer_refresh_authorization_async (authorizer, + priv->cancellable, + refresh_auth_cb, task); + return; + } + + setup_body_stream (task); +} + +static gssize gdata_download_stream_read_finish (GInputStream *stream, GAsyncResult *result, GError **error) +{ + return 0; +} + + /* Even though calling g_input_stream_close() multiple times on this stream is guaranteed to call gdata_download_stream_close() at most once, other * GIO methods (notably g_output_stream_splice()) can call gdata_download_stream_close() directly. Consequently, we need to be careful to be idempotent * after the first call. * - * If the network thread hasn't yet been started (i.e. gdata_download_stream_read() hasn't been called at all yet), %TRUE will be returned immediately. - * - * If the global cancellable, ->cancellable, or @cancellable are cancelled before the call to gdata_download_stream_close(), - * gdata_download_stream_close() should return immediately with %G_IO_ERROR_CANCELLED. If they're cancelled during the call, - * gdata_download_stream_close() should stop waiting for any outstanding network activity to finish and return %G_IO_ERROR_CANCELLED (though the - * operation to finish off network activity and close the stream will still continue). - * - * If the call to gdata_download_stream_close() is not cancelled by any #GCancellable, it will cancel the ongoing network activity, and wait until - * the operation has been cleaned up before returning success. */ + * If the message hasn't been sent yet (i.e. gdata_download_stream_read() hasn't been called at all yet), %TRUE will be returned immediately. + */ static gboolean gdata_download_stream_close (GInputStream *stream, GCancellable *cancellable, GError **error) { GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_STREAM (stream)->priv; - gulong cancelled_signal = 0, global_cancelled_signal = 0; - gboolean cancelled = FALSE; /* must only be touched with ->finished_mutex held */ - gboolean success = TRUE; - CancelledData data; - GError *child_error = NULL; - - /* If the operation was never started, return successfully immediately */ - if (priv->network_thread == NULL) { - goto done; - } - - /* Allow cancellation */ - data.download_stream = GDATA_DOWNLOAD_STREAM (stream); - data.cancelled = &cancelled; - global_cancelled_signal = g_cancellable_connect (priv->cancellable, (GCallback) close_cancelled_cb, &data, NULL); + if (priv->body_stream) { + if (!g_input_stream_close (priv->body_stream, cancellable, error)) + return FALSE; - if (cancellable != NULL) - cancelled_signal = g_cancellable_connect (cancellable, (GCallback) close_cancelled_cb, &data, NULL); - - g_mutex_lock (&(priv->finished_mutex)); + g_object_unref (priv->body_stream); + priv->body_stream = NULL; + } - /* If the operation has started but hasn't already finished, cancel the network thread and wait for it to finish before returning */ - if (priv->finished == FALSE) { - g_cancellable_cancel (priv->network_cancellable); + return TRUE; +} - /* Allow the close() call to be cancelled by cancelling either @cancellable or ->cancellable. Note that this won't prevent the stream - * from continuing to be closed in the background — it'll just stop waiting on the operation to finish being cancelled. */ - if (cancelled == FALSE) { - g_cond_wait (&(priv->finished_cond), &(priv->finished_mutex)); - } +static void +close_async_cb (GObject *source, GAsyncResult *result, gpointer user_data) +{ + GInputStream *body_stream = G_INPUT_STREAM (source); + GError *error = NULL; + GTask *task = user_data; + GDataDownloadStreamPrivate *priv = g_task_get_task_data (task); + + if (!g_input_stream_close_finish (body_stream, result, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; } - /* Error handling */ - if (priv->finished == FALSE && cancelled == TRUE) { - /* Cancelled? If ->finished is TRUE, the network activity finished before the gdata_download_stream_close() operation was cancelled, - * so we don't need to return an error. */ - g_assert (g_cancellable_set_error_if_cancelled (cancellable, &child_error) == TRUE || - g_cancellable_set_error_if_cancelled (priv->cancellable, &child_error) == TRUE); - success = FALSE; - } + g_object_unref (body_stream); + priv->body_stream = NULL; + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} - g_mutex_unlock (&(priv->finished_mutex)); +static void +gdata_download_stream_close_async (GInputStream *stream, int io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) +{ + GTask *task; + GDataDownloadStreamPrivate *priv = GDATA_DOWNLOAD_STREAM (stream)->priv; - /* Disconnect from the signal handlers. Note that we have to do this without @finished_mutex held, as g_cancellable_disconnect() blocks - * until any outstanding cancellation callbacks return, and they will block on @finished_mutex. */ - if (cancelled_signal != 0) - g_cancellable_disconnect (cancellable, cancelled_signal); - if (global_cancelled_signal != 0) - g_cancellable_disconnect (priv->cancellable, global_cancelled_signal); + task = g_task_new (stream, cancellable, callback, user_data); -done: - /* If we were successful, tidy up various bits of state */ - g_mutex_lock (&(priv->finished_mutex)); + g_task_set_priority (task, io_priority); + g_task_set_task_data (task, priv, NULL); - if (success == TRUE && priv->finished == TRUE) { - reset_network_thread (GDATA_DOWNLOAD_STREAM (stream)); + if (!priv->body_stream) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; } - g_mutex_unlock (&(priv->finished_mutex)); + g_input_stream_close_async (priv->body_stream, g_task_get_priority (task), + g_task_get_cancellable (task), + close_async_cb, task); - g_assert ((success == TRUE && child_error == NULL) || (success == FALSE && child_error != NULL)); +} - if (child_error != NULL) - g_propagate_error (error, child_error); +static gboolean gdata_download_stream_close_finish (GInputStream *stream, GAsyncResult *result, GError **error) +{ + GTask *task = G_TASK (result); - return success; + return g_task_propagate_boolean (task, error); } static goffset @@ -731,32 +910,33 @@ gdata_download_stream_seek (GSeekable *seekable, goffset offset, GSeekType type, } /* There are three cases to consider: - * 1. The network thread hasn't been started. In this case, we need to set the offset and do nothing. When the network thread is started + * 1. The message hasn't been sent. In this case, we need to set the offset and do nothing. When the message is sent * (in the next read() call), a Range header will be set on it which will give the correct seek. - * 2. The network thread has been started and the seek is to a position greater than our current position (i.e. one which already does, or - * will soon, exist in the buffer). In this case, we need to pop the intervening bytes off the buffer (which may block) and update the - * offset. - * 3. The network thread has been started and the seek is to a position which has already been popped off the buffer. In this case, we need - * to set the offset and cancel the network thread. When the network thread is restarted (in the next read() call), a Range header will + * 2. The message has been sent and the seek is to a position greater than our current position (i.e. one which already does, or + * will soon, exist in the buffer). In this case, we need to skip the intervening bytes off the body stream and update the offset. + * 3. The message has been sent and the seek is to a position which has already been read. In this case, we need + * to set the offset and close the body stream. When the message is sent again (in the next read() call), a Range header will * be set on it which will give the correct seek. */ - if (priv->network_thread == NULL) { + if (priv->body_stream == NULL) { /* Case 1. Set the offset and we're done. */ priv->offset = offset; goto done; } - /* Cases 2 and 3. The network thread has already been started. */ + /* Cases 2 and 3. */ if (offset >= priv->offset) { goffset num_intervening_bytes; gssize length_read; - /* Case 2. Pop off the intervening bytes and update the offset. If we can't pop enough bytes off, we throw an error. */ + /* Case 2. Skip the intervening bytes and update the offset. If we can't skip enough bytes, we throw an error. */ num_intervening_bytes = offset - priv->offset; - g_assert (priv->buffer != NULL); - length_read = (gssize) gdata_buffer_pop_data (priv->buffer, NULL, num_intervening_bytes, NULL, cancellable); + length_read = (gssize) g_input_stream_skip (priv->body_stream, num_intervening_bytes, cancellable, &child_error); + if (length_read < 0) { + goto done; + } if (length_read != num_intervening_bytes) { if (g_cancellable_set_error_if_cancelled (cancellable, &child_error) == FALSE) { @@ -766,29 +946,14 @@ gdata_download_stream_seek (GSeekable *seekable, goffset offset, GSeekType type, goto done; } - - /* Update the offset */ - priv->offset = offset; - - goto done; - } else { - /* Case 3. Cancel the current network thread. Note that we don't allow cancellation of this call, as we depend on it waiting for - * the network thread to join. */ - if (gdata_download_stream_close (G_INPUT_STREAM (seekable), NULL, &child_error) == FALSE) { - goto done; - } - - /* Update the offset */ - priv->offset = offset; - - /* Mark the thread as unfinished */ - g_mutex_lock (&(priv->finished_mutex)); - priv->finished = FALSE; - g_mutex_unlock (&(priv->finished_mutex)); - + } else if (gdata_download_stream_close (G_INPUT_STREAM (seekable), NULL, &child_error) == FALSE) { + /* Case 3. Close the body stream. We do not allow cancelation here. */ goto done; } + /* Update the offset */ + priv->offset = offset; + done: g_input_stream_clear_pending (G_INPUT_STREAM (seekable)); @@ -813,132 +978,6 @@ gdata_download_stream_truncate (GSeekable *seekable, goffset offset, GCancellabl return FALSE; } -static void -got_headers_cb (SoupMessage *message, GDataDownloadStream *self) -{ - goffset end; - goffset start; - goffset total_length; - - /* Don't get the client's hopes up by setting the Content-Type or -Length if the response - * is actually unsuccessful. */ - if (SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (message)) == FALSE) - return; - - g_mutex_lock (&(self->priv->content_mutex)); - self->priv->content_type = g_strdup (soup_message_headers_get_content_type (soup_message_get_response_headers (message), NULL)); - self->priv->content_length = soup_message_headers_get_content_length (soup_message_get_response_headers (message)); - if (soup_message_headers_get_content_range (soup_message_get_response_headers (message), &start, &end, &total_length)) { - self->priv->content_length = (gssize) total_length; - } - g_mutex_unlock (&(self->priv->content_mutex)); - - /* Emit the notifications for the Content-Length and -Type properties */ - g_object_freeze_notify (G_OBJECT (self)); - g_object_notify (G_OBJECT (self), "content-length"); - g_object_notify (G_OBJECT (self), "content-type"); - g_object_thaw_notify (G_OBJECT (self)); -} - -static void -got_chunk_cb (SoupMessage *message, SoupBuffer *buffer, GDataDownloadStream *self) -{ - /* Ignore the chunk if the response is unsuccessful or it has zero length */ - if (SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (message)) == FALSE || buffer->length == 0) - return; - - /* Push the data onto the buffer immediately */ - g_assert (self->priv->buffer != NULL); - gdata_buffer_push_data (self->priv->buffer, (const guint8*) buffer->data, buffer->length); -} - -static gpointer -download_thread (GDataDownloadStream *self) -{ - GDataDownloadStreamPrivate *priv = self->priv; - GDataAuthorizer *authorizer; - - g_object_ref (self); - - g_assert (priv->network_cancellable != NULL); - - /* FIXME: Refresh authorization before sending message in order to prevent authorization errors during transfer. - * See: https://gitlab.gnome.org/GNOME/libgdata/issues/23 */ - authorizer = gdata_service_get_authorizer (priv->service); - if (authorizer) { - g_autoptr(GError) error = NULL; - - gdata_authorizer_refresh_authorization (authorizer, priv->cancellable, &error); - if (error != NULL) - g_debug ("Error returned when refreshing authorization: %s", error->message); - else - gdata_authorizer_process_request (authorizer, priv->authorization_domain, priv->message); - } - /* Connect to the got-headers signal so we can notify clients of the values of content-type and content-length */ - g_signal_connect (priv->message, "got-headers", (GCallback) got_headers_cb, self); - g_signal_connect (priv->message, "got-chunk", (GCallback) got_chunk_cb, self); - - /* Set a Range header if our starting offset is non-zero */ - if (priv->offset > 0) { - soup_message_headers_set_range (soup_message_get_request_headers (priv->message), priv->offset, -1); - } else { - soup_message_headers_remove (soup_message_get_request_headers (priv->message), "Range"); - } - - _gdata_service_actually_send_message (priv->session, priv->message, priv->network_cancellable, NULL); - - /* Mark the buffer as having reached EOF */ - g_assert (priv->buffer != NULL); - gdata_buffer_push_data (priv->buffer, NULL, 0); - - /* Mark the download as finished */ - g_mutex_lock (&(priv->finished_mutex)); - priv->finished = TRUE; - g_cond_signal (&(priv->finished_cond)); - g_mutex_unlock (&(priv->finished_mutex)); - - g_object_unref (self); - - return NULL; -} - -static void -create_network_thread (GDataDownloadStream *self, GError **error) -{ - GDataDownloadStreamPrivate *priv = self->priv; - - g_assert (priv->buffer == NULL); - priv->buffer = gdata_buffer_new (); - - g_assert (priv->network_thread == NULL); - priv->network_thread = g_thread_try_new ("download-thread", (GThreadFunc) download_thread, self, error); -} - -static void -reset_network_thread (GDataDownloadStream *self) -{ - GDataDownloadStreamPrivate *priv = self->priv; - - priv->network_thread = NULL; - - if (priv->buffer != NULL) { - gdata_buffer_free (priv->buffer); - priv->buffer = NULL; - } - - if (priv->message != NULL) { - soup_session_cancel_message (priv->session, priv->message, SOUP_STATUS_CANCELLED); - g_signal_handlers_disconnect_by_func (priv->message, got_headers_cb, self); - g_signal_handlers_disconnect_by_func (priv->message, got_chunk_cb, self); - } - - priv->offset = 0; - - if (priv->network_cancellable != NULL) { - g_cancellable_reset (priv->network_cancellable); - } -} - /** * gdata_download_stream_new: * @service: a #GDataService @@ -1043,16 +1082,8 @@ gdata_download_stream_get_download_uri (GDataDownloadStream *self) const gchar * gdata_download_stream_get_content_type (GDataDownloadStream *self) { - const gchar *content_type; - g_return_val_if_fail (GDATA_IS_DOWNLOAD_STREAM (self), NULL); - - g_mutex_lock (&(self->priv->content_mutex)); - content_type = self->priv->content_type; - g_mutex_unlock (&(self->priv->content_mutex)); - - /* It's safe to return this, even though we're not taking a copy of it, as it's immutable once set. */ - return content_type; + return self->priv->content_type; } /** @@ -1069,17 +1100,9 @@ gdata_download_stream_get_content_type (GDataDownloadStream *self) gssize gdata_download_stream_get_content_length (GDataDownloadStream *self) { - gssize content_length; - g_return_val_if_fail (GDATA_IS_DOWNLOAD_STREAM (self), -1); - - g_mutex_lock (&(self->priv->content_mutex)); - content_length = self->priv->content_length; - g_mutex_unlock (&(self->priv->content_mutex)); - - g_assert (content_length >= -1); - - return content_length; + g_assert (self->priv->content_length >= -1); + return self->priv->content_length; } /** -- GitLab From 1399edfa5990dd0d30d7f6c62427f14301536d3c Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:15 +0100 Subject: [PATCH 15/45] soup3: add async code for goa-authorizer --- gdata/gdata-goa-authorizer.c | 118 ++++++++++++++++++++++++++++------- 1 file changed, 94 insertions(+), 24 deletions(-) diff --git a/gdata/gdata-goa-authorizer.c b/gdata/gdata-goa-authorizer.c index 525adef7..96ec32a7 100644 --- a/gdata/gdata-goa-authorizer.c +++ b/gdata/gdata-goa-authorizer.c @@ -77,14 +77,8 @@ static void gdata_goa_authorizer_interface_init (GDataAuthorizerInterface *interface); -/* GDataAuthorizer methods must be thread-safe. */ -static GMutex mutex; - struct _GDataGoaAuthorizerPrivate { - /* GoaObject is already thread-safe. */ GoaObject *goa_object; - - /* These members are protected by the global mutex (above). */ gchar *access_token; gchar *access_token_secret; GHashTable *authorization_domains; @@ -104,8 +98,6 @@ gdata_goa_authorizer_add_oauth2_authorization (GDataAuthorizer *authorizer, Soup GDataGoaAuthorizerPrivate *priv; GString *authorization; - /* This MUST be called with the mutex already locked. */ - priv = GDATA_GOA_AUTHORIZER (authorizer)->priv; /* We can't add an Authorization header without an access token. Let the request fail. GData should refresh us if it gets back a @@ -128,8 +120,6 @@ gdata_goa_authorizer_add_authorization (GDataAuthorizer *authorizer, SoupMessage { GDataGoaAuthorizerPrivate *priv; - /* This MUST be called with the mutex already locked. */ - priv = GDATA_GOA_AUTHORIZER (authorizer)->priv; /* Only support OAuth 2.0. OAuth 1.0 was deprecated in 2012. */ @@ -143,8 +133,6 @@ gdata_goa_authorizer_add_authorization (GDataAuthorizer *authorizer, SoupMessage static gboolean gdata_goa_authorizer_is_authorized (GDataAuthorizer *authorizer, GDataAuthorizationDomain *domain) { - /* This MUST be called with the mutex already locked. */ - if (domain == NULL) { return TRUE; } @@ -249,13 +237,9 @@ gdata_goa_authorizer_finalize (GObject *object) static void gdata_goa_authorizer_process_request (GDataAuthorizer *authorizer, GDataAuthorizationDomain *domain, SoupMessage *message) { - g_mutex_lock (&mutex); - if (gdata_goa_authorizer_is_authorized (authorizer, domain)) { gdata_goa_authorizer_add_authorization (authorizer, message); } - - g_mutex_unlock (&mutex); } static gboolean @@ -263,12 +247,8 @@ gdata_goa_authorizer_is_authorized_for_domain (GDataAuthorizer *authorizer, GDat { gboolean authorized; - g_mutex_lock (&mutex); - authorized = gdata_goa_authorizer_is_authorized (authorizer, domain); - g_mutex_unlock (&mutex); - return authorized; } @@ -282,8 +262,6 @@ gdata_goa_authorizer_refresh_authorization (GDataAuthorizer *authorizer, GCancel priv = GDATA_GOA_AUTHORIZER (authorizer)->priv; - g_mutex_lock (&mutex); - g_free (priv->access_token); priv->access_token = NULL; @@ -310,11 +288,101 @@ exit: g_clear_object (&goa_account); g_clear_object (&goa_oauth2_based); - g_mutex_unlock (&mutex); - return success; } +static void +access_token_cb (GObject *object, GAsyncResult *result, gpointer user_data) +{ + GoaOAuth2Based *goa_oauth2_based = GOA_OAUTH2_BASED (object); + GTask *task = user_data; + GDataGoaAuthorizer *authorizer = g_task_get_task_data (task); + GDataGoaAuthorizerPrivate *priv = authorizer->priv; + gboolean success; + GError *error = NULL; + + success = goa_oauth2_based_call_get_access_token_finish (goa_oauth2_based, &priv->access_token, NULL, result, &error); + + g_object_unref (goa_oauth2_based); + + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, success); + + g_object_unref (task); +} + +static void +refresh_authorization_cb (GObject *object, GAsyncResult *result, gpointer user_data) +{ + GoaAccount *goa_account = GOA_ACCOUNT (object); + GTask *task = user_data; + GDataAuthorizer *authorizer = g_task_get_task_data (task); + GDataGoaAuthorizerPrivate *priv; + gboolean success; + GError *error = NULL; + + priv = GDATA_GOA_AUTHORIZER (authorizer)->priv; + + success = goa_account_call_ensure_credentials_finish (goa_account, NULL, result, &error); + g_object_unref (goa_account); + + if (success) { + GoaOAuth2Based *goa_oauth2_based = goa_object_get_oauth2_based (priv->goa_object); + /* Only support OAuth 2.0. */ + if (goa_oauth2_based != NULL) { + goa_oauth2_based_call_get_access_token (goa_oauth2_based, g_task_get_cancellable (task), access_token_cb, task); + return; + } else { + g_warn_if_reached (); /* should never happen */ + } + } + + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, success); + + g_object_unref (task); +} + +static void +gdata_goa_authorizer_refresh_authorization_async (GDataAuthorizer *authorizer, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) +{ + GDataGoaAuthorizerPrivate *priv; + GoaAccount *goa_account; + GTask *task = NULL; + + priv = GDATA_GOA_AUTHORIZER (authorizer)->priv; + + g_free (priv->access_token); + priv->access_token = NULL; + + g_free (priv->access_token_secret); + priv->access_token_secret = NULL; + + goa_account = goa_object_get_account (priv->goa_object); + + task = g_task_new (authorizer, cancellable, callback, user_data); + g_task_set_source_tag (task, gdata_goa_authorizer_refresh_authorization_async); + g_task_set_task_data (task, g_object_ref (authorizer), g_object_unref); + + goa_account_call_ensure_credentials (goa_account, g_task_get_cancellable (task), refresh_authorization_cb, task); +} + +static gboolean +gdata_goa_authorizer_refresh_authorization_finish (GDataAuthorizer *self, GAsyncResult *result, GError **error) +{ + g_assert (GDATA_IS_GOA_AUTHORIZER (self)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (error == NULL || *error == NULL); + g_assert (g_task_is_valid (result, self)); + g_assert (g_async_result_is_tagged (result, gdata_goa_authorizer_refresh_authorization_async)); + + return g_task_propagate_boolean (G_TASK (result), error); +} + static void gdata_goa_authorizer_class_init (GDataGoaAuthorizerClass *class) { @@ -344,6 +412,8 @@ gdata_goa_authorizer_interface_init (GDataAuthorizerInterface *interface) interface->process_request = gdata_goa_authorizer_process_request; interface->is_authorized_for_domain = gdata_goa_authorizer_is_authorized_for_domain; interface->refresh_authorization = gdata_goa_authorizer_refresh_authorization; + interface->refresh_authorization_async = gdata_goa_authorizer_refresh_authorization_async; + interface->refresh_authorization_finish = gdata_goa_authorizer_refresh_authorization_finish; } static void -- GitLab From 911824a063c96dc480500418dd019b3eba594af2 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:16 +0100 Subject: [PATCH 16/45] soup3: async for gdata-calendar-calendar --- .../calendar/gdata-calendar-calendar.c | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/gdata/services/calendar/gdata-calendar-calendar.c b/gdata/services/calendar/gdata-calendar-calendar.c index 69bca348..52ba8854 100644 --- a/gdata/services/calendar/gdata-calendar-calendar.c +++ b/gdata/services/calendar/gdata-calendar-calendar.c @@ -214,6 +214,22 @@ get_authorization_domain (GDataAccessHandler *self) return gdata_calendar_service_get_primary_authorization_domain (); } +typedef struct { + SoupMessage *message; + GDataAccessHandler *self; + GDataQueryProgressCallback progress_callback; + gpointer progress_user_data; + GDestroyNotify destroy_progress_user_data; +} GetRulesAsyncData; + +static void +get_rules_async_data_free (GetRulesAsyncData *self) +{ + g_clear_object (&self->message); + g_clear_object (&self->self); + g_slice_free (GetRulesAsyncData, self); +} + static GDataFeed * get_rules (GDataAccessHandler *self, GDataService *service, @@ -283,17 +299,126 @@ get_rules (GDataAccessHandler *self, g_free (uri); } + g_bytes_unref (body); g_object_unref (message); return feed; } +static void +get_rules_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) +{ + GDataService *service = GDATA_SERVICE (object); + GTask *task = user_data; + GetRulesAsyncData *data = g_task_get_task_data (task); + GDataFeed *feed; + GDataLink *_link; + GBytes *body; + GList/**/ *rules, *i; + const gchar *calendar_id; + GError *error = NULL; + + body = _gdata_service_send_finish (service, result, &error); + + if (!_gdata_service_query_async_check (service, task, data->message, body, &error, data->destroy_progress_user_data, data->progress_user_data)) + return; + + feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), + GDATA_TYPE_CALENDAR_ACCESS_RULE, + data->progress_callback, + data->progress_user_data, + &error); + + /* Set the self link on all the ACL rules so they can be deleted. + * Sigh. */ + rules = gdata_feed_get_entries (feed); + calendar_id = gdata_entry_get_id (GDATA_ENTRY (data->self)); + + for (i = rules; i != NULL; i = i->next) { + const gchar *id; + gchar *uri = NULL; /* owned */ + + /* Set the self link, which is needed for + * gdata_service_delete_entry(). Unfortunately, it needs the + * ACL ID _and_ the calendar ID. */ + id = gdata_entry_get_id (GDATA_ENTRY (i->data)); + + if (id == NULL || calendar_id == NULL) { + continue; + } + + uri = g_strconcat ("https://www.googleapis.com" + "/calendar/v3/calendars/", + calendar_id, "/acl/", id, NULL); + _link = gdata_link_new (uri, GDATA_LINK_SELF); + gdata_entry_add_link (GDATA_ENTRY (i->data), _link); + g_object_unref (_link); + g_free (uri); + } + + g_bytes_unref (body); + + if (data->destroy_progress_user_data != NULL) { + data->destroy_progress_user_data (data->progress_user_data); + } + + if (error) + g_task_return_error (task, error); + else + g_task_return_pointer (task, feed, g_object_unref); + + g_object_unref (task); +} + +static void +get_rules_async (GDataAccessHandler *self, + GDataService *service, + GCancellable *cancellable, + GDataQueryProgressCallback progress_callback, + gpointer progress_user_data, + GDestroyNotify destroy_progress_user_data, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task = NULL; + GDataAccessHandlerIface *iface; + GDataAuthorizationDomain *domain = NULL; + GDataLink *_link; + GetRulesAsyncData *data; + + _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); + } + + data = g_slice_new (GetRulesAsyncData); + data->message = NULL; + data->self = g_object_ref (self); + data->progress_callback = progress_callback; + data->progress_user_data = progress_user_data; + data->destroy_progress_user_data = destroy_progress_user_data; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, gdata_service_query_async); + g_task_set_task_data (task, data, (GDestroyNotify) get_rules_async_data_free); + + _gdata_service_query_async (service, domain, gdata_link_get_uri (_link), + NULL, get_rules_async_cb, task, &data->message); +} + static void gdata_calendar_calendar_access_handler_init (GDataAccessHandlerIface *iface) { iface->is_owner_rule = is_owner_rule; iface->get_authorization_domain = get_authorization_domain; iface->get_rules = get_rules; + iface->get_rules_async = get_rules_async; } static void -- GitLab From e85f2623c4b00d740d85b2d3f9bd3ae793e58ea5 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:16 +0100 Subject: [PATCH 17/45] soup3: use GUri in gdata-picasaweb-file --- .../services/picasaweb/gdata-picasaweb-file.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/gdata/services/picasaweb/gdata-picasaweb-file.c b/gdata/services/picasaweb/gdata-picasaweb-file.c index 96db23a6..77e2e879 100644 --- a/gdata/services/picasaweb/gdata-picasaweb-file.c +++ b/gdata/services/picasaweb/gdata-picasaweb-file.c @@ -1067,9 +1067,9 @@ static gchar * get_query_comments_uri (GDataCommentable *self) { GDataLink *_link; - SoupURI *uri; + GUri *uri, *nuri; GHashTable *query; - gchar *output_uri; + gchar *output_uri, *query_enc; /* Get the feed link of the form: https://picasaweb.google.com/data/feed/api/user/[userID]/albumid/[albumID]/photoid/[photoID] */ _link = gdata_entry_look_up_link (GDATA_ENTRY (self), "http://schemas.google.com/g/2005#feed"); @@ -1077,15 +1077,19 @@ get_query_comments_uri (GDataCommentable *self) /* We're going to query the comments belonging to the photo, so add the comment kind. This link isn't available as a normal on * photos. It's of the form: https://picasaweb.google.com/data/feed/api/user/[userID]/albumid/[albumID]/photoid/[photoID]?kind=comment */ - uri = soup_uri_new (gdata_link_get_uri (_link)); - query = soup_form_decode (uri->query); + uri = g_uri_parse (gdata_link_get_uri (_link), SOUP_HTTP_URI_FLAGS, NULL); + query = soup_form_decode (g_uri_get_query (uri)); g_hash_table_replace (query, g_strdup ("kind"), (gchar*) "comment"); /* libsoup only specifies a destruction function for the key */ - soup_uri_set_query_from_form (uri, query); - output_uri = soup_uri_to_string (uri, FALSE); + query_enc = soup_form_encode_hash (query); g_hash_table_destroy (query); - soup_uri_free (uri); + + nuri = soup_uri_copy (uri, SOUP_URI_QUERY, query_enc, SOUP_URI_NONE); + output_uri = g_uri_to_string_partial (uri, G_URI_HIDE_PASSWORD); + g_uri_unref (nuri); + g_uri_unref (uri); + g_free (query_enc); return output_uri; } -- GitLab From f3834fb735e32b775004c094f2098ad053294915 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:16 +0100 Subject: [PATCH 18/45] soup3: async in authorizer tests --- gdata/tests/authorization.c | 38 ++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/gdata/tests/authorization.c b/gdata/tests/authorization.c index 6a5a2952..3698e85b 100644 --- a/gdata/tests/authorization.c +++ b/gdata/tests/authorization.c @@ -92,7 +92,7 @@ simple_authorizer_init (SimpleAuthorizer *self) static void simple_authorizer_process_request (GDataAuthorizer *self, GDataAuthorizationDomain *domain, SoupMessage *message) { - SoupURI *test_uri; + GUri *test_uri; /* Check that the domain and message are as expected */ g_assert (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain)); @@ -102,9 +102,9 @@ simple_authorizer_process_request (GDataAuthorizer *self, GDataAuthorizationDoma g_assert (message != NULL); g_assert (SOUP_IS_MESSAGE (message)); - test_uri = soup_uri_new ("http://example.com/"); + test_uri = g_uri_parse ("http://example.com/", SOUP_HTTP_URI_FLAGS, NULL); g_assert (soup_uri_equal (soup_message_get_uri (message), test_uri) == TRUE); - soup_uri_free (test_uri); + g_uri_unref (test_uri); /* Check that this is the first time we've touched the message, and if so, flag the message as touched */ if (domain != NULL) { @@ -197,6 +197,36 @@ normal_authorizer_refresh_authorization (GDataAuthorizer *self, GCancellable *ca return TRUE; } +static void +normal_authorizer_refresh_authorization_async (GDataAuthorizer *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) +{ + GTask *task; + GError *error = NULL; + gboolean ret; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, normal_authorizer_refresh_authorization_async); + + ret = normal_authorizer_refresh_authorization (self, g_task_get_cancellable (task), &error); + + if (!ret) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static gboolean +normal_authorizer_refresh_authorization_finish (GDataAuthorizer *self, GAsyncResult *async_result, GError **error) +{ + g_return_val_if_fail (GDATA_IS_AUTHORIZER (self), FALSE); + g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + return g_task_propagate_boolean (G_TASK (async_result), error); +} + static void normal_authorizer_authorizer_init (GDataAuthorizerInterface *iface) { @@ -206,6 +236,8 @@ normal_authorizer_authorizer_init (GDataAuthorizerInterface *iface) /* Unlike SimpleAuthorizer, also implement refresh_authorization() (but not the async versions). */ iface->refresh_authorization = normal_authorizer_refresh_authorization; + iface->refresh_authorization_async = normal_authorizer_refresh_authorization_async; + iface->refresh_authorization_finish = normal_authorizer_refresh_authorization_finish; } /* Complex implementation of GDataAuthorizer for test purposes */ -- GitLab From 811be173ed7431123e69cce214c3097a71203efa Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:17 +0100 Subject: [PATCH 19/45] soup3: use new uploader for documents tests --- gdata/tests/documents.c | 106 +++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 49 deletions(-) diff --git a/gdata/tests/documents.c b/gdata/tests/documents.c index 6d4ef3e6..511d3c13 100644 --- a/gdata/tests/documents.c +++ b/gdata/tests/documents.c @@ -243,8 +243,9 @@ _set_up_temp_document (GDataDocumentsEntry *entry, GDataService *service, GFile GDataDocumentsDocument *document, *new_document; GFileInfo *file_info; GFileInputStream *file_stream; - GDataUploadStream *upload_stream; + GDataUploader *uploader; GError *error = NULL; + GBytes *response; /* Query for information on the file. */ file_info = g_file_query_info (document_file, @@ -253,13 +254,13 @@ _set_up_temp_document (GDataDocumentsEntry *entry, GDataService *service, GFile g_assert_no_error (error); /* Prepare the upload stream */ - upload_stream = gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), - GDATA_DOCUMENTS_DOCUMENT (entry), - g_file_info_get_display_name (file_info), - g_file_info_get_content_type (file_info), - NULL, NULL, &error); + uploader = gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), + GDATA_DOCUMENTS_DOCUMENT (entry), + g_file_info_get_display_name (file_info), + g_file_info_get_content_type (file_info), + NULL, &error); g_assert_no_error (error); - g_assert (GDATA_IS_UPLOAD_STREAM (upload_stream)); + g_assert (GDATA_IS_UPLOADER (uploader)); g_object_unref (file_info); @@ -268,15 +269,16 @@ _set_up_temp_document (GDataDocumentsEntry *entry, GDataService *service, GFile g_assert_no_error (error); /* Upload the document */ - g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream), - G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error); + gdata_uploader_set_input (uploader, G_INPUT_STREAM (file_stream)); + response = gdata_uploader_send (uploader, NULL, NULL, &error); g_assert_no_error (error); /* Finish the upload */ - document = gdata_documents_service_finish_upload (GDATA_DOCUMENTS_SERVICE (service), upload_stream, &error); + document = gdata_documents_service_finish_upload (GDATA_DOCUMENTS_SERVICE (service), uploader, response, &error); g_assert_no_error (error); - g_object_unref (upload_stream); + g_object_unref (uploader); + g_bytes_unref (response); /* HACK: Query for the new document, as Google's servers appear to modify it behind our back when creating the document: * http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=2337. We have to wait a few seconds before trying this to allow the @@ -821,25 +823,26 @@ test_upload (UploadDocumentData *data, gconstpointer _test_params) data->folder, NULL, &error)); g_assert_no_error (error); } else { - GDataUploadStream *upload_stream; + GDataUploader *uploader; GFileInputStream *file_stream; + GBytes *response; /* Prepare the upload stream */ switch (test_params->resumable_type) { case UPLOAD_NON_RESUMABLE: - upload_stream = gdata_documents_service_upload_document (test_params->service, document, - g_file_info_get_display_name (file_info), - g_file_info_get_content_type (file_info), data->folder, - NULL, &error); + uploader = gdata_documents_service_upload_document (test_params->service, document, + g_file_info_get_display_name (file_info), + g_file_info_get_content_type (file_info), data->folder, + &error); break; case UPLOAD_RESUMABLE: gdata_documents_upload_query_set_folder (upload_query, data->folder); - upload_stream = gdata_documents_service_upload_document_resumable (test_params->service, document, - g_file_info_get_display_name (file_info), - g_file_info_get_content_type (file_info), - g_file_info_get_size (file_info), upload_query, - NULL, &error); + uploader = gdata_documents_service_upload_document_resumable (test_params->service, document, + g_file_info_get_display_name (file_info), + g_file_info_get_content_type (file_info), + g_file_info_get_size (file_info), upload_query, + &error); break; default: @@ -847,7 +850,7 @@ test_upload (UploadDocumentData *data, gconstpointer _test_params) } g_assert_no_error (error); - g_assert (GDATA_IS_UPLOAD_STREAM (upload_stream)); + g_assert (GDATA_IS_UPLOADER (uploader)); g_object_unref (file_info); @@ -856,15 +859,16 @@ test_upload (UploadDocumentData *data, gconstpointer _test_params) g_assert_no_error (error); /* Upload the document */ - g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream), - G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error); + gdata_uploader_set_input (uploader, G_INPUT_STREAM (file_stream)); + response = gdata_uploader_send (uploader, NULL, NULL, &error); g_assert_no_error (error); /* Finish the upload */ - data->new_document = gdata_documents_service_finish_upload (test_params->service, upload_stream, &error); + data->new_document = gdata_documents_service_finish_upload (test_params->service, uploader, response, &error); g_assert_no_error (error); - g_object_unref (upload_stream); + g_object_unref (uploader); + g_bytes_unref (response); g_object_unref (file_stream); } @@ -1054,11 +1058,12 @@ test_update (UpdateDocumentData *data, gconstpointer _test_params) GDATA_ENTRY (data->document), NULL, &error)); g_assert_no_error (error); } else { - GDataUploadStream *upload_stream; + GDataUploader *uploader; GFileInputStream *file_stream; GFile *updated_document_file; GFileInfo *file_info; gchar *path = NULL; + GBytes *response; /* Prepare the updated file */ path = g_test_build_filename (G_TEST_DIST, "test_updated.odt", NULL); @@ -1073,23 +1078,23 @@ test_update (UpdateDocumentData *data, gconstpointer _test_params) /* Prepare the upload stream */ switch (test_params->resumable_type) { case UPLOAD_NON_RESUMABLE: - upload_stream = gdata_documents_service_update_document (test_params->service, data->document, - g_file_info_get_display_name (file_info), - g_file_info_get_content_type (file_info), - NULL, &error); + uploader = gdata_documents_service_update_document (test_params->service, data->document, + g_file_info_get_display_name (file_info), + g_file_info_get_content_type (file_info), + &error); break; case UPLOAD_RESUMABLE: - upload_stream = gdata_documents_service_update_document_resumable (test_params->service, data->document, - g_file_info_get_display_name (file_info), - g_file_info_get_content_type (file_info), - g_file_info_get_size (file_info), NULL, &error); + uploader = gdata_documents_service_update_document_resumable (test_params->service, data->document, + g_file_info_get_display_name (file_info), + g_file_info_get_content_type (file_info), + g_file_info_get_size (file_info), &error); break; default: g_assert_not_reached (); } g_assert_no_error (error); - g_assert (GDATA_IS_UPLOAD_STREAM (upload_stream)); + g_assert (GDATA_IS_UPLOADER (uploader)); g_object_unref (file_info); @@ -1100,15 +1105,16 @@ test_update (UpdateDocumentData *data, gconstpointer _test_params) g_object_unref (updated_document_file); /* Upload the updated document */ - g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream), - G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error); + gdata_uploader_set_input (uploader, G_INPUT_STREAM (file_stream)); + response = gdata_uploader_send (uploader, NULL, NULL, &error); g_assert_no_error (error); /* Finish the upload */ - updated_document = gdata_documents_service_finish_upload (test_params->service, upload_stream, &error); + updated_document = gdata_documents_service_finish_upload (test_params->service, uploader, response, &error); g_assert_no_error (error); - g_object_unref (upload_stream); + g_object_unref (uploader); + g_bytes_unref (response); g_object_unref (file_stream); } @@ -1193,12 +1199,13 @@ set_up_folders (FoldersData *data, GDataDocumentsService *service, gboolean init GDataDocumentsFolder *folder; GDataDocumentsFolder *root; GDataDocumentsDocument *document, *new_document; - GDataUploadStream *upload_stream; + GDataUploader *uploader; GFileInputStream *file_stream; GFile *document_file; GFileInfo *file_info; gchar *path = NULL; GError *error = NULL; + GBytes *response; root = GDATA_DOCUMENTS_FOLDER (gdata_service_query_single_entry (GDATA_SERVICE (service), gdata_documents_service_get_primary_authorization_domain (), @@ -1239,11 +1246,11 @@ set_up_folders (FoldersData *data, GDataDocumentsService *service, gboolean init g_assert_no_error (error); /* Prepare the upload stream */ - upload_stream = gdata_documents_service_upload_document (service, document, g_file_info_get_display_name (file_info), - g_file_info_get_content_type (file_info), - (initially_in_folder == TRUE) ? data->folder : NULL, NULL, &error); + uploader = gdata_documents_service_upload_document (service, document, g_file_info_get_display_name (file_info), + g_file_info_get_content_type (file_info), + (initially_in_folder == TRUE) ? data->folder : NULL, &error); g_assert_no_error (error); - g_assert (GDATA_IS_UPLOAD_STREAM (upload_stream)); + g_assert (GDATA_IS_UPLOADER (uploader)); g_object_unref (file_info); g_object_unref (document); @@ -1255,16 +1262,17 @@ set_up_folders (FoldersData *data, GDataDocumentsService *service, gboolean init g_object_unref (document_file); /* Upload the document */ - g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream), - G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error); + gdata_uploader_set_input (uploader, G_INPUT_STREAM (file_stream)); + response = gdata_uploader_send (uploader, NULL, NULL, &error); g_assert_no_error (error); /* Finish the upload */ - new_document = gdata_documents_service_finish_upload (service, upload_stream, &error); + new_document = gdata_documents_service_finish_upload (service, uploader, response, &error); g_assert_no_error (error); g_assert (GDATA_IS_DOCUMENTS_TEXT (new_document)); - g_object_unref (upload_stream); + g_object_unref (uploader); + g_bytes_unref (response); g_object_unref (file_stream); /* HACK: Query for the new document, as Google's servers appear to modify it behind our back when creating the document: -- GitLab From a151f73a3bc312214193a68398eb32c2835490e3 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:17 +0100 Subject: [PATCH 20/45] soup3: use uploader for picasaweb tests --- gdata/tests/picasaweb.c | 73 ++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/gdata/tests/picasaweb.c b/gdata/tests/picasaweb.c index b218aa7f..93326510 100644 --- a/gdata/tests/picasaweb.c +++ b/gdata/tests/picasaweb.c @@ -406,7 +406,8 @@ upload_file (GDataPicasaWebService *service, const gchar *title, GDataPicasaWebA GFile *photo_file; GFileInfo *file_info; GFileInputStream *input_stream; - GDataUploadStream *upload_stream; + GDataUploader *uploader; + GBytes *response; gchar *path = NULL; file = gdata_picasaweb_file_new (NULL); @@ -429,23 +430,23 @@ upload_file (GDataPicasaWebService *service, const gchar *title, GDataPicasaWebA g_object_unref (photo_file); /* Prepare the upload stream */ - upload_stream = gdata_picasaweb_service_upload_file (GDATA_PICASAWEB_SERVICE (service), album, file, g_file_info_get_display_name (file_info), - g_file_info_get_content_type (file_info), NULL, NULL); - g_assert (GDATA_IS_UPLOAD_STREAM (upload_stream)); + uploader = gdata_picasaweb_service_upload_file (GDATA_PICASAWEB_SERVICE (service), album, file, g_file_info_get_display_name (file_info), + g_file_info_get_content_type (file_info), NULL); + g_assert (GDATA_IS_UPLOADER (uploader)); g_object_unref (file_info); g_object_unref (file); /* Upload the photo */ - g_assert_cmpint (g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (input_stream), - G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, NULL), >, 0); - + gdata_uploader_set_input (uploader, G_INPUT_STREAM (input_stream)); + response = gdata_uploader_send (uploader, NULL, NULL, NULL); g_object_unref (input_stream); /* Finish off the upload */ - uploaded_file = gdata_picasaweb_service_finish_file_upload (GDATA_PICASAWEB_SERVICE (service), upload_stream, NULL); + uploaded_file = gdata_picasaweb_service_finish_file_upload (GDATA_PICASAWEB_SERVICE (service), response, NULL); - g_object_unref (upload_stream); + g_object_unref (uploader); + g_bytes_unref (response); g_assert (GDATA_IS_PICASAWEB_FILE (uploaded_file)); @@ -1643,28 +1644,30 @@ tear_down_upload (UploadData *data, gconstpointer service) static void test_upload_default_album (UploadData *data, gconstpointer service) { - GDataUploadStream *upload_stream; + GDataUploader *uploader; const gchar * const *tags, * const *tags2; - gssize transfer_size; GError *error = NULL; + GBytes *response; + gsize transfer_size; gdata_test_mock_server_start_trace (mock_server, "upload-default-album"); /* Prepare the upload stream */ /* TODO right now, it will just go to the default album, we want an uploading one :| */ - upload_stream = gdata_picasaweb_service_upload_file (GDATA_PICASAWEB_SERVICE (service), NULL, data->photo, data->slug, data->content_type, - NULL, &error); + uploader = gdata_picasaweb_service_upload_file (GDATA_PICASAWEB_SERVICE (service), NULL, data->photo, data->slug, data->content_type, + &error); g_assert_no_error (error); - g_assert (GDATA_IS_UPLOAD_STREAM (upload_stream)); + g_assert (GDATA_IS_UPLOADER (uploader)); /* Upload the photo */ - transfer_size = g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (data->file_stream), - G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error); + gdata_uploader_set_input (uploader, G_INPUT_STREAM (data->file_stream)); + response = gdata_uploader_send (uploader, NULL, &transfer_size, &error); g_assert_no_error (error); g_assert_cmpint (transfer_size, >, 0); /* Finish off the upload */ - data->updated_photo = gdata_picasaweb_service_finish_file_upload (GDATA_PICASAWEB_SERVICE (service), upload_stream, &error); + data->updated_photo = gdata_picasaweb_service_finish_file_upload (GDATA_PICASAWEB_SERVICE (service), response, &error); + g_bytes_unref (response); g_assert_no_error (error); g_assert (GDATA_IS_PICASAWEB_FILE (data->updated_photo)); @@ -1689,40 +1692,40 @@ GDATA_ASYNC_CLOSURE_FUNCTIONS (upload, UploadData); GDATA_ASYNC_TEST_FUNCTIONS (upload_default_album, UploadData, G_STMT_START { - GDataUploadStream *upload_stream; + GDataUploader *uploader; GError *error = NULL; /* Prepare the upload stream */ - upload_stream = gdata_picasaweb_service_upload_file (GDATA_PICASAWEB_SERVICE (service), NULL, data->photo, data->slug, - data->content_type, cancellable, &error); + uploader = gdata_picasaweb_service_upload_file (GDATA_PICASAWEB_SERVICE (service), NULL, data->photo, data->slug, + data->content_type, &error); g_assert_no_error (error); - g_assert (GDATA_IS_UPLOAD_STREAM (upload_stream)); + g_assert (GDATA_IS_UPLOADER (uploader)); /* Upload the photo asynchronously */ - g_output_stream_splice_async (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (data->file_stream), - G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, G_PRIORITY_DEFAULT, NULL, - async_ready_callback, async_data); + gdata_uploader_set_input (uploader, G_INPUT_STREAM (data->file_stream)); + response = gdata_uploader_send_async (uploader, cancellable); - g_object_unref (upload_stream); + g_bytes_unref (response); + g_object_unref (uploader); /* Reset the input stream to the beginning. */ g_assert (g_seekable_seek (G_SEEKABLE (data->file_stream), 0, G_SEEK_SET, NULL, NULL) == TRUE); } G_STMT_END, G_STMT_START { - GOutputStream *stream = G_OUTPUT_STREAM (obj); + GDataUploader *uploader = GDATA_UPLOADER (obj); const gchar * const *tags; const gchar * const *tags2; - gssize transfer_size; + GBytes *response; + gsize transfer_size; GError *upload_error = NULL; /* Finish off the transfer */ - transfer_size = g_output_stream_splice_finish (stream, async_result, &error); + response = gdata_uploader_send_finish (uploader, async_result, &transfer_size, &error); if (error == NULL) { - g_assert_cmpint (transfer_size, >, 0); - /* Finish off the upload */ - data->updated_photo = gdata_picasaweb_service_finish_file_upload (data->service, GDATA_UPLOAD_STREAM (stream), &upload_error); + g_assert_cmpint (transfer_size, >, 0); + data->updated_photo = gdata_picasaweb_service_finish_file_upload (data->service, GDATA_UPLOADER (stream), &upload_error); g_assert_no_error (upload_error); g_assert (GDATA_IS_PICASAWEB_FILE (data->updated_photo)); @@ -1737,13 +1740,9 @@ G_STMT_START { g_assert_cmpstr (tags2[0], ==, tags[0]); g_assert_cmpstr (tags2[1], ==, tags[1]); g_assert_cmpstr (tags2[2], ==, tags[2]); + g_bytes_unref (response); } else { - g_assert_cmpint (transfer_size, ==, -1); - - /* Finish off the upload */ - data->updated_photo = gdata_picasaweb_service_finish_file_upload (data->service, GDATA_UPLOAD_STREAM (stream), &upload_error); - g_assert_no_error (upload_error); - g_assert (data->updated_photo == NULL); + data->uploaded_photo = NULL; } g_clear_error (&upload_error); -- GitLab From 6ab8be3a7f50a37aca9450aff9c304ae0f77f8af Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:18 +0100 Subject: [PATCH 21/45] soup3: use uploader in gdata-documents-upload-query --- .../documents/gdata-documents-upload-query.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/gdata/services/documents/gdata-documents-upload-query.c b/gdata/services/documents/gdata-documents-upload-query.c index c242abcd..a723a2b0 100644 --- a/gdata/services/documents/gdata-documents-upload-query.c +++ b/gdata/services/documents/gdata-documents-upload-query.c @@ -41,7 +41,7 @@ * goffset file_size; * GDataDocumentsUploadQuery *upload_query; * GFileInputStream *file_stream; - * GDataUploadStream *upload_stream; + * GDataUploader *uploader; * GError *error = NULL; * * /* Create a service. */ @@ -88,7 +88,7 @@ * gdata_documents_upload_query_set_convert (upload_query, FALSE); * * /* Get an upload stream for the file. */ - * upload_stream = gdata_documents_service_upload_document_resumable (service, document, slug, content_type, file_size, upload_query, NULL, &error); + * uploader = gdata_documents_service_upload_document_resumable (service, document, slug, content_type, file_size, upload_query, &error); * * g_object_unref (upload_query); * g_object_unref (document); @@ -103,23 +103,24 @@ * } * * /* Upload the document. This is a blocking operation, and should normally be done asynchronously. */ - * g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream), - * G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error); + * gdata_uploader_set_input (uploader, G_INPUT_STREAM (file_stream)); + * response = gdata_uploader_send (uploader, NULL, NULL, &error); * * g_object_unref (file_stream); * * if (error != NULL) { * g_error ("Error splicing streams: %s", error->message); * g_error_free (error); - * g_object_unref (upload_stream); + * g_object_unref (uploader); * g_object_unref (service); * return; * } * * /* Finish off the upload by parsing the returned updated document metadata entry. */ - * uploaded_document = gdata_documents_service_finish_upload (service, upload_stream, &error); + * uploaded_document = gdata_documents_service_finish_upload (service, uploader, response, &error); * - * g_object_unref (upload_stream); + * g_object_unref (uploader); + * g_bytes_unref (response); * g_object_unref (service); * * if (error != NULL) { @@ -279,7 +280,7 @@ gdata_documents_upload_query_new (void) * gdata_documents_upload_query_build_uri: * @self: a #GDataDocumentsUploadQuery * - * Builds an upload URI suitable for passing to gdata_upload_stream_new_resumable() in order to upload a document to Google Documents as described in + * Builds an upload URI suitable for passing to gdata_uploader_new_resumable() in order to upload a document to Google Documents as described in * the * * online documentation. -- GitLab From 0dee38c18e1dc3ba06fbf0b997b55e6e107c4703 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:18 +0100 Subject: [PATCH 22/45] soup3: use GUri in gdata-youtube-video --- gdata/services/youtube/gdata-youtube-video.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/gdata/services/youtube/gdata-youtube-video.c b/gdata/services/youtube/gdata-youtube-video.c index 771ebc7e..af0d1d65 100644 --- a/gdata/services/youtube/gdata-youtube-video.c +++ b/gdata/services/youtube/gdata-youtube-video.c @@ -2181,22 +2181,22 @@ gchar * gdata_youtube_video_get_video_id_from_uri (const gchar *video_uri) { gchar *video_id = NULL; - SoupURI *uri; + GUri *uri; g_return_val_if_fail (video_uri != NULL && *video_uri != '\0', NULL); /* Extract the query string from the URI */ - uri = soup_uri_new (video_uri); + uri = g_uri_parse (video_uri, SOUP_HTTP_URI_FLAGS, NULL); if (uri == NULL) return NULL; - else if (uri->host == NULL || strstr (uri->host, "youtube") == NULL) { - soup_uri_free (uri); + else if (strstr (g_uri_get_host (uri), "youtube") == NULL) { + g_uri_unref (uri); return NULL; } /* Try the "v" parameter (e.g. format is: http://www.youtube.com/watch?v=ylLzyHk54Z0) */ - if (uri->query != NULL) { - GHashTable *params = soup_form_decode (uri->query); + if (g_uri_get_query (uri)) { + GHashTable *params = soup_form_decode (g_uri_get_query (uri)); video_id = g_strdup (g_hash_table_lookup (params, "v")); g_hash_table_destroy (params); } @@ -2204,10 +2204,10 @@ gdata_youtube_video_get_video_id_from_uri (const gchar *video_uri) /* Try the "v" fragment component (e.g. format is: http://www.youtube.com/watch#!v=ylLzyHk54Z0). * YouTube introduced this new URI format in March 2010: * http://apiblog.youtube.com/2010/03/upcoming-change-to-youtube-video-page.html */ - if (video_id == NULL && uri->fragment != NULL) { + if (video_id == NULL && g_uri_get_fragment (uri) != NULL) { gchar **components, **i; - components = g_strsplit (uri->fragment, "!", -1); + components = g_strsplit (g_uri_get_fragment (uri), "!", -1); for (i = components; *i != NULL; i++) { if (**i == 'v' && *((*i) + 1) == '=') { video_id = g_strdup ((*i) + 2); @@ -2217,7 +2217,7 @@ gdata_youtube_video_get_video_id_from_uri (const gchar *video_uri) g_strfreev (components); } - soup_uri_free (uri); + g_uri_unref (uri); return video_id; } -- GitLab From f6827798dea08d4c9fd2d2156cf6ec9ce7d6fe40 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:19 +0100 Subject: [PATCH 23/45] soup3: drop legacy libsoup code in streams tests --- gdata/tests/streams.c | 72 +------------------------------------------ 1 file changed, 1 insertion(+), 71 deletions(-) diff --git a/gdata/tests/streams.c b/gdata/tests/streams.c index 6ba5f960..6763e91d 100644 --- a/gdata/tests/streams.c +++ b/gdata/tests/streams.c @@ -32,7 +32,6 @@ #include "gdata.h" #include "common.h" -#ifdef HAVE_LIBSOUP_2_55_90 static gpointer run_server_thread (GMainLoop *loop) { @@ -42,15 +41,6 @@ run_server_thread (GMainLoop *loop) return NULL; } -#else /* if !HAVE_LIBSOUP_2_55_90 */ -static gpointer -run_server_thread (SoupServer *server) -{ - soup_server_run (server); - - return NULL; -} -#endif /* !HAVE_LIBSOUP_2_55_90 */ static GThread * run_server (SoupServer *server, GMainLoop *loop) @@ -59,29 +49,18 @@ run_server (SoupServer *server, GMainLoop *loop) gchar *port_string; GError *error = NULL; guint16 port; + GSList *uris; /* GUri, owned */ -#ifdef HAVE_LIBSOUP_2_55_90 thread = g_thread_try_new ("server-thread", (GThreadFunc) run_server_thread, loop, &error); -#else /* if !HAVE_LIBSOUP_2_55_90 */ - thread = g_thread_try_new ("server-thread", (GThreadFunc) run_server_thread, server, &error); -#endif /* !HAVE_LIBSOUP_2_55_90 */ g_assert_no_error (error); g_assert (thread != NULL); /* Set the port so that libgdata doesn't override it. */ -#ifdef HAVE_LIBSOUP_2_55_90 -{ - GSList *uris; /* owned */ - uris = soup_server_get_uris (server); g_assert (uris != NULL); port = soup_uri_get_port (uris->data); g_slist_free_full (uris, (GDestroyNotify) soup_uri_free); -} -#else /* if !HAVE_LIBSOUP_2_55_90 */ - port = soup_server_get_port (server); -#endif /* !HAVE_LIBSOUP_2_55_90 */ port_string = g_strdup_printf ("%u", port); g_setenv ("LIBGDATA_HTTPS_PORT", port_string, TRUE); @@ -90,7 +69,6 @@ run_server (SoupServer *server, GMainLoop *loop) return thread; } -#ifdef HAVE_LIBSOUP_2_55_90 static gboolean quit_server_cb (GMainLoop *loop) { @@ -105,22 +83,6 @@ stop_server (SoupServer *server, GMainLoop *loop) soup_add_completion (g_main_loop_get_context (loop), (GSourceFunc) quit_server_cb, loop); } -#else /* if !HAVE_LIBSOUP_2_55_90 */ -static gboolean -quit_server_cb (SoupServer *server) -{ - soup_server_quit (server); - - return FALSE; -} - -static void -stop_server (SoupServer *server, GMainLoop *loop) -{ - soup_add_completion (g_main_loop_get_context (loop), - (GSourceFunc) quit_server_cb, server); -} -#endif /* !HAVE_LIBSOUP_2_55_90 */ static gchar * get_test_string (guint start_num, guint end_num) @@ -162,23 +124,14 @@ create_server (SoupServerCallback callback, gpointer user_data, GMainLoop **main { GMainContext *context; SoupServer *server; -#ifdef HAVE_LIBSOUP_2_55_90 gchar *cert_path = NULL, *key_path = NULL; GError *error = NULL; -#else /* if !HAVE_LIBSOUP_2_55_90 */ - union { - struct sockaddr_in in; - struct sockaddr norm; - } sock; - SoupAddress *addr; -#endif /* HAVE_LIBSOUP_2_55_90 */ /* Create the server */ g_assert (main_loop != NULL); context = g_main_context_new (); *main_loop = g_main_loop_new (context, FALSE); -#ifdef HAVE_LIBSOUP_2_55_90 server = soup_server_new (NULL, NULL); cert_path = g_test_build_filename (G_TEST_DIST, "cert.pem", NULL); @@ -199,23 +152,6 @@ create_server (SoupServerCallback callback, gpointer user_data, GMainLoop **main g_assert_no_error (error); g_main_context_pop_thread_default (context); -#else /* if !HAVE_LIBSOUP_2_55_90 */ - memset (&sock, 0, sizeof (sock)); - sock.in.sin_family = AF_INET; - sock.in.sin_addr.s_addr = htonl (INADDR_LOOPBACK); - sock.in.sin_port = htons (0); /* random port */ - - addr = soup_address_new_from_sockaddr (&sock.norm, sizeof (sock.norm)); - g_assert (addr != NULL); - - server = soup_server_new (SOUP_SERVER_INTERFACE, addr, - SOUP_SERVER_ASYNC_CONTEXT, context, - NULL); - - soup_server_add_handler (server, NULL, callback, user_data, NULL); - - g_object_unref (addr); -#endif /* !HAVE_LIBSOUP_2_55_90 */ g_assert (server != NULL); g_main_context_unref (context); @@ -226,7 +162,6 @@ create_server (SoupServerCallback callback, gpointer user_data, GMainLoop **main static gchar * build_server_uri (SoupServer *server) { -#ifdef HAVE_LIBSOUP_2_55_90 GSList *uris; /* owned */ GSList *l; /* unowned */ gchar *retval = NULL; /* owned */ @@ -244,11 +179,6 @@ build_server_uri (SoupServer *server) g_assert (retval != NULL); return retval; -#else /* if !HAVE_LIBSOUP_2_55_90 */ - return g_strdup_printf ("https://%s:%u/", - soup_address_get_physical (soup_socket_get_local_address (soup_server_get_listener (server))), - soup_server_get_port (server)); -#endif /* !HAVE_LIBSOUP_2_55_90 */ } static void -- GitLab From b32a95493b9abe3fc971b90d64238bbd4309b59f Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:19 +0100 Subject: [PATCH 24/45] soup3: port server part of streams tests to soup3 --- gdata/tests/streams.c | 123 +++++++++++++++++++++++------------------- 1 file changed, 68 insertions(+), 55 deletions(-) diff --git a/gdata/tests/streams.c b/gdata/tests/streams.c index 6763e91d..3ebba80c 100644 --- a/gdata/tests/streams.c +++ b/gdata/tests/streams.c @@ -58,9 +58,9 @@ run_server (SoupServer *server, GMainLoop *loop) /* Set the port so that libgdata doesn't override it. */ uris = soup_server_get_uris (server); g_assert (uris != NULL); - port = soup_uri_get_port (uris->data); + port = g_uri_get_port (uris->data); - g_slist_free_full (uris, (GDestroyNotify) soup_uri_free); + g_slist_free_full (uris, (GDestroyNotify) g_uri_unref); port_string = g_strdup_printf ("%u", port); g_setenv ("LIBGDATA_HTTPS_PORT", port_string, TRUE); @@ -80,8 +80,13 @@ quit_server_cb (GMainLoop *loop) static void stop_server (SoupServer *server, GMainLoop *loop) { - soup_add_completion (g_main_loop_get_context (loop), - (GSourceFunc) quit_server_cb, loop); + GSource *source = g_idle_source_new (); + + g_source_set_priority (source, G_PRIORITY_DEFAULT); + g_source_set_callback (source, (GSourceFunc) quit_server_cb, loop, NULL); + g_source_attach (source, g_main_loop_get_context (loop)); + + g_source_unref (source); } static gchar * @@ -101,8 +106,9 @@ get_test_string (guint start_num, guint end_num) } static void -test_download_stream_download_server_content_length_handler_cb (SoupServer *server, SoupMessage *message, const char *path, GHashTable *query, - SoupClientContext *client, gpointer user_data) +test_download_stream_download_server_content_length_handler_cb (SoupServer *server, SoupServerMessage *message, + const char *path, GHashTable *query, + gpointer user_data) { gchar *test_string; goffset test_string_length; @@ -111,12 +117,12 @@ test_download_stream_download_server_content_length_handler_cb (SoupServer *serv test_string_length = strlen (test_string) + 1; /* Add some response headers */ - soup_message_set_status (message, SOUP_STATUS_OK); - soup_message_headers_set_content_type (message->response_headers, "text/plain", NULL); - soup_message_headers_set_content_length (message->response_headers, test_string_length); + soup_server_message_set_status (message, SOUP_STATUS_OK, NULL); + soup_message_headers_set_content_type (soup_server_message_get_response_headers (message), "text/plain", NULL); + soup_message_headers_set_content_length (soup_server_message_get_response_headers (message), test_string_length); /* Set the response body */ - soup_message_body_append (message->response_body, SOUP_MEMORY_TAKE, test_string, test_string_length); + soup_message_body_append (soup_server_message_get_response_body (message), SOUP_MEMORY_TAKE, test_string, test_string_length); } static SoupServer * @@ -126,6 +132,7 @@ create_server (SoupServerCallback callback, gpointer user_data, GMainLoop **main SoupServer *server; gchar *cert_path = NULL, *key_path = NULL; GError *error = NULL; + GTlsCertificate *cert = NULL; /* Create the server */ g_assert (main_loop != NULL); @@ -137,12 +144,15 @@ create_server (SoupServerCallback callback, gpointer user_data, GMainLoop **main cert_path = g_test_build_filename (G_TEST_DIST, "cert.pem", NULL); key_path = g_test_build_filename (G_TEST_DIST, "key.pem", NULL); - soup_server_set_ssl_cert_file (server, cert_path, key_path, &error); + cert = g_tls_certificate_new_from_files (cert_path, key_path, &error); g_assert_no_error (error); g_free (key_path); g_free (cert_path); + soup_server_set_tls_certificate (server, cert); + g_object_unref (cert); + soup_server_add_handler (server, NULL, callback, user_data, NULL); g_main_context_push_thread_default (context); @@ -169,12 +179,12 @@ build_server_uri (SoupServer *server) uris = soup_server_get_uris (server); for (l = uris; l != NULL && retval == NULL; l = l->next) { - if (soup_uri_get_scheme (l->data) == SOUP_URI_SCHEME_HTTPS) { - retval = soup_uri_to_string (l->data, FALSE); + if (!strcmp (g_uri_get_scheme (l->data), "https")) { + retval = g_uri_to_string_partial (l->data, G_URI_HIDE_PASSWORD); } } - g_slist_free_full (uris, (GDestroyNotify) soup_uri_free); + g_slist_free_full (uris, (GDestroyNotify) g_uri_unref); g_assert (retval != NULL); @@ -244,8 +254,9 @@ test_download_stream_download_content_length (void) } static void -test_download_stream_download_server_seek_handler_cb (SoupServer *server, SoupMessage *message, const char *path, GHashTable *query, - SoupClientContext *client, gpointer user_data) +test_download_stream_download_server_seek_handler_cb (SoupServer *server, SoupServerMessage *message, + const char *path, GHashTable *query, + gpointer user_data) { gchar *test_string; goffset test_string_length; @@ -254,8 +265,8 @@ test_download_stream_download_server_seek_handler_cb (SoupServer *server, SoupMe test_string_length = strlen (test_string) + 1; /* Add some response headers */ - soup_message_set_status (message, SOUP_STATUS_OK); - soup_message_body_append (message->response_body, SOUP_MEMORY_TAKE, test_string, test_string_length); + soup_server_message_set_status (message, SOUP_STATUS_OK, NULL); + soup_message_body_append (soup_server_message_get_response_body (message), SOUP_MEMORY_TAKE, test_string, test_string_length); } /* Test seeking before the first read */ @@ -521,31 +532,32 @@ test_download_stream_download_seek_after_start_backwards (void) } static void -test_upload_stream_upload_no_entry_content_length_server_handler_cb (SoupServer *server, SoupMessage *message, const char *path, GHashTable *query, - SoupClientContext *client, gpointer user_data) +test_upload_stream_upload_no_entry_content_length_server_handler_cb (SoupServer *server, SoupServerMessage *message, + const char *path, GHashTable *query, + gpointer user_data) { gchar *test_string; goffset test_string_length; /* Check the Slug and Content-Type headers have been correctly set by the client */ - g_assert_cmpstr (soup_message_headers_get_content_type (message->request_headers, NULL), ==, "text/plain"); - g_assert_cmpstr (soup_message_headers_get_one (message->request_headers, "Slug"), ==, "slug"); + g_assert_cmpstr (soup_message_headers_get_content_type (soup_server_message_get_request_headers (message), NULL), ==, "text/plain"); + g_assert_cmpstr (soup_message_headers_get_one (soup_server_message_get_request_headers (message), "Slug"), ==, "slug"); /* Check the client sent the right data */ test_string = get_test_string (1, 1000); test_string_length = strlen (test_string) + 1; - g_assert_cmpint (message->request_body->length, ==, test_string_length); - g_assert_cmpstr (message->request_body->data, ==, test_string); + g_assert_cmpint (soup_server_message_get_request_body (message)->length, ==, test_string_length); + g_assert_cmpstr (soup_server_message_get_request_body (message)->data, ==, test_string); g_free (test_string); /* Add some response headers */ - soup_message_set_status (message, SOUP_STATUS_OK); - soup_message_headers_set_content_type (message->response_headers, "text/plain", NULL); + soup_server_message_set_status (message, SOUP_STATUS_OK, NULL); + soup_message_headers_set_content_type (soup_server_message_get_response_headers (message), "text/plain", NULL); /* Set the response body */ - soup_message_body_append (message->response_body, SOUP_MEMORY_STATIC, "Test passed!", 13); + soup_message_body_append (soup_server_message_get_response_body (message), SOUP_MEMORY_STATIC, "Test passed!", 13); } static void @@ -646,8 +658,9 @@ typedef struct { } UploadStreamResumableServerData; static void -test_upload_stream_resumable_server_handler_cb (SoupServer *server, SoupMessage *message, const char *path, GHashTable *query, - SoupClientContext *client, UploadStreamResumableServerData *server_data) +test_upload_stream_resumable_server_handler_cb (SoupServer *server, SoupServerMessage *message, + const char *path, GHashTable *query, + UploadStreamResumableServerData *server_data) { UploadStreamResumableTestParams *test_params; @@ -659,28 +672,28 @@ test_upload_stream_resumable_server_handler_cb (SoupServer *server, SoupMessage gchar *file_size_str; /* Check the Slug and X-Upload-* headers. */ - g_assert_cmpstr (soup_message_headers_get_one (message->request_headers, "Slug"), ==, "slug"); + g_assert_cmpstr (soup_message_headers_get_one (soup_server_message_get_request_headers (message), "Slug"), ==, "slug"); file_size_str = g_strdup_printf ("%" G_GSIZE_FORMAT, test_params->file_size); - g_assert_cmpstr (soup_message_headers_get_one (message->request_headers, "X-Upload-Content-Type"), ==, "text/plain"); - g_assert_cmpstr (soup_message_headers_get_one (message->request_headers, "X-Upload-Content-Length"), ==, file_size_str); + g_assert_cmpstr (soup_message_headers_get_one (soup_server_message_get_request_headers (message), "X-Upload-Content-Type"), ==, "text/plain"); + g_assert_cmpstr (soup_message_headers_get_one (soup_server_message_get_request_headers (message), "X-Upload-Content-Length"), ==, file_size_str); g_free (file_size_str); /* Check the Content-Type and content. */ switch (test_params->content_type) { case CONTENT_ONLY: /* Check nothing was sent. */ - g_assert_cmpstr (soup_message_headers_get_content_type (message->request_headers, NULL), ==, NULL); - g_assert_cmpint (message->request_body->length, ==, 0); + g_assert_cmpstr (soup_message_headers_get_content_type (soup_server_message_get_request_headers (message), NULL), ==, NULL); + g_assert_cmpint (soup_server_message_get_request_body (message)->length, ==, 0); break; case CONTENT_AND_METADATA: case METADATA_ONLY: /* Check the JSON sent by the client. */ - g_assert_cmpstr (soup_message_headers_get_content_type (message->request_headers, NULL), ==, "application/json"); + g_assert_cmpstr (soup_message_headers_get_content_type (soup_server_message_get_request_headers (message), NULL), ==, "application/json"); - g_assert (message->request_body->data[message->request_body->length] == '\0'); - g_assert (gdata_test_compare_json_strings (message->request_body->data, + g_assert (soup_server_message_get_request_body (message)->data[soup_server_message_get_request_body (message)->length] == '\0'); + g_assert (gdata_test_compare_json_strings (soup_server_message_get_request_body (message)->data, "{" "'title': 'Test title!'," "'kind': 'youtube#video'," @@ -721,9 +734,9 @@ test_upload_stream_resumable_server_handler_cb (SoupServer *server, SoupMessage /* Subsequent request. */ /* Check the Slug and X-Upload-* headers. */ - g_assert_cmpstr (soup_message_headers_get_one (message->request_headers, "Slug"), ==, NULL); - g_assert_cmpstr (soup_message_headers_get_one (message->request_headers, "X-Upload-Content-Type"), ==, NULL); - g_assert_cmpstr (soup_message_headers_get_one (message->request_headers, "X-Upload-Content-Length"), ==, NULL); + g_assert_cmpstr (soup_message_headers_get_one (soup_server_message_get_request_headers (message), "Slug"), ==, NULL); + g_assert_cmpstr (soup_message_headers_get_one (soup_server_message_get_request_headers (message), "X-Upload-Content-Type"), ==, NULL); + g_assert_cmpstr (soup_message_headers_get_one (soup_server_message_get_request_headers (message), "X-Upload-Content-Length"), ==, NULL); /* Check the Content-Type and content. */ switch (test_params->content_type) { @@ -732,19 +745,19 @@ test_upload_stream_resumable_server_handler_cb (SoupServer *server, SoupMessage goffset range_start, range_end, range_length; /* Check the headers. */ - g_assert_cmpstr (soup_message_headers_get_content_type (message->request_headers, NULL), ==, "text/plain"); - g_assert_cmpint (soup_message_headers_get_content_length (message->request_headers), ==, message->request_body->length); - g_assert_cmpint (message->request_body->length, >, 0); - g_assert_cmpint (message->request_body->length, <=, 512 * 1024 /* 512 KiB */); - g_assert (soup_message_headers_get_content_range (message->request_headers, &range_start, &range_end, + g_assert_cmpstr (soup_message_headers_get_content_type (soup_server_message_get_request_headers (message), NULL), ==, "text/plain"); + g_assert_cmpint (soup_message_headers_get_content_length (soup_server_message_get_request_headers (message)), ==, soup_server_message_get_request_body (message)->length); + g_assert_cmpint (soup_server_message_get_request_body (message)->length, >, 0); + g_assert_cmpint (soup_server_message_get_request_body (message)->length, <=, 512 * 1024 /* 512 KiB */); + g_assert (soup_message_headers_get_content_range (soup_server_message_get_request_headers (message), &range_start, &range_end, &range_length) == TRUE); g_assert_cmpint (range_start, ==, server_data->next_range_start); g_assert_cmpint (range_end, ==, server_data->next_range_end); g_assert_cmpint (range_length, ==, test_params->file_size); /* Check the content. */ - g_assert (memcmp (server_data->test_string + range_start, message->request_body->data, - message->request_body->length) == 0); + g_assert (memcmp (server_data->test_string + range_start, soup_server_message_get_request_body (message)->data, + soup_server_message_get_request_body (message)->length) == 0); /* Update the expected values. */ server_data->next_range_start = range_end + 1; @@ -806,8 +819,8 @@ error: { "}"; /* Error. */ - soup_message_set_status (message, SOUP_STATUS_UNAUTHORIZED); /* arbitrary error status code */ - soup_message_body_append (message->response_body, SOUP_MEMORY_STATIC, error_response, strlen (error_response)); + soup_server_message_set_status (message, SOUP_STATUS_UNAUTHORIZED, NULL); /* arbitrary error status code */ + soup_message_body_append (soup_server_message_get_response_body (message), SOUP_MEMORY_STATIC, error_response, strlen (error_response)); } return; @@ -817,16 +830,16 @@ continuation: { /* Continuation. */ if (server_data->next_path_index == 0) { - soup_message_set_status (message, SOUP_STATUS_OK); + soup_server_message_set_status (message, SOUP_STATUS_OK, NULL); } else { - soup_message_set_status (message, 308); + soup_server_message_set_status (message, 308, NULL); } server_uri = build_server_uri (server); g_assert_cmpstr (server_uri + strlen (server_uri) - 1, ==, "/"); upload_uri = g_strdup_printf ("%s%u", server_uri, ++server_data->next_path_index); - soup_message_headers_replace (message->response_headers, "Location", upload_uri); + soup_message_headers_replace (soup_server_message_get_response_headers (message), "Location", upload_uri); g_free (upload_uri); g_free (server_uri); } @@ -850,9 +863,9 @@ completion: { "}"; /* Completion. */ - soup_message_set_status (message, SOUP_STATUS_CREATED); - soup_message_headers_set_content_type (message->response_headers, "application/json", NULL); - soup_message_body_append (message->response_body, SOUP_MEMORY_STATIC, completion_response, strlen (completion_response)); + soup_server_message_set_status (message, SOUP_STATUS_CREATED, NULL); + soup_message_headers_set_content_type (soup_server_message_get_response_headers (message), "application/json", NULL); + soup_message_body_append (soup_server_message_get_response_body (message), SOUP_MEMORY_STATIC, completion_response, strlen (completion_response)); } } -- GitLab From bb17f67faa1ee8d603bd20b1467e25d8f16ca502 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:19 +0100 Subject: [PATCH 25/45] soup3: use uploader in streams, youtube tests --- gdata/tests/streams.c | 74 ++++++++++++++----------------------------- gdata/tests/youtube.c | 51 ++++++++++++++--------------- 2 files changed, 48 insertions(+), 77 deletions(-) diff --git a/gdata/tests/streams.c b/gdata/tests/streams.c index 3ebba80c..1c2d2cab 100644 --- a/gdata/tests/streams.c +++ b/gdata/tests/streams.c @@ -568,11 +568,11 @@ test_upload_stream_upload_no_entry_content_length (void) GThread *thread; gchar *upload_uri, *test_string; GDataService *service; - GOutputStream *upload_stream; - gssize length_written; - gsize total_length_written = 0; + GDataUploader *uploader; goffset test_string_length; - gboolean success; + GBytes *body; + GBytes *response; + gsize transfer_size; GError *error = NULL; /* Create and run the server */ @@ -582,7 +582,7 @@ test_upload_stream_upload_no_entry_content_length (void) /* Create a new upload stream uploading to the server */ upload_uri = build_server_uri (server); service = GDATA_SERVICE (gdata_youtube_service_new ("developer-key", NULL)); - upload_stream = gdata_upload_stream_new (service, NULL, SOUP_METHOD_POST, upload_uri, NULL, "slug", "text/plain", NULL); + uploader = gdata_uploader_new (service, NULL, SOUP_METHOD_POST, upload_uri, NULL, "slug", "text/plain"); g_object_unref (service); g_free (upload_uri); @@ -590,30 +590,26 @@ test_upload_stream_upload_no_entry_content_length (void) test_string = get_test_string (1, 1000); test_string_length = strlen (test_string) + 1; - while ((length_written = g_output_stream_write (upload_stream, test_string + total_length_written, - test_string_length - total_length_written, NULL, &error)) > 0) { - g_assert_cmpint (length_written, <=, test_string_length - total_length_written); + body = g_bytes_new_static (test_string, test_string_length); + gdata_uploader_set_input_from_bytes (uploader, body); - total_length_written += length_written; - } + response = gdata_uploader_send (uploader, NULL, &transfer_size, &error); + g_assert_cmpint (transfer_size, ==, test_string_length); + g_bytes_unref (body); g_free (test_string); /* Check we've had a successful return value */ + g_assert_nonnull (response); g_assert_no_error (error); - g_assert_cmpint (length_written, ==, 0); - g_assert_cmpint (total_length_written, ==, test_string_length); - /* Close the stream */ - success = g_output_stream_close (upload_stream, NULL, &error); - g_assert_no_error (error); - g_assert (success == TRUE); + g_bytes_unref (response); /* Kill the server and wait for it to die */ stop_server (server, main_loop); g_thread_join (thread); - g_object_unref (upload_stream); + g_object_unref (uploader); g_object_unref (server); g_main_loop_unref (main_loop); } @@ -880,13 +876,12 @@ test_upload_stream_resumable (gconstpointer user_data) gchar *upload_uri; GDataService *service; GDataEntry *entry = NULL; - GOutputStream *upload_stream; - gssize length_written; + GDataUploader *uploader; gsize total_length_written = 0; gchar *test_string; goffset test_string_length; - gboolean success; GError *error = NULL; + GBytes *response; test_params = (UploadStreamResumableTestParams*) user_data; @@ -920,55 +915,34 @@ test_upload_stream_resumable (gconstpointer user_data) upload_uri = build_server_uri (server); service = GDATA_SERVICE (gdata_youtube_service_new ("developer-key", NULL)); - upload_stream = gdata_upload_stream_new_resumable (service, NULL, SOUP_METHOD_POST, upload_uri, entry, "slug", "text/plain", - test_params->file_size, NULL); + uploader = gdata_uploader_new_resumable (service, NULL, SOUP_METHOD_POST, upload_uri, entry, "slug", "text/plain", + test_params->file_size); g_object_unref (service); g_free (upload_uri); g_clear_object (&entry); if (test_params->file_size > 0) { - while ((length_written = g_output_stream_write (upload_stream, test_string + total_length_written, - test_string_length - total_length_written, NULL, &error)) > 0) { - g_assert_cmpint (length_written, <=, test_string_length - total_length_written); - - total_length_written += length_written; - } - } else { - /* Do an empty write to poke things into action. */ - if ((length_written = g_output_stream_write (upload_stream, "", 0, NULL, &error)) > 0) { - total_length_written += length_written; - } + GInputStream *stream = g_memory_input_stream_new_from_data (test_string, test_string_length, NULL); + gdata_uploader_set_input (uploader, stream); + g_object_unref (stream); } + response = gdata_uploader_send (uploader, NULL, &total_length_written, &error); /* Check the return value. */ switch (test_params->error_type) { case ERROR_ON_INITIAL_REQUEST: case ERROR_ON_SUBSEQUENT_REQUEST: case ERROR_ON_FINAL_REQUEST: - /* We can't check the write() call for errors, since whether it throws an error depends on whether the range it's writing - * overlaps a resumable upload chunk, which is entirely arbitrary and unpredictable. */ - g_assert_cmpint (length_written, <=, 0); + g_assert (response == NULL); g_assert_cmpint (total_length_written, <=, test_string_length); - g_clear_error (&error); - - /* Close the stream */ - success = g_output_stream_close (upload_stream, NULL, &error); g_assert_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED); - g_assert (success == FALSE); g_clear_error (&error); - break; case NO_ERROR: /* Check we've had a successful return value */ g_assert_no_error (error); - g_assert_cmpint (length_written, ==, 0); g_assert_cmpint (total_length_written, ==, test_string_length); - - /* Close the stream */ - success = g_output_stream_close (upload_stream, NULL, &error); - g_assert_no_error (error); - g_assert (success == TRUE); - + g_bytes_unref (response); break; default: g_assert_not_reached (); @@ -979,7 +953,7 @@ test_upload_stream_resumable (gconstpointer user_data) g_thread_join (thread); g_free (test_string); - g_object_unref (upload_stream); + g_object_unref (uploader); g_object_unref (server); g_main_loop_unref (main_loop); } diff --git a/gdata/tests/youtube.c b/gdata/tests/youtube.c index e4780848..d6dce5d4 100644 --- a/gdata/tests/youtube.c +++ b/gdata/tests/youtube.c @@ -264,19 +264,20 @@ tear_down_upload (UploadData *data, gconstpointer service) static void test_upload_simple (UploadData *data, gconstpointer service) { - GDataUploadStream *upload_stream; + GDataUploader *uploader; GFileInputStream *file_stream; const gchar * const *tags, * const *tags2; - gssize transfer_size; + gsize transfer_size; + GBytes *response; GError *error = NULL; gdata_test_mock_server_start_trace (mock_server, "upload-simple"); /* Prepare the upload stream */ - upload_stream = gdata_youtube_service_upload_video (GDATA_YOUTUBE_SERVICE (service), data->video, data->slug, data->content_type, NULL, - &error); + uploader = gdata_youtube_service_upload_video (GDATA_YOUTUBE_SERVICE (service), data->video, data->slug, data->content_type, + &error); g_assert_no_error (error); - g_assert (GDATA_IS_UPLOAD_STREAM (upload_stream)); + g_assert (GDATA_IS_UPLOADER (uploader)); /* Get an input stream for the file */ file_stream = g_file_read (data->video_file, NULL, &error); @@ -284,18 +285,18 @@ test_upload_simple (UploadData *data, gconstpointer service) g_assert (G_IS_FILE_INPUT_STREAM (file_stream)); /* Upload the video */ - transfer_size = g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream), - G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error); + gdata_uploader_set_input (uploader, G_INPUT_STREAM (file_stream)); + response = gdata_uploader_send (uploader, NULL, &transfer_size, &error); g_assert_no_error (error); g_assert_cmpint (transfer_size, >, 0); /* Finish off the upload */ - data->updated_video = gdata_youtube_service_finish_video_upload (GDATA_YOUTUBE_SERVICE (service), upload_stream, &error); + data->updated_video = gdata_youtube_service_finish_video_upload (GDATA_YOUTUBE_SERVICE (service), response, &error); g_assert_no_error (error); g_assert (GDATA_IS_YOUTUBE_VIDEO (data->updated_video)); g_object_unref (file_stream); - g_object_unref (upload_stream); + g_object_unref (uploader); /* Check the video's properties */ g_assert (gdata_entry_is_inserted (GDATA_ENTRY (data->updated_video))); @@ -318,15 +319,15 @@ GDATA_ASYNC_CLOSURE_FUNCTIONS (upload, UploadData); GDATA_ASYNC_TEST_FUNCTIONS (upload, UploadData, G_STMT_START { - GDataUploadStream *upload_stream; + GDataUploader *uploader; GFileInputStream *file_stream; GError *error = NULL; /* Prepare the upload stream */ - upload_stream = gdata_youtube_service_upload_video (GDATA_YOUTUBE_SERVICE (service), data->video, data->slug, - data->content_type, cancellable, &error); + uploader = gdata_youtube_service_upload_video (GDATA_YOUTUBE_SERVICE (service), data->video, data->slug, + data->content_type, &error); g_assert_no_error (error); - g_assert (GDATA_IS_UPLOAD_STREAM (upload_stream)); + g_assert (GDATA_IS_UPLOADER (uploader)); /* Get an input stream for the file */ file_stream = g_file_read (data->video_file, NULL, &error); @@ -334,28 +335,29 @@ G_STMT_START { g_assert (G_IS_FILE_INPUT_STREAM (file_stream)); /* Upload the video asynchronously */ - g_output_stream_splice_async (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream), - G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, G_PRIORITY_DEFAULT, NULL, - async_ready_callback, async_data); + gdata_uploader_set_input (uploader, G_INPUT_STREAM (file_stream)); + gdata_uploader_send_async (uploader, cancellable, async_ready_callback, async_data); g_object_unref (file_stream); - g_object_unref (upload_stream); + g_object_unref (uploader); } G_STMT_END, G_STMT_START { - GOutputStream *stream = G_OUTPUT_STREAM (obj); + GDataUploader *uploader = GDATA_UPLOADER (obj); const gchar * const *tags; const gchar * const *tags2; - gssize transfer_size; + GBytes *response; + gsize transfer_size; GError *upload_error = NULL; /* Finish off the transfer */ - transfer_size = g_output_stream_splice_finish (stream, async_result, &error); + + response = gdata_uploader_send_finish (uploader, async_result, &transfer_size, &error); if (error == NULL) { g_assert_cmpint (transfer_size, >, 0); /* Finish off the upload */ - data->updated_video = gdata_youtube_service_finish_video_upload (data->service, GDATA_UPLOAD_STREAM (stream), &upload_error); + data->updated_video = gdata_youtube_service_finish_video_upload (data->service, response, &upload_error); g_assert_no_error (upload_error); g_assert (GDATA_IS_YOUTUBE_VIDEO (data->updated_video)); @@ -373,12 +375,7 @@ G_STMT_START { g_assert_cmpstr (tags2[1], ==, tags[1]); g_assert_cmpstr (tags2[2], ==, tags[2]); } else { - g_assert_cmpint (transfer_size, ==, -1); - - /* Finish off the upload */ - data->updated_video = gdata_youtube_service_finish_video_upload (data->service, GDATA_UPLOAD_STREAM (stream), &upload_error); - g_assert_no_error (upload_error); - g_assert (data->updated_video == NULL); + data->updated_video = NULL; } g_clear_error (&upload_error); -- GitLab From 99043554b1115de2badee5d3c91177b7ac669aad Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:20 +0100 Subject: [PATCH 26/45] soup3: proper async in gdata-youtube-service --- .../services/youtube/gdata-youtube-service.c | 57 +++++++++++++++---- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/gdata/services/youtube/gdata-youtube-service.c b/gdata/services/youtube/gdata-youtube-service.c index b6dd3304..7e9cbd1c 100644 --- a/gdata/services/youtube/gdata-youtube-service.c +++ b/gdata/services/youtube/gdata-youtube-service.c @@ -1132,18 +1132,33 @@ gdata_youtube_service_get_categories (GDataYouTubeService *self, GCancellable *c } static void -get_categories_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) +get_categories_async_cb (GObject *object, GAsyncResult *result, + gpointer user_data) { - GDataYouTubeService *service = GDATA_YOUTUBE_SERVICE (source_object); - g_autoptr(GDataAPPCategories) categories = NULL; - g_autoptr(GError) error = NULL; - - /* Get the categories and return */ - categories = gdata_youtube_service_get_categories (service, cancellable, &error); - if (error != NULL) - g_task_return_error (task, g_steal_pointer (&error)); + GDataService *self = GDATA_SERVICE (object); + GTask *task = user_data; + SoupMessage *message = g_task_get_task_data (task); + GDataAPPCategories *categories; + GBytes *body; + GError *error = NULL; + + body = _gdata_service_send_finish (self, result, &error); + + if (!_gdata_service_query_async_check (self, task, message, body, &error, NULL, NULL)) + return; + + categories = GDATA_APP_CATEGORIES (_gdata_parsable_new_from_json (GDATA_TYPE_APP_CATEGORIES, + g_bytes_get_data (body, NULL), g_bytes_get_size (body), + GSIZE_TO_POINTER (GDATA_TYPE_YOUTUBE_CATEGORY), + &error)); + + if (error) + g_task_return_error (task, error); else - g_task_return_pointer (task, g_steal_pointer (&categories), g_object_unref); + g_task_return_pointer (task, categories, g_object_unref); + + g_bytes_unref (body); + g_object_unref (task); } /** @@ -1166,7 +1181,10 @@ get_categories_thread (GTask *task, gpointer source_object, gpointer task_data, void gdata_youtube_service_get_categories_async (GDataYouTubeService *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { - g_autoptr(GTask) task = NULL; + const gchar *locale; + gchar *uri; + GTask *task = NULL; + SoupMessage *message; g_return_if_fail (GDATA_IS_YOUTUBE_SERVICE (self)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); @@ -1174,7 +1192,22 @@ gdata_youtube_service_get_categories_async (GDataYouTubeService *self, GCancella task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, gdata_youtube_service_get_categories_async); - g_task_run_in_thread (task, get_categories_thread); + + /* Download the category list. Note that this is (service) + * locale-dependent, and a locale must always be specified. */ + locale = gdata_service_get_locale (GDATA_SERVICE (self)); + if (locale == NULL) { + locale = "US"; + } + + uri = _gdata_service_build_uri ("https://www.googleapis.com/youtube/v3/videoCategories" + "?part=snippet" + "®ionCode=%s", + locale); + _gdata_service_query_async (GDATA_SERVICE (self), + get_youtube_authorization_domain (), + uri, NULL, get_categories_async_cb, + task, &message); } /** -- GitLab From 24d4d41b55c982cf29659b3250c2f587bb9bdf40 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:20 +0100 Subject: [PATCH 27/45] soup3: uploader in gdata-youtube-service --- .../services/youtube/gdata-youtube-service.c | 69 ++++++++----------- .../services/youtube/gdata-youtube-service.h | 8 +-- 2 files changed, 34 insertions(+), 43 deletions(-) diff --git a/gdata/services/youtube/gdata-youtube-service.c b/gdata/services/youtube/gdata-youtube-service.c index 7e9cbd1c..7c7603b0 100644 --- a/gdata/services/youtube/gdata-youtube-service.c +++ b/gdata/services/youtube/gdata-youtube-service.c @@ -77,8 +77,9 @@ * GFileInfo *file_info; * const gchar *slug, *content_type; * GFileInputStream *file_stream; - * GDataUploadStream *upload_stream; + * GDataUploader *uploader; * GError *error = NULL; + * GBytes *response; * * /* Create a service */ * service = create_youtube_service (); @@ -126,7 +127,7 @@ * g_object_unref (category); * * /* Get an upload stream for the video */ - * upload_stream = gdata_youtube_service_upload_video (service, video, slug, content_type, NULL, &error); + * uploader = gdata_youtube_service_upload_video (service, video, slug, content_type, &error); * * g_object_unref (video); * g_object_unref (file_info); @@ -140,23 +141,24 @@ * } * * /* Upload the video. This is a blocking operation, and should normally be done asynchronously. */ - * g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream), - * G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error); - * + * gdata_uploader_set_input (uploader, G_INPUT_STREAM (file_stream)); + * response = gdata_uploader_send (uploader, NULL, NULL, &error); + * g_object_unref (file_stream); * * if (error != NULL) { * g_error ("Error splicing streams: %s", error->message); * g_error_free (error); - * g_object_unref (upload_stream); + * g_object_unref (uploader); * g_object_unref (service); * return; * } * * /* Finish off the upload by parsing the returned updated video entry */ - * uploaded_video = gdata_youtube_service_finish_video_upload (service, upload_stream, &error); + * uploaded_video = gdata_youtube_service_finish_video_upload (service, response, &error); * - * g_object_unref (upload_stream); + * g_object_unref (uploader); + * g_bytes_unref (response); * g_object_unref (service); * * if (error != NULL) { @@ -403,10 +405,10 @@ append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, !gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)), get_youtube_authorization_domain ())) { const gchar *query; - SoupURI *uri; + GUri *uri; uri = soup_message_get_uri (message); - query = soup_uri_get_query (uri); + query = g_uri_get_query (uri); /* Set the key on every unauthorised request: * https://developers.google.com/youtube/v3/docs/standard_parameters#key */ @@ -420,7 +422,9 @@ append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, priv->developer_key, NULL, FALSE); - soup_uri_set_query (uri, new_query->str); + uri = soup_uri_copy (uri, SOUP_URI_QUERY, new_query->str, SOUP_URI_NONE); + soup_message_set_uri (message, uri); + g_uri_unref (uri); g_string_free (new_query, TRUE); } } @@ -977,17 +981,14 @@ gdata_youtube_service_query_related_async (GDataYouTubeService *self, GDataYouTu * * Since: 0.8.0 */ -GDataUploadStream * +GDataUploader * gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo *video, const gchar *slug, const gchar *content_type, - GCancellable *cancellable, GError **error) + GError **error) { - GOutputStream *stream = NULL; /* owned */ - g_return_val_if_fail (GDATA_IS_YOUTUBE_SERVICE (self), NULL); g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (video), NULL); g_return_val_if_fail (slug != NULL && *slug != '\0', NULL); g_return_val_if_fail (content_type != NULL && *content_type != '\0', NULL); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); if (gdata_entry_is_inserted (GDATA_ENTRY (video)) == TRUE) { @@ -1008,23 +1009,20 @@ gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo /* FIXME: Add support for resumable uploads. That means a new * gdata_youtube_service_upload_video_resumable() method a la * Documents. */ - stream = gdata_upload_stream_new (GDATA_SERVICE (self), - get_youtube_authorization_domain (), - SOUP_METHOD_POST, - "https://www.googleapis.com/upload/youtube/v3/videos" - "?part=snippet,status," - "recordingDetails", - GDATA_ENTRY (video), slug, - content_type, - cancellable); - - return GDATA_UPLOAD_STREAM (stream); + return gdata_uploader_new (GDATA_SERVICE (self), + get_youtube_authorization_domain (), + SOUP_METHOD_POST, + "https://www.googleapis.com/upload/youtube/v3/videos" + "?part=snippet,status," + "recordingDetails", + GDATA_ENTRY (video), slug, + content_type); } /** * gdata_youtube_service_finish_video_upload: * @self: a #GDataYouTubeService - * @upload_stream: the #GDataUploadStream from the operation + * @respose: the response from uploader * @error: a #GError, or %NULL * * Finish off a video upload operation started by gdata_youtube_service_upload_video(), parsing the result and returning the new #GDataYouTubeVideo. @@ -1038,24 +1036,16 @@ gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo * Since: 0.8.0 */ GDataYouTubeVideo * -gdata_youtube_service_finish_video_upload (GDataYouTubeService *self, GDataUploadStream *upload_stream, GError **error) +gdata_youtube_service_finish_video_upload (GDataYouTubeService *self, GBytes *response, GError **error) { - const gchar *response_body; - gssize response_length; g_return_val_if_fail (GDATA_IS_YOUTUBE_SERVICE (self), NULL); - g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (upload_stream), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - /* Get the response from the server */ - response_body = gdata_upload_stream_get_response (upload_stream, &response_length); - if (response_body == NULL || response_length == 0) - return NULL; - /* Parse the response to produce a GDataYouTubeVideo */ return GDATA_YOUTUBE_VIDEO (gdata_parsable_new_from_json (GDATA_TYPE_YOUTUBE_VIDEO, - response_body, - (gint) response_length, + g_bytes_get_data (response, NULL), + (gint) g_bytes_get_size (response), error)); } @@ -1126,6 +1116,7 @@ gdata_youtube_service_get_categories (GDataYouTubeService *self, GCancellable *c g_bytes_get_data (body, NULL), g_bytes_get_size (body), GSIZE_TO_POINTER (GDATA_TYPE_YOUTUBE_CATEGORY), error)); + g_bytes_unref (body); g_object_unref (message); return categories; diff --git a/gdata/services/youtube/gdata-youtube-service.h b/gdata/services/youtube/gdata-youtube-service.h index dfedb753..ae214ca1 100644 --- a/gdata/services/youtube/gdata-youtube-service.h +++ b/gdata/services/youtube/gdata-youtube-service.h @@ -136,10 +136,10 @@ void gdata_youtube_service_query_related_async (GDataYouTubeService *self, GData GDestroyNotify destroy_progress_user_data, GAsyncReadyCallback callback, gpointer user_data); -GDataUploadStream *gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo *video, const gchar *slug, - const gchar *content_type, GCancellable *cancellable, - GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; -GDataYouTubeVideo *gdata_youtube_service_finish_video_upload (GDataYouTubeService *self, GDataUploadStream *upload_stream, +GDataUploader *gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo *video, const gchar *slug, + const gchar *content_type, + GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; +GDataYouTubeVideo *gdata_youtube_service_finish_video_upload (GDataYouTubeService *self, GBytes *response, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; const gchar *gdata_youtube_service_get_developer_key (GDataYouTubeService *self) G_GNUC_PURE; -- GitLab From 099dd78c053bec2cbb7398a9903802753c72b2b5 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:21 +0100 Subject: [PATCH 28/45] soup3: adjust gdata-documents-service for GUri/new async service --- .../documents/gdata-documents-service.c | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/gdata/services/documents/gdata-documents-service.c b/gdata/services/documents/gdata-documents-service.c index a2ab71c1..536f56f7 100644 --- a/gdata/services/documents/gdata-documents-service.c +++ b/gdata/services/documents/gdata-documents-service.c @@ -309,12 +309,12 @@ append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, Soup gchar *upload_uri; const gchar *v3_pos; - upload_uri = soup_uri_to_string (soup_message_get_uri (message), FALSE); + upload_uri = g_uri_to_string_partial (soup_message_get_uri (message), G_URI_HIDE_PASSWORD); v3_pos = strstr (upload_uri, "://docs.google.com/feeds/upload/create-session/default/private/full"); if (v3_pos != NULL) { gchar *v2_upload_uri; - SoupURI *_v2_upload_uri; + GUri *_v2_upload_uri; /* Content length header for resumable uploads. Only set it if this looks like the initial request of a resumable upload, and * if no content length has been set previously. @@ -330,9 +330,9 @@ append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, Soup * to the v2 API's upload URI. Grrr. */ v2_upload_uri = g_strconcat (_gdata_service_get_scheme (), "://docs.google.com/feeds/default/private/full", v3_pos + strlen ("://docs.google.com/feeds/upload/create-session/default/private/full"), NULL); - _v2_upload_uri = soup_uri_new (v2_upload_uri); + _v2_upload_uri = g_uri_parse (v2_upload_uri, SOUP_HTTP_URI_FLAGS, NULL); soup_message_set_uri (message, _v2_upload_uri); - soup_uri_free (_v2_upload_uri); + g_uri_unref (_v2_upload_uri); g_free (v2_upload_uri); } @@ -445,8 +445,10 @@ gdata_documents_service_get_metadata (GDataDocumentsService *self, GCancellable body = _gdata_service_send (GDATA_SERVICE (self), message, cancellable, error); status = soup_message_get_status (message); - if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) { + if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ + _gdata_service_parse_soup_error (GDATA_SERVICE (self), error); + g_clear_pointer (&body, g_bytes_unref); g_object_unref (message); return NULL; } else if (status != SOUP_STATUS_OK) { @@ -1263,6 +1265,18 @@ gdata_documents_service_copy_document_finish (GDataDocumentsService *self, GAsyn return g_task_propagate_pointer (G_TASK (async_result), error); } +static void +entry_message_starting (SoupMessage *msg, gpointer body) +{ + soup_message_set_request_body_from_bytes (msg, "application/json", body); +} + +static void +entry_message_finished (SoupMessage *msg, gpointer body) +{ + g_bytes_unref (body); +} + /** * gdata_documents_service_add_entry_to_folder: * @self: an authenticated #GDataDocumentsService @@ -1350,15 +1364,21 @@ gdata_documents_service_add_entry_to_folder (GDataDocumentsService *self, GDataD /* Append the data */ upload_data = gdata_parsable_get_json (GDATA_PARSABLE (local_entry)); - soup_message_set_request (message, "application/json", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data)); + body = g_bytes_new_take (upload_data, strlen (upload_data)); + + g_signal_connect (message, "starting", G_CALLBACK (entry_message_starting), body); + g_signal_connect (message, "finished", G_CALLBACK (entry_message_finished), body); + g_object_unref (local_entry); /* Send the message */ body = _gdata_service_send (GDATA_SERVICE (self), message, cancellable, error); status = soup_message_get_status (message); - if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) { + if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ + _gdata_service_parse_soup_error (GDATA_SERVICE (self), error); + g_clear_pointer (&body, g_bytes_unref); g_object_unref (message); return NULL; } else if (status != SOUP_STATUS_OK) { @@ -1580,8 +1600,10 @@ gdata_documents_service_remove_entry_from_folder (GDataDocumentsService *self, G body = _gdata_service_send (GDATA_SERVICE (self), message, cancellable, error); status = soup_message_get_status (message); - if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) { + if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ + _gdata_service_parse_soup_error (GDATA_SERVICE (self), error); + g_clear_pointer (&body, g_bytes_unref); g_object_unref (message); return NULL; } else if (status != SOUP_STATUS_OK && status != SOUP_STATUS_NO_CONTENT) { -- GitLab From 5a535a2ee6ef14e094d9fe05419f0d399bfef719 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:21 +0100 Subject: [PATCH 29/45] soup3: add proper async code for gdata-documents-service --- .../documents/gdata-documents-service.c | 442 +++++++++++++++--- 1 file changed, 380 insertions(+), 62 deletions(-) diff --git a/gdata/services/documents/gdata-documents-service.c b/gdata/services/documents/gdata-documents-service.c index 536f56f7..99ca6607 100644 --- a/gdata/services/documents/gdata-documents-service.c +++ b/gdata/services/documents/gdata-documents-service.c @@ -477,18 +477,57 @@ gdata_documents_service_get_metadata (GDataDocumentsService *self, GCancellable } static void -get_metadata_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) +get_metadata_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) { - GDataDocumentsService *service = GDATA_DOCUMENTS_SERVICE (source_object); - g_autoptr(GDataDocumentsMetadata) metadata = NULL; - g_autoptr(GError) error = NULL; - - /* Copy the metadata and return */ - metadata = gdata_documents_service_get_metadata (service, cancellable, &error); - if (error != NULL) - g_task_return_error (task, g_steal_pointer (&error)); + GDataDocumentsService *self = GDATA_DOCUMENTS_SERVICE (object); + GDataDocumentsMetadata *metadata; + GTask *task = user_data; + SoupMessage *message = g_task_get_task_data (task); + GBytes *body; + GError *error = NULL; + guint status; + + body = _gdata_service_send_finish (GDATA_SERVICE (self), result, &error); + status = soup_message_get_status (message); + + if (!body || status == SOUP_STATUS_NONE) { + /* Redirect error or cancelled */ + _gdata_service_parse_soup_error (GDATA_SERVICE (self), &error); + g_clear_pointer (&body, g_bytes_unref); + if (error) + g_task_return_error (task, error); + else + g_task_return_pointer (task, NULL, NULL); + g_object_unref (task); + return; + } else if (status != SOUP_STATUS_OK) { + /* Error */ + GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self); + g_assert (klass->parse_error_response != NULL); + klass->parse_error_response (GDATA_SERVICE (self), GDATA_OPERATION_QUERY, status, + soup_message_get_reason_phrase (message), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), &error); + g_bytes_unref (body); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Parse the JSON; and update the entry */ + g_assert (body && g_bytes_get_data (body, NULL)); + metadata = GDATA_DOCUMENTS_METADATA (gdata_parsable_new_from_json (GDATA_TYPE_DOCUMENTS_METADATA, + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), + &error)); + + if (error) + g_task_return_error (task, error); else - g_task_return_pointer (task, g_steal_pointer (&metadata), g_object_unref); + g_task_return_pointer (task, metadata, g_object_unref); + + g_bytes_unref (body); + g_object_unref (task); } /** @@ -512,14 +551,25 @@ void gdata_documents_service_get_metadata_async (GDataDocumentsService *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { - g_autoptr(GTask) task = NULL; + GTask *task = NULL; + SoupMessage *message; + const gchar *uri = "https://www.googleapis.com/drive/v2/about"; g_return_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, gdata_documents_service_get_metadata_async); - g_task_run_in_thread (task, get_metadata_thread); + + message = _gdata_service_build_message (GDATA_SERVICE (self), + get_documents_authorization_domain (), + SOUP_METHOD_GET, uri, NULL, FALSE); + + g_task_set_task_data (task, message, g_object_unref); + + _gdata_service_send_async (GDATA_SERVICE (self), message, + g_task_get_cancellable (task), + get_metadata_async_cb, task); } /** @@ -1192,19 +1242,34 @@ gdata_documents_service_copy_document (GDataDocumentsService *self, GDataDocumen } static void -copy_document_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) +copy_document_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) { - GDataDocumentsService *service = GDATA_DOCUMENTS_SERVICE (source_object); - GDataDocumentsDocument *document = task_data; - g_autoptr(GDataDocumentsDocument) new_document = NULL; - g_autoptr(GError) error = NULL; - - /* Copy the document and return */ - new_document = gdata_documents_service_copy_document (service, document, cancellable, &error); - if (error != NULL) - g_task_return_error (task, g_steal_pointer (&error)); + GDataDocumentsService *self = GDATA_DOCUMENTS_SERVICE (object); + GDataDocumentsDocument *document; + GDataDocumentsDocument *new_document; + GDataEntry *parent; + GTask *task = user_data; + GError *error = NULL; + + document = g_task_get_task_data (task); + + parent = gdata_service_query_single_entry_finish (GDATA_SERVICE (self), result, &error); + + if (!parent) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + new_document = GDATA_DOCUMENTS_DOCUMENT (gdata_documents_service_add_entry_to_folder (self, GDATA_DOCUMENTS_ENTRY (document), GDATA_DOCUMENTS_FOLDER (parent), g_task_get_cancellable (task), &error)); + g_object_unref (parent); + + if (error) + g_task_return_error (task, error); else - g_task_return_pointer (task, g_steal_pointer (&new_document), g_object_unref); + g_task_return_pointer (task, new_document, g_object_unref); + + g_object_unref (task); } /** @@ -1229,7 +1294,10 @@ void gdata_documents_service_copy_document_async (GDataDocumentsService *self, GDataDocumentsDocument *document, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { - g_autoptr(GTask) task = NULL; + GTask *task = NULL; + GList *i; + GList *parent_folders_list; + const gchar *parent_id = NULL; g_return_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self)); g_return_if_fail (GDATA_IS_DOCUMENTS_DOCUMENT (document)); @@ -1238,7 +1306,44 @@ gdata_documents_service_copy_document_async (GDataDocumentsService *self, GDataD task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, gdata_documents_service_copy_document_async); g_task_set_task_data (task, g_object_ref (document), (GDestroyNotify) g_object_unref); - g_task_run_in_thread (task, copy_document_thread); + + if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)), + get_documents_authorization_domain ()) == FALSE) { + g_task_return_error (task, g_error_new (GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED, + _("You must be authenticated to copy documents."))); + g_object_unref (task); + return; + } + + parent_folders_list = gdata_entry_look_up_links (GDATA_ENTRY (document), 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) { + parent_id = id; + break; + } + } + + g_list_free (parent_folders_list); + + if (parent_id == NULL) { + g_task_return_error (task, g_error_new (GDATA_SERVICE_ERROR, + GDATA_SERVICE_ERROR_NOT_FOUND, + _("Parent folder not found"))); + g_object_unref (task); + return; + } + + gdata_service_query_single_entry_async (GDATA_SERVICE (self), + get_documents_authorization_domain (), + parent_id, NULL, + GDATA_TYPE_DOCUMENTS_FOLDER, + g_task_get_cancellable (task), + copy_document_async_cb, + task); } /** @@ -1409,30 +1514,72 @@ gdata_documents_service_add_entry_to_folder (GDataDocumentsService *self, GDataD typedef struct { GDataDocumentsEntry *entry; GDataDocumentsFolder *folder; + GType entry_type; + SoupMessage *message; + GDataOperationType operation_type; } AddEntryToFolderData; static void add_entry_to_folder_data_free (AddEntryToFolderData *data) { - g_object_unref (data->entry); - g_object_unref (data->folder); + g_clear_object (&data->message); g_slice_free (AddEntryToFolderData, data); } static void -add_entry_to_folder_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) +add_entry_to_folder_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) { - GDataDocumentsService *service = GDATA_DOCUMENTS_SERVICE (source_object); - g_autoptr(GDataDocumentsEntry) updated_entry = NULL; - AddEntryToFolderData *data = task_data; - g_autoptr(GError) error = NULL; - - /* Add the entry to the folder and return */ - updated_entry = gdata_documents_service_add_entry_to_folder (service, data->entry, data->folder, cancellable, &error); - if (error != NULL) - g_task_return_error (task, g_steal_pointer (&error)); + GDataDocumentsService *self = GDATA_DOCUMENTS_SERVICE (object); + GDataDocumentsEntry *new_entry; + GTask *task = user_data; + AddEntryToFolderData *data = g_task_get_task_data (task); + SoupMessage *message = data->message; + GDataOperationType operation_type = data->operation_type; + GBytes *body; + GError *error = NULL; + guint status; + + body = _gdata_service_send_finish (GDATA_SERVICE (self), result, &error); + status = soup_message_get_status (message); + + if (!body || status == SOUP_STATUS_NONE) { + /* Redirect error or cancelled */ + _gdata_service_parse_soup_error (GDATA_SERVICE (self), &error); + g_clear_pointer (&body, g_bytes_unref); + if (error) + g_task_return_error (task, error); + else + g_task_return_pointer (task, NULL, NULL); + g_object_unref (task); + return; + } else if (status != SOUP_STATUS_OK) { + /* Error */ + GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self); + g_assert (klass->parse_error_response != NULL); + klass->parse_error_response (GDATA_SERVICE (self), operation_type, status, + soup_message_get_reason_phrase (message), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), &error); + g_bytes_unref (body); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Parse the JSON; and update the entry */ + g_assert (body && g_bytes_get_data (body, NULL)); + new_entry = GDATA_DOCUMENTS_ENTRY (gdata_parsable_new_from_json (data->entry_type, + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), + &error)); + + if (error) + g_task_return_error (task, error); else - g_task_return_pointer (task, g_steal_pointer (&updated_entry), (GDestroyNotify) g_object_unref); + g_task_return_pointer (task, new_entry, g_object_unref); + + g_bytes_unref (body); + g_object_unref (task); } /** @@ -1458,8 +1605,19 @@ void gdata_documents_service_add_entry_to_folder_async (GDataDocumentsService *self, GDataDocumentsEntry *entry, GDataDocumentsFolder *folder, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { - g_autoptr(GTask) task = NULL; + GTask *task = NULL; + GDataDocumentsEntry *local_entry; + GDataOperationType operation_type; + GType entry_type; + const gchar *content_type; + const gchar *etag; + const gchar *title; AddEntryToFolderData *data; + const gchar *uri_prefix = "https://www.googleapis.com/drive/v2/files"; + gchar *upload_data; + gchar *uri; + GBytes *body; + GList *l; g_return_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self)); g_return_if_fail (GDATA_IS_DOCUMENTS_ENTRY (entry)); @@ -1467,13 +1625,72 @@ gdata_documents_service_add_entry_to_folder_async (GDataDocumentsService *self, g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); data = g_slice_new (AddEntryToFolderData); - data->entry = g_object_ref (entry); - data->folder = g_object_ref (folder); + data->message = NULL; task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, gdata_documents_service_add_entry_to_folder_async); - g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) add_entry_to_folder_data_free); - g_task_run_in_thread (task, add_entry_to_folder_thread); + g_task_set_task_data (task, data, (GDestroyNotify) add_entry_to_folder_data_free); + + if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)), + get_documents_authorization_domain ()) == FALSE) { + g_task_return_error (task, g_error_new (GDATA_SERVICE_ERROR, + GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED, + _("You must be authenticated to insert or move documents and folders."))); + g_object_unref (task); + return; + } + + if (gdata_entry_is_inserted (GDATA_ENTRY (entry)) == TRUE) { + const gchar *id; + + id = gdata_entry_get_id (GDATA_ENTRY (entry)); + uri = g_strconcat (uri_prefix, "/", id, "/copy", NULL); + operation_type = GDATA_OPERATION_UPDATE; + } else { + uri = g_strdup (uri_prefix); + operation_type = GDATA_OPERATION_INSERTION; + } + + entry_type = G_OBJECT_TYPE (entry); + content_type = gdata_documents_utils_get_content_type (entry); + etag = gdata_entry_get_etag (GDATA_ENTRY (entry)); + title = gdata_entry_get_title (GDATA_ENTRY (entry)); + local_entry = g_object_new (entry_type, "etag", etag, "title", title, NULL); + gdata_documents_utils_add_content_type (local_entry, content_type); + add_folder_link_to_entry (local_entry, folder); + + data->operation_type = operation_type; + data->entry_type = entry_type; + + for (l = gdata_documents_entry_get_document_properties (entry); l != NULL; l = l->next) { + GDataDocumentsProperty *old_prop; + g_autoptr(GDataDocumentsProperty) new_prop = NULL; + + old_prop = GDATA_DOCUMENTS_PROPERTY (l->data); + + new_prop = gdata_documents_property_new (gdata_documents_property_get_key (old_prop)); + gdata_documents_property_set_value (new_prop, gdata_documents_property_get_value (old_prop)); + gdata_documents_property_set_visibility (new_prop, gdata_documents_property_get_visibility (old_prop)); + gdata_documents_entry_add_documents_property (local_entry, new_prop); + } + + data->message = _gdata_service_build_message (GDATA_SERVICE (self), get_documents_authorization_domain (), SOUP_METHOD_POST, uri, NULL, FALSE); + g_free (uri); + + /* Append the data */ + upload_data = gdata_parsable_get_json (GDATA_PARSABLE (local_entry)); + body = g_bytes_new_take (upload_data, strlen (upload_data)); + + g_signal_connect (data->message, "starting", G_CALLBACK (entry_message_starting), body); + g_signal_connect (data->message, "restarted", G_CALLBACK (entry_message_starting), body); + g_signal_connect (data->message, "finished", G_CALLBACK (entry_message_finished), body); + + g_object_unref (local_entry); + + _gdata_service_send_async (GDATA_SERVICE (self), data->message, + g_task_get_cancellable (task), + add_entry_to_folder_async_cb, + task); } /** @@ -1633,32 +1850,65 @@ gdata_documents_service_remove_entry_from_folder (GDataDocumentsService *self, G } typedef struct { + SoupMessage *message; + GDataLink *folder_link; GDataDocumentsEntry *entry; - GDataDocumentsFolder *folder; } RemoveEntryFromFolderData; static void remove_entry_from_folder_data_free (RemoveEntryFromFolderData *data) { - g_object_unref (data->entry); - g_object_unref (data->folder); + g_clear_object (&data->message); + g_clear_object (&data->entry); g_slice_free (RemoveEntryFromFolderData, data); } static void -remove_entry_from_folder_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) +remove_entry_from_folder_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) { - GDataDocumentsService *service = GDATA_DOCUMENTS_SERVICE (source_object); - g_autoptr(GDataDocumentsEntry) updated_entry = NULL; - RemoveEntryFromFolderData *data = task_data; - g_autoptr(GError) error = NULL; - - /* Remove the entry from the folder and return */ - updated_entry = gdata_documents_service_remove_entry_from_folder (service, data->entry, data->folder, cancellable, &error); - if (error != NULL) - g_task_return_error (task, g_steal_pointer (&error)); - else - g_task_return_pointer (task, g_steal_pointer (&updated_entry), g_object_unref); + GDataDocumentsService *self = GDATA_DOCUMENTS_SERVICE (object); + GTask *task = user_data; + RemoveEntryFromFolderData *data = g_task_get_task_data (task); + GBytes *body; + GError *error = NULL; + guint status; + + body = _gdata_service_send_finish (GDATA_SERVICE (self), result, &error); + status = soup_message_get_status (data->message); + + if (!body || status == SOUP_STATUS_NONE) { + /* Redirect error or cancelled */ + _gdata_service_parse_soup_error (GDATA_SERVICE (self), &error); + g_clear_pointer (&body, g_bytes_unref); + if (error) + g_task_return_error (task, error); + else + g_task_return_pointer (task, NULL, NULL); + g_object_unref (task); + return; + } else if (status != SOUP_STATUS_OK && status != SOUP_STATUS_NO_CONTENT) { + /* Error */ + GDataServiceClass *service_klass = GDATA_SERVICE_GET_CLASS (self); + g_assert (service_klass->parse_error_response != NULL); + service_klass->parse_error_response (GDATA_SERVICE (self), + GDATA_OPERATION_DELETION, + status, + soup_message_get_reason_phrase (data->message), + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), &error); + g_bytes_unref (body); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + + /* Remove parent link from File's Data Entry */ + gdata_entry_remove_link (GDATA_ENTRY (data->entry), data->folder_link); + g_task_return_pointer (task, g_object_ref (data->entry), g_object_unref); + + g_bytes_unref (body); + g_object_unref (task); } /** @@ -1684,8 +1934,15 @@ void gdata_documents_service_remove_entry_from_folder_async (GDataDocumentsService *self, GDataDocumentsEntry *entry, GDataDocumentsFolder *folder, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { - g_autoptr(GTask) task = NULL; + GTask *task = NULL; + GDataParsableClass *klass; RemoveEntryFromFolderData *data; + GDataAuthorizationDomain *domain; + gchar *fixed_uri, *modified_uri; + const gchar *folder_id; + GList *i; + GList *parent_folders_list; + GDataLink *folder_link = NULL, *file_link = NULL; g_return_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self)); g_return_if_fail (GDATA_IS_DOCUMENTS_ENTRY (entry)); @@ -1694,12 +1951,73 @@ gdata_documents_service_remove_entry_from_folder_async (GDataDocumentsService *s data = g_slice_new (RemoveEntryFromFolderData); data->entry = g_object_ref (entry); - data->folder = g_object_ref (folder); task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, gdata_documents_service_remove_entry_from_folder_async); - g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) remove_entry_from_folder_data_free); - g_task_run_in_thread (task, remove_entry_from_folder_thread); + g_task_set_task_data (task, data, (GDestroyNotify) remove_entry_from_folder_data_free); + + if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)), + get_documents_authorization_domain ()) == FALSE) { + g_task_return_error (task, g_error_new (GDATA_SERVICE_ERROR, + GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED, + _("You must be authenticated to move documents and folders."))); + g_object_unref (task); + return; + } + + domain = gdata_documents_service_get_primary_authorization_domain(); + + folder_id = gdata_entry_get_id (GDATA_ENTRY (folder)); + g_assert (folder_id != NULL); + + parent_folders_list = gdata_entry_look_up_links (GDATA_ENTRY (entry), 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 (g_strcmp0 (folder_id, id) == 0) { + folder_link = _link; + break; + } + } + + g_list_free (parent_folders_list); + + if (folder_link == NULL) { + g_task_return_error (task, g_error_new (GDATA_SERVICE_ERROR, + GDATA_SERVICE_ERROR_NOT_FOUND, + _("Parent folder not found"))); + g_object_unref (task); + return; + } + + klass = GDATA_PARSABLE_GET_CLASS (entry); + + g_assert (klass->get_content_type != NULL); + if (g_strcmp0 (klass->get_content_type (), "application/json") == 0) { + file_link = gdata_entry_look_up_link (GDATA_ENTRY (entry), GDATA_LINK_SELF); + } else { + file_link = gdata_entry_look_up_link (GDATA_ENTRY (entry), GDATA_LINK_EDIT); + } + g_debug ("Link = %s", gdata_link_get_uri(file_link)); + g_assert (file_link != NULL); + + fixed_uri = _gdata_service_fix_uri_scheme (gdata_link_get_uri (file_link)); + modified_uri = g_strconcat (fixed_uri, "/parents/", folder_id, NULL); + + data->message = _gdata_service_build_message (GDATA_SERVICE (self), + domain, + SOUP_METHOD_DELETE, + modified_uri, + gdata_entry_get_etag (GDATA_ENTRY (entry)), + TRUE); + g_free (fixed_uri); + g_free (modified_uri); + + _gdata_service_send_async (GDATA_SERVICE (self), data->message, + g_task_get_cancellable (task), + remove_entry_from_folder_async_cb, task); } /** -- GitLab From 94413312e0872096e4af7f7626ebecc1613a1015 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:22 +0100 Subject: [PATCH 30/45] soup3: use uploader in gdata-documents-service --- .../documents/gdata-documents-service.c | 121 +++++++----------- .../documents/gdata-documents-service.h | 30 ++--- 2 files changed, 59 insertions(+), 92 deletions(-) diff --git a/gdata/services/documents/gdata-documents-service.c b/gdata/services/documents/gdata-documents-service.c index 99ca6607..8128bc18 100644 --- a/gdata/services/documents/gdata-documents-service.c +++ b/gdata/services/documents/gdata-documents-service.c @@ -48,8 +48,9 @@ * GFileInfo *file_info; * const gchar *slug, *content_type; * GFileInputStream *file_stream; - * GDataUploadStream *upload_stream; + * GDataUploader *uploader; * GError *error = NULL; + * GBytes *response; * * /* Create a service */ * service = create_documents_service (); @@ -93,7 +94,7 @@ * gdata_entry_set_title (GDATA_ENTRY (document), "Document Title"); * * /* Get an upload stream for the document */ - * upload_stream = gdata_documents_service_upload_document (service, document, slug, content_type, destination_folder, NULL, &error); + * uploader = gdata_documents_service_upload_document (service, document, slug, content_type, destination_folder, &error); * * g_object_unref (document); * g_object_unref (file_info); @@ -108,23 +109,23 @@ * } * * /* Upload the document. This is a blocking operation, and should normally be done asynchronously. */ - * g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream), - * G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error); + * gdata_uploader_set_input (uploader, G_INPUT_STREAM (file_stream)); + * response = gdata_uploader_send (uploader, cancellable, NULL, &error); * * g_object_unref (file_stream); * * if (error != NULL) { * g_error ("Error splicing streams: %s", error->message); * g_error_free (error); - * g_object_unref (upload_stream); + * g_object_unref (uploader); * g_object_unref (service); * return; * } * * /* Finish off the upload by parsing the returned updated document metadata entry */ - * uploaded_document = gdata_documents_service_finish_upload (service, upload_stream, &error); + * uploaded_document = gdata_documents_service_finish_upload (service, response, &error); * - * g_object_unref (upload_stream); + * g_object_unref (uploader); * g_object_unref (service); * * if (error != NULL) { @@ -815,10 +816,9 @@ add_folder_link_to_entry (GDataDocumentsEntry *entry, GDataDocumentsFolder *fold g_free (uri); } -static GDataUploadStream * +static GDataUploader * upload_update_document (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug, const gchar *content_type, - GDataDocumentsFolder *folder, goffset content_length, const gchar *method, const gchar *upload_uri, - GCancellable *cancellable) + GDataDocumentsFolder *folder, goffset content_length, const gchar *method, const gchar *upload_uri) { /* HACK: Corrects a bug on spreadsheet content types handling * The content type for ODF spreadsheets is "application/vnd.oasis.opendocument.spreadsheet" for my ODF spreadsheet; @@ -831,16 +831,15 @@ upload_update_document (GDataDocumentsService *self, GDataDocumentsDocument *doc if (folder != NULL) add_folder_link_to_entry (GDATA_DOCUMENTS_ENTRY (document), folder); - /* We need streaming file I/O: GDataUploadStream */ + /* We need streaming file I/O: GDataUploader */ if (content_length == -1) { /* Non-resumable upload. */ - return GDATA_UPLOAD_STREAM (gdata_upload_stream_new (GDATA_SERVICE (self), get_documents_authorization_domain (), method, upload_uri, - GDATA_ENTRY (document), slug, content_type, cancellable)); + return GDATA_UPLOADER (gdata_uploader_new (GDATA_SERVICE (self), get_documents_authorization_domain (), method, upload_uri, + GDATA_ENTRY (document), slug, content_type)); } else { /* Resumable upload. */ - return GDATA_UPLOAD_STREAM (gdata_upload_stream_new_resumable (GDATA_SERVICE (self), get_documents_authorization_domain (), method, - upload_uri, GDATA_ENTRY (document), slug, content_type, content_length, - cancellable)); + return GDATA_UPLOADER (gdata_uploader_new_resumable (GDATA_SERVICE (self), get_documents_authorization_domain (), method, + upload_uri, GDATA_ENTRY (document), slug, content_type, content_length)); } } @@ -873,7 +872,7 @@ _upload_checks (GDataDocumentsService *self, GDataDocumentsDocument *document, G * @cancellable: (allow-none): a #GCancellable for the entire upload stream, or %NULL * @error: a #GError, or %NULL * - * Uploads a document to Google Documents, using the properties from @document and the document data written to the resulting #GDataUploadStream. If + * Uploads a document to Google Documents, using the properties from @document and the document data written to the resulting #GDataUploader. If * the document data does not need to be provided at the moment, just the metadata, use gdata_service_insert_entry() instead (e.g. in the case of * creating a new, empty file to be edited at a later date). * @@ -887,21 +886,17 @@ _upload_checks (GDataDocumentsService *self, GDataDocumentsDocument *document, G * is closed (using g_output_stream_close()), gdata_documents_service_finish_upload() should be called on it to parse and return the updated * #GDataDocumentsDocument for the document. This must be done, as @document isn't updated in-place. * - * In order to cancel the upload, a #GCancellable passed in to @cancellable must be cancelled using g_cancellable_cancel(). Cancelling the individual - * #GOutputStream operations on the #GDataUploadStream will not cancel the entire upload; merely the write or close operation in question. See the - * #GDataUploadStream:cancellable for more details. - * * Any upload errors will be thrown by the stream methods, and may come from the #GDataServiceError domain. * - * Return value: (transfer full): a #GDataUploadStream to write the document data to, or %NULL; unref with g_object_unref() + * Return value: (transfer full): a #GDataUploader to write the document data to, or %NULL; unref with g_object_unref() * * Since: 0.8.0 */ -GDataUploadStream * +GDataUploader * gdata_documents_service_upload_document (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug, const gchar *content_type, - GDataDocumentsFolder *folder, GCancellable *cancellable, GError **error) + GDataDocumentsFolder *folder, GError **error) { - GDataUploadStream *upload_stream; + GDataUploader *uploader; gchar *upload_uri; gchar *upload_uri_prefix; @@ -910,7 +905,6 @@ gdata_documents_service_upload_document (GDataDocumentsService *self, GDataDocum g_return_val_if_fail (slug != NULL && *slug != '\0', NULL); g_return_val_if_fail (content_type != NULL && *content_type != '\0', NULL); g_return_val_if_fail (folder == NULL || GDATA_IS_DOCUMENTS_FOLDER (folder), NULL); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); if (_upload_checks (self, document, error) == FALSE) { @@ -919,11 +913,11 @@ gdata_documents_service_upload_document (GDataDocumentsService *self, GDataDocum upload_uri_prefix = gdata_documents_service_get_upload_uri (folder); upload_uri = g_strconcat (upload_uri_prefix, "?uploadType=multipart", NULL); - upload_stream = upload_update_document (self, document, slug, content_type, folder, -1, SOUP_METHOD_POST, upload_uri, cancellable); + uploader = upload_update_document (self, document, slug, content_type, folder, -1, SOUP_METHOD_POST, upload_uri); g_free (upload_uri); g_free (upload_uri_prefix); - return upload_stream; + return uploader; } /** @@ -937,7 +931,7 @@ gdata_documents_service_upload_document (GDataDocumentsService *self, GDataDocum * @cancellable: (allow-none): a #GCancellable for the entire upload stream, or %NULL * @error: a #GError, or %NULL * - * Uploads a document to Google Documents, using the properties from @document and the document data written to the resulting #GDataUploadStream. If + * Uploads a document to Google Documents, using the properties from @document and the document data written to the resulting #GDataUoloader. If * the document data does not need to be provided at the moment, just the metadata, use gdata_service_insert_entry() instead (e.g. in the case of * creating a new, empty file to be edited at a later date). * @@ -958,22 +952,18 @@ gdata_documents_service_upload_document (GDataDocumentsService *self, GDataDocum * is closed (using g_output_stream_close()), gdata_documents_service_finish_upload() should be called on it to parse and return the updated * #GDataDocumentsDocument for the document. This must be done, as @document isn't updated in-place. * - * In order to cancel the upload, a #GCancellable passed in to @cancellable must be cancelled using g_cancellable_cancel(). Cancelling the individual - * #GOutputStream operations on the #GDataUploadStream will not cancel the entire upload; merely the write or close operation in question. See the - * #GDataUploadStream:cancellable for more details. - * * Any upload errors will be thrown by the stream methods, and may come from the #GDataServiceError domain. * - * Return value: (transfer full): a #GDataUploadStream to write the document data to, or %NULL; unref with g_object_unref() + * Return value: (transfer full): a #GDataUploader to write the document data to, or %NULL; unref with g_object_unref() * * Since: 0.13.0 */ -GDataUploadStream * +GDataUploader * gdata_documents_service_upload_document_resumable (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug, const gchar *content_type, goffset content_length, GDataDocumentsUploadQuery *query, - GCancellable *cancellable, GError **error) + GError **error) { - GDataUploadStream *upload_stream; + GDataUploader *uploader; gchar *upload_uri; g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self), NULL); @@ -981,7 +971,6 @@ gdata_documents_service_upload_document_resumable (GDataDocumentsService *self, g_return_val_if_fail (slug != NULL && *slug != '\0', NULL); g_return_val_if_fail (content_type != NULL && *content_type != '\0', NULL); g_return_val_if_fail (query == NULL || GDATA_IS_DOCUMENTS_UPLOAD_QUERY (query), NULL); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); if (_upload_checks (self, document, error) == FALSE) { @@ -989,10 +978,10 @@ gdata_documents_service_upload_document_resumable (GDataDocumentsService *self, } upload_uri = _get_upload_uri_for_query_and_folder (query, NULL); - upload_stream = upload_update_document (self, document, slug, content_type, NULL, content_length, SOUP_METHOD_POST, upload_uri, cancellable); + uploader = upload_update_document (self, document, slug, content_type, NULL, content_length, SOUP_METHOD_POST, upload_uri); g_free (upload_uri); - return upload_stream; + return uploader; } static gboolean @@ -1017,7 +1006,7 @@ _update_checks (GDataDocumentsService *self, GError **error) * @cancellable: (allow-none): a #GCancellable for the entire upload stream, or %NULL * @error: a #GError, or %NULL * - * Update the document using the properties from @document and the document data written to the resulting #GDataUploadStream. If the document data does + * Update the document using the properties from @document and the document data written to the resulting #GDataUploader. If the document data does * not need to be changed, just the metadata, use gdata_service_update_entry() instead. * * This performs a non-resumable upload, unlike gdata_documents_service_update_document(). This means that errors during transmission will cause the @@ -1028,23 +1017,19 @@ _update_checks (GDataDocumentsService *self, GError **error) * is closed (using g_output_stream_close()), gdata_documents_service_finish_upload() should be called on it to parse and return the updated * #GDataDocumentsDocument for the document. This must be done, as @document isn't updated in-place. * - * In order to cancel the update, a #GCancellable passed in to @cancellable must be cancelled using g_cancellable_cancel(). Cancelling the individual - * #GOutputStream operations on the #GDataUploadStream will not cancel the entire update; merely the write or close operation in question. See the - * #GDataUploadStream:cancellable for more details. - * * Any upload errors will be thrown by the stream methods, and may come from the #GDataServiceError domain. * * For more information, see gdata_service_update_entry(). * - * Return value: (transfer full): a #GDataUploadStream to write the document data to; unref with g_object_unref() + * Return value: (transfer full): a #GDataUploader to write the document data to; unref with g_object_unref() * * Since: 0.8.0 */ -GDataUploadStream * +GDataUploader * gdata_documents_service_update_document (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug, const gchar *content_type, - GCancellable *cancellable, GError **error) + GError **error) { - GDataUploadStream *update_stream; + GDataUploader *update_stream; const gchar *id; gchar *update_uri; gchar *update_uri_prefix; @@ -1053,7 +1038,6 @@ gdata_documents_service_update_document (GDataDocumentsService *self, GDataDocum g_return_val_if_fail (GDATA_IS_DOCUMENTS_DOCUMENT (document), NULL); g_return_val_if_fail (slug != NULL && *slug != '\0', NULL); g_return_val_if_fail (content_type != NULL && *content_type != '\0', NULL); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); if (_update_checks (self, error) == FALSE) { @@ -1063,7 +1047,7 @@ gdata_documents_service_update_document (GDataDocumentsService *self, GDataDocum update_uri_prefix = gdata_documents_service_get_upload_uri (NULL); id = gdata_entry_get_id (GDATA_ENTRY (document)); update_uri = g_strconcat (update_uri_prefix, "/", id, "?uploadType=multipart", NULL); - update_stream = upload_update_document (self, document, slug, content_type, NULL, -1, SOUP_METHOD_PUT, update_uri, cancellable); + update_stream = upload_update_document (self, document, slug, content_type, NULL, -1, SOUP_METHOD_PUT, update_uri); g_free (update_uri); g_free (update_uri_prefix); @@ -1080,7 +1064,7 @@ gdata_documents_service_update_document (GDataDocumentsService *self, GDataDocum * @cancellable: (allow-none): a #GCancellable for the entire upload stream, or %NULL * @error: a #GError, or %NULL * - * Update the document using the properties from @document and the document data written to the resulting #GDataUploadStream. If the document data does + * Update the document using the properties from @document and the document data written to the resulting #GDataUploader. If the document data does * not need to be changed, just the metadata, use gdata_service_update_entry() instead. * * Unlike gdata_documents_service_update_document(), this method performs a @@ -1091,21 +1075,17 @@ gdata_documents_service_update_document (GDataDocumentsService *self, GDataDocum * is closed (using g_output_stream_close()), gdata_documents_service_finish_upload() should be called on it to parse and return the updated * #GDataDocumentsDocument for the document. This must be done, as @document isn't updated in-place. * - * In order to cancel the update, a #GCancellable passed in to @cancellable must be cancelled using g_cancellable_cancel(). Cancelling the individual - * #GOutputStream operations on the #GDataUploadStream will not cancel the entire update; merely the write or close operation in question. See the - * #GDataUploadStream:cancellable for more details. - * * Any upload errors will be thrown by the stream methods, and may come from the #GDataServiceError domain. * * For more information, see gdata_service_update_entry(). * - * Return value: (transfer full): a #GDataUploadStream to write the document data to; unref with g_object_unref() + * Return value: (transfer full): a #GDataUploader to write the document data to; unref with g_object_unref() * * Since: 0.13.0 */ -GDataUploadStream * +GDataUploader * gdata_documents_service_update_document_resumable (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug, - const gchar *content_type, goffset content_length, GCancellable *cancellable, GError **error) + const gchar *content_type, goffset content_length, GError **error) { GDataLink *update_link; @@ -1113,7 +1093,6 @@ gdata_documents_service_update_document_resumable (GDataDocumentsService *self, g_return_val_if_fail (GDATA_IS_DOCUMENTS_DOCUMENT (document), NULL); g_return_val_if_fail (slug != NULL && *slug != '\0', NULL); g_return_val_if_fail (content_type != NULL && *content_type != '\0', NULL); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); if (_update_checks (self, error) == FALSE) { @@ -1123,14 +1102,13 @@ gdata_documents_service_update_document_resumable (GDataDocumentsService *self, update_link = gdata_entry_look_up_link (GDATA_ENTRY (document), GDATA_LINK_RESUMABLE_EDIT_MEDIA); g_assert (update_link != NULL); - return upload_update_document (self, document, slug, content_type, NULL, content_length, SOUP_METHOD_PUT, gdata_link_get_uri (update_link), - cancellable); + return upload_update_document (self, document, slug, content_type, NULL, content_length, SOUP_METHOD_PUT, gdata_link_get_uri (update_link)); } /** * gdata_documents_service_finish_upload: * @self: a #GDataDocumentsService - * @upload_stream: the #GDataUploadStream from the operation + * @uploader: the #GDataUploader from the operation * @error: a #GError, or %NULL * * Finish off a document upload or update operation started by gdata_documents_service_upload_document() or gdata_documents_service_update_document(), @@ -1149,21 +1127,12 @@ gdata_documents_service_update_document_resumable (GDataDocumentsService *self, * Since: 0.8.0 */ GDataDocumentsDocument * -gdata_documents_service_finish_upload (GDataDocumentsService *self, GDataUploadStream *upload_stream, GError **error) +gdata_documents_service_finish_upload (GDataDocumentsService *self, GDataUploader *uploader, GBytes *response, GError **error) { const gchar *content_type; - const gchar *response_body; - gssize response_length; GType new_document_type = G_TYPE_INVALID; - /* Get and parse the response from the server */ - response_body = gdata_upload_stream_get_response (upload_stream, &response_length); - if (response_body == NULL || response_length == 0) { - /* Error will have been set by the upload stream. */ - return NULL; - } - - content_type = gdata_upload_stream_get_content_type (upload_stream); + content_type = gdata_uploader_get_content_type (uploader); new_document_type = gdata_documents_utils_get_type_from_content_type (content_type); if (g_type_is_a (new_document_type, GDATA_TYPE_DOCUMENTS_DOCUMENT) == FALSE) { @@ -1173,7 +1142,7 @@ gdata_documents_service_finish_upload (GDataDocumentsService *self, GDataUploadS return NULL; } - return GDATA_DOCUMENTS_DOCUMENT (gdata_parsable_new_from_json (new_document_type, response_body, (gint) response_length, error)); + return GDATA_DOCUMENTS_DOCUMENT (gdata_parsable_new_from_json (new_document_type, g_bytes_get_data (response, NULL), (gint) g_bytes_get_size (response), error)); } /** @@ -1512,8 +1481,6 @@ gdata_documents_service_add_entry_to_folder (GDataDocumentsService *self, GDataD } typedef struct { - GDataDocumentsEntry *entry; - GDataDocumentsFolder *folder; GType entry_type; SoupMessage *message; GDataOperationType operation_type; diff --git a/gdata/services/documents/gdata-documents-service.h b/gdata/services/documents/gdata-documents-service.h index c16a623c..d18607e8 100644 --- a/gdata/services/documents/gdata-documents-service.h +++ b/gdata/services/documents/gdata-documents-service.h @@ -125,22 +125,22 @@ void gdata_documents_service_query_drives_async (GDataDocumentsService *self, GD #include #include -GDataUploadStream *gdata_documents_service_upload_document (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug, - const gchar *content_type, GDataDocumentsFolder *folder, - GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; -GDataUploadStream *gdata_documents_service_upload_document_resumable (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug, - const gchar *content_type, goffset content_length, - GDataDocumentsUploadQuery *query, - GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; - -GDataUploadStream *gdata_documents_service_update_document (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug, - const gchar *content_type, GCancellable *cancellable, - GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; -GDataUploadStream *gdata_documents_service_update_document_resumable (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug, - const gchar *content_type, goffset content_length, GCancellable *cancellable, - GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; +GDataUploader *gdata_documents_service_upload_document (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug, + const gchar *content_type, GDataDocumentsFolder *folder, + GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; +GDataUploader *gdata_documents_service_upload_document_resumable (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug, + const gchar *content_type, goffset content_length, + GDataDocumentsUploadQuery *query, + GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; + +GDataUploader *gdata_documents_service_update_document (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug, + const gchar *content_type, + GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; +GDataUploader *gdata_documents_service_update_document_resumable (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug, + const gchar *content_type, goffset content_length, + GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; -GDataDocumentsDocument *gdata_documents_service_finish_upload (GDataDocumentsService *self, GDataUploadStream *upload_stream, +GDataDocumentsDocument *gdata_documents_service_finish_upload (GDataDocumentsService *self, GDataUploader *uploader, GBytes *response, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; GDataDocumentsDocument *gdata_documents_service_copy_document (GDataDocumentsService *self, GDataDocumentsDocument *document, -- GitLab From 780e5939a0c25d6ab60b7acce43dd5f063cb744e Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:22 +0100 Subject: [PATCH 31/45] soup3: add async for gdata-picasaweb-service --- .../picasaweb/gdata-picasaweb-service.c | 68 +++++++++++++++---- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/gdata/services/picasaweb/gdata-picasaweb-service.c b/gdata/services/picasaweb/gdata-picasaweb-service.c index c98c9a07..64304865 100644 --- a/gdata/services/picasaweb/gdata-picasaweb-service.c +++ b/gdata/services/picasaweb/gdata-picasaweb-service.c @@ -259,21 +259,45 @@ gdata_picasaweb_service_get_user (GDataPicasaWebService *self, const gchar *user return GDATA_PICASAWEB_USER (user); } +typedef struct { + SoupMessage *message; + gchar *username; +} GetUserData; + static void -get_user_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) +get_user_data_free (GetUserData *data) { - GDataPicasaWebService *service = GDATA_PICASAWEB_SERVICE (source_object); - const gchar *username = task_data; - g_autoptr(GDataPicasaWebUser) user = NULL; - g_autoptr(GError) error = NULL; + g_clear_object (&data->message); + g_free (data->username); + g_slice_free (GetUserData, data); +} - /* Get the user and return */ - user = gdata_picasaweb_service_get_user (service, username, cancellable, &error); +static void +get_user_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) +{ + GDataService *self = GDATA_SERVICE (object); + GTask *task = user_data; + SoupMessage *message = g_task_get_task_data (task); + GDataParsable *user; + GBytes *body; + GError *error = NULL; - if (error != NULL) - g_task_return_error (task, g_steal_pointer (&error)); + body = _gdata_service_send_finish (self, result, &error); + + if (!_gdata_service_query_async_check (self, task, message, body, &error, NULL, NULL)) + return; + + user = gdata_parsable_new_from_xml (GDATA_TYPE_PICASAWEB_USER, + g_bytes_get_data (body, NULL), + g_bytes_get_size (body), &error); + + if (error) + g_task_return_error (task, error); else - g_task_return_pointer (task, g_steal_pointer (&user), g_object_unref); + g_task_return_pointer (task, user, g_object_unref); + + g_bytes_unref (body); + g_object_unref (task); } /** @@ -297,16 +321,34 @@ void gdata_picasaweb_service_get_user_async (GDataPicasaWebService *self, const gchar *username, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { - g_autoptr(GTask) task = NULL; + GTask *task = NULL; + GetUserData *data; + gchar *uri; g_return_if_fail (GDATA_IS_PICASAWEB_SERVICE (self)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (callback != NULL); + data = g_slice_new (GetUserData); + data->username = g_strdup (username); + task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, gdata_picasaweb_service_get_user_async); - g_task_set_task_data (task, g_strdup (username), (GDestroyNotify) g_free); - g_task_run_in_thread (task, get_user_thread); + g_task_set_task_data (task, data, (GDestroyNotify) get_user_data_free); + + uri = create_uri (self, username, "entry"); + if (uri == NULL) { + g_task_return_error (task, g_error_new (GDATA_SERVICE_ERROR, + GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED, + _("You must specify a username or be authenticated to query a user."))); + g_object_unref (task); + return; + } + + _gdata_service_query_async (GDATA_SERVICE (self), + get_picasaweb_authorization_domain (), + uri, NULL, get_user_async_cb, task, &data->message); + g_free (uri); } /** -- GitLab From 5bcfdf4a5621123d3fa3f5544e99bb4c7252092d Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 18 Nov 2021 21:00:22 +0100 Subject: [PATCH 32/45] soup3: use uploader in gdata-picasaweb-service --- .../picasaweb/gdata-picasaweb-service.c | 51 +++++++------------ .../picasaweb/gdata-picasaweb-service.h | 6 +-- 2 files changed, 22 insertions(+), 35 deletions(-) diff --git a/gdata/services/picasaweb/gdata-picasaweb-service.c b/gdata/services/picasaweb/gdata-picasaweb-service.c index 64304865..dbefea3e 100644 --- a/gdata/services/picasaweb/gdata-picasaweb-service.c +++ b/gdata/services/picasaweb/gdata-picasaweb-service.c @@ -61,10 +61,11 @@ * Uploading a Photo or Video * * GDataPicasaWebFile *file_entry, *uploaded_file_entry; - * GDataUploadStream *upload_stream; + * GDataUploader *uploader; * GFile *file_data; * GFileInfo *file_info; * GFileInputStream *file_stream; + * GBytes *response; * * /* Specify the GFile image on disk to upload */ * file_data = g_file_new_for_path (path); @@ -80,8 +81,8 @@ * gdata_entry_set_summary (GDATA_ENTRY (file_entry), "Photo of the world's most beautiful cat."); * * /* Create an upload stream for the file. This is non-blocking. */ - * upload_stream = gdata_picasaweb_service_upload_file (service, album, file_entry, g_file_info_get_display_name (file_info), - * g_file_info_get_content_type (file_info), NULL, NULL); + * uploader = gdata_picasaweb_service_upload_file (service, album, file_entry, g_file_info_get_display_name (file_info), + * g_file_info_get_content_type (file_info), NULL); * g_object_unref (file_info); * g_object_unref (file_entry); * @@ -90,13 +91,14 @@ * g_object_unref (file_data); * * /* Upload the file to the server. Note that this is a blocking operation. */ - * g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream), - * G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, NULL); + * gdata_uploader_set_input (uploader, G_INPUT_STREAM (file_stream); + * response = gdata_uploader_send (uploader, NULL, NULL, &error); * * /* Parse the resulting updated entry. This is a non-blocking operation. */ - * uploaded_file_entry = gdata_picasaweb_service_finish_file_upload (service, upload_stream, NULL); + * uploaded_file_entry = gdata_picasaweb_service_finish_file_upload (service, response, NULL); * g_object_unref (file_stream); - * g_object_unref (upload_stream); + * g_object_unref (uploader); + * g_bytes_unref (response); * * /* ... */ * @@ -608,7 +610,6 @@ gdata_picasaweb_service_query_files_async (GDataPicasaWebService *self, GDataPic * @file_entry: a #GDataPicasaWebFile to insert * @slug: the filename to give to the uploaded file * @content_type: the content type of the uploaded data - * @cancellable: (allow-none): a #GCancellable for the entire upload stream, or %NULL * @error: a #GError, or %NULL * * Uploads a file (photo or video) to the given PicasaWeb @album, using the metadata from @file and the file data written to the resulting @@ -624,22 +625,18 @@ gdata_picasaweb_service_query_files_async (GDataPicasaWebService *self, GDataPic * is closed (using g_output_stream_close()), gdata_picasaweb_service_finish_file_upload() should be called on it to parse and return the updated * #GDataPicasaWebFile for the uploaded file. This must be done, as @file_entry isn't updated in-place. * - * In order to cancel the upload, a #GCancellable passed in to @cancellable must be cancelled using g_cancellable_cancel(). Cancelling the individual - * #GOutputStream operations on the #GDataUploadStream will not cancel the entire upload; merely the write or close operation in question. See the - * #GDataUploadStream:cancellable for more details. - * * Any upload errors will be thrown by the stream methods, and may come from the #GDataServiceError domain. * - * Return value: (transfer full): a #GDataUploadStream to write the file data to, or %NULL; unref with g_object_unref() + * Return value: (transfer full): a #GDataUploader to write the file data to, or %NULL; unref with g_object_unref() * * Since: 0.8.0 */ -GDataUploadStream * +GDataUploader * gdata_picasaweb_service_upload_file (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GDataPicasaWebFile *file_entry, const gchar *slug, - const gchar *content_type, GCancellable *cancellable, GError **error) + const gchar *content_type, GError **error) { const gchar *album_id = NULL; - GDataUploadStream *upload_stream; + GDataUploader *uploader; gchar *upload_uri; g_return_val_if_fail (GDATA_IS_PICASAWEB_SERVICE (self), NULL); @@ -647,7 +644,6 @@ gdata_picasaweb_service_upload_file (GDataPicasaWebService *self, GDataPicasaWeb g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (file_entry), NULL); g_return_val_if_fail (slug != NULL && *slug != '\0', NULL); g_return_val_if_fail (content_type != NULL && *content_type != '\0', NULL); - g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); if (gdata_entry_is_inserted (GDATA_ENTRY (file_entry)) == TRUE) { @@ -668,17 +664,17 @@ gdata_picasaweb_service_upload_file (GDataPicasaWebService *self, GDataPicasaWeb /* Build the upload URI and upload stream */ upload_uri = _gdata_service_build_uri ("https://picasaweb.google.com/data/feed/api/user/default/albumid/%s", album_id); - upload_stream = GDATA_UPLOAD_STREAM (gdata_upload_stream_new (GDATA_SERVICE (self), get_picasaweb_authorization_domain (), SOUP_METHOD_POST, - upload_uri, GDATA_ENTRY (file_entry), slug, content_type, cancellable)); + uploader = GDATA_UPLOADER (gdata_uploader_new (GDATA_SERVICE (self), get_picasaweb_authorization_domain (), SOUP_METHOD_POST, + upload_uri, GDATA_ENTRY (file_entry), slug, content_type)); g_free (upload_uri); - return upload_stream; + return uploader; } /** * gdata_picasaweb_service_finish_file_upload: * @self: a #GDataPicasaWebService - * @upload_stream: the #GDataUploadStream from the operation + * @response: the response from the uploader * @error: a #GError, or %NULL * * Finish off a file upload operation started by gdata_picasaweb_service_upload_file(), parsing the result and returning the new #GDataPicasaWebFile. @@ -692,22 +688,13 @@ gdata_picasaweb_service_upload_file (GDataPicasaWebService *self, GDataPicasaWeb * Since: 0.8.0 */ GDataPicasaWebFile * -gdata_picasaweb_service_finish_file_upload (GDataPicasaWebService *self, GDataUploadStream *upload_stream, GError **error) +gdata_picasaweb_service_finish_file_upload (GDataPicasaWebService *self, GBytes *response, GError **error) { - const gchar *response_body; - gssize response_length; - g_return_val_if_fail (GDATA_IS_PICASAWEB_SERVICE (self), NULL); - g_return_val_if_fail (GDATA_IS_UPLOAD_STREAM (upload_stream), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - /* Get the response from the server */ - response_body = gdata_upload_stream_get_response (upload_stream, &response_length); - if (response_body == NULL || response_length == 0) - return NULL; - /* Parse the response to produce a GDataPicasaWebFile */ - return GDATA_PICASAWEB_FILE (gdata_parsable_new_from_xml (GDATA_TYPE_PICASAWEB_FILE, response_body, (gint) response_length, error)); + return GDATA_PICASAWEB_FILE (gdata_parsable_new_from_xml (GDATA_TYPE_PICASAWEB_FILE, g_bytes_get_data (response, NULL), (gint) g_bytes_get_size (response), error)); } /** diff --git a/gdata/services/picasaweb/gdata-picasaweb-service.h b/gdata/services/picasaweb/gdata-picasaweb-service.h index c5f31082..d3fcbd4a 100644 --- a/gdata/services/picasaweb/gdata-picasaweb-service.h +++ b/gdata/services/picasaweb/gdata-picasaweb-service.h @@ -104,10 +104,10 @@ void gdata_picasaweb_service_query_files_async (GDataPicasaWebService *self, GDa #include -GDataUploadStream *gdata_picasaweb_service_upload_file (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GDataPicasaWebFile *file_entry, - const gchar *slug, const gchar *content_type, GCancellable *cancellable, +GDataUploader *gdata_picasaweb_service_upload_file (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GDataPicasaWebFile *file_entry, + const gchar *slug, const gchar *content_type, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; -GDataPicasaWebFile *gdata_picasaweb_service_finish_file_upload (GDataPicasaWebService *self, GDataUploadStream *upload_stream, +GDataPicasaWebFile *gdata_picasaweb_service_finish_file_upload (GDataPicasaWebService *self, GBytes *response, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; GDataPicasaWebAlbum *gdata_picasaweb_service_insert_album (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GCancellable *cancellable, -- GitLab From ef8a2e88630afb0152c9dfe5a6cc9ed404fcc61b Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Tue, 23 Nov 2021 19:50:45 +0100 Subject: [PATCH 33/45] soup3: switch meson.build --- gdata/meson.build | 4 ++-- meson.build | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/gdata/meson.build b/gdata/meson.build index 6acc32c5..e21c59ea 100644 --- a/gdata/meson.build +++ b/gdata/meson.build @@ -166,8 +166,8 @@ pkgconfig.generate( ) if get_option('introspection') - gdata_gir_includes = ['GObject-2.0', 'libxml2-2.0', 'Soup-2.4', 'Json-1.0'] - gdata_vapi_deps = ['libxml-2.0', 'libsoup-2.4', 'json-glib-1.0'] + gdata_gir_includes = ['GObject-2.0', 'libxml2-2.0', 'Soup-3.0', 'Json-1.0'] + gdata_vapi_deps = ['libxml-2.0', 'libsoup-3.0', 'json-glib-1.0'] if enable_goa gdata_gir_includes += ['Goa-1.0'] diff --git a/meson.build b/meson.build index d2f2d625..3d0199fc 100644 --- a/meson.build +++ b/meson.build @@ -92,11 +92,8 @@ gdata_private_deps = [ dependency('gthread-2.0'), ] -libsoup_dep = dependency('libsoup-2.4', version: '>= 2.42.0') +libsoup_dep = dependency('libsoup-3.0', version: '>= 3.0.0') gdata_deps += libsoup_dep -# libsoup 2.47.3 is needed for the new SoupServer API; but it contained a bug in -# soup_server_set_ssl_cert_file() which was only fixed in 2.55.90. -config_h.set('HAVE_LIBSOUP_2_55_90', libsoup_dep.version().version_compare('>= 2.55.90')) # Check for gtk gtk_dep_req_version = '>= 2.91.2' -- GitLab From 58f9d804f0ed61a11a28a88f675042b5945873b8 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Tue, 23 Nov 2021 19:53:30 +0100 Subject: [PATCH 34/45] soup3: switch demos to uploader --- demos/scrapbook/scrapbook.c | 18 ++++++++++-------- demos/youtube/youtube-cli.c | 25 ++++++++++++------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/demos/scrapbook/scrapbook.c b/demos/scrapbook/scrapbook.c index 9e330cb8..92a8570f 100644 --- a/demos/scrapbook/scrapbook.c +++ b/demos/scrapbook/scrapbook.c @@ -532,8 +532,9 @@ select_file (ScrapPUpload *self, GtkFileChooser *file_chooser) GFile *file; GError *error = NULL; GFileInfo *file_info; - GDataUploadStream *upload_stream; + GDataUploader *uploader; GFileInputStream *file_stream; + GBytes *response; file = gtk_file_chooser_get_file (file_chooser); @@ -543,9 +544,9 @@ select_file (ScrapPUpload *self, GtkFileChooser *file_chooser) /* upload our file, using the service we've set up, and metadata * set up in upload () * no album is specified, but that should be easy to add */ - upload_stream = gdata_picasaweb_service_upload_file (self->main_data->picasaweb_service, NULL /* for now uploading to drop box */, - self->file, g_file_info_get_display_name (file_info), - g_file_info_get_content_type (file_info), NULL, &error); + uploader = gdata_picasaweb_service_upload_file (self->main_data->picasaweb_service, NULL /* for now uploading to drop box */, + self->file, g_file_info_get_display_name (file_info), + g_file_info_get_content_type (file_info), &error); g_object_unref (file_info); g_object_unref (self->file); @@ -563,13 +564,14 @@ select_file (ScrapPUpload *self, GtkFileChooser *file_chooser) file_stream = g_file_read (file, NULL, NULL); g_object_unref (file); - g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream), - G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, NULL); + gdata_uploader_set_input (uploader, G_INPUT_STREAM (file_stream)); + response = gdata_uploader_send (uploader, NULL, NULL, NULL); - self->file = gdata_picasaweb_service_finish_file_upload (self->main_data->picasaweb_service, upload_stream, NULL); + self->file = gdata_picasaweb_service_finish_file_upload (self->main_data->picasaweb_service, response, NULL); g_object_unref (file_stream); - g_object_unref (upload_stream); + g_object_unref (uploader); + g_bytes_unref (response); } static void diff --git a/demos/youtube/youtube-cli.c b/demos/youtube/youtube-cli.c index 35f3d6a7..0043bd97 100644 --- a/demos/youtube/youtube-cli.c +++ b/demos/youtube/youtube-cli.c @@ -404,7 +404,7 @@ static int command_upload (int argc, char *argv[]) { GDataYouTubeService *service = NULL; - GDataUploadStream *upload_stream = NULL; + GDataUploader *uploader = NULL; GError *error = NULL; gint retval = 0; const gchar *filename; @@ -413,7 +413,8 @@ command_upload (int argc, char *argv[]) GFileInfo *video_file_info = NULL; GDataYouTubeVideo *video = NULL; GDataYouTubeVideo *uploaded_video = NULL; - gssize transfer_size; + GBytes *response; + gsize transfer_size; const gchar *content_type, *slug; GDataAuthorizer *authorizer = NULL; const gchar *title, *description; @@ -481,9 +482,9 @@ command_upload (int argc, char *argv[]) GDATA_AUTHORIZER (authorizer)); /* Start the upload. */ - upload_stream = gdata_youtube_service_upload_video (service, video, - slug, content_type, - NULL, &error); + uploader = gdata_youtube_service_upload_video (service, video, + slug, content_type, + &error); if (error != NULL) { g_printerr ("%s: Error initializing upload with YouTube: %s\n", @@ -494,11 +495,9 @@ command_upload (int argc, char *argv[]) } /* Upload the video */ - transfer_size = g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), - G_INPUT_STREAM (video_file_stream), - G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | - G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, - NULL, &error); + + gdata_uploader_set_input (uploader, G_INPUT_STREAM (video_file_stream)); + response = gdata_uploader_send (uploader, NULL, &transfer_size, &error); if (error != NULL) { g_printerr ("%s: Error transferring file: %s\n", @@ -510,7 +509,7 @@ command_upload (int argc, char *argv[]) /* Finish off the upload */ uploaded_video = gdata_youtube_service_finish_video_upload (service, - upload_stream, + response, &error); if (error != NULL) { @@ -522,7 +521,7 @@ command_upload (int argc, char *argv[]) } /* Print the uploaded video as confirmation. */ - g_print ("Uploaded %" G_GSSIZE_FORMAT " bytes.\n", transfer_size); + g_print ("Uploaded %" G_GSIZE_FORMAT " bytes.\n", transfer_size); print_video (uploaded_video); done: @@ -532,7 +531,7 @@ done: g_clear_object (&video_file_info); g_clear_object (&video_file_stream); g_clear_object (&video_file); - g_clear_object (&upload_stream); + g_clear_object (&uploader); g_clear_object (&service); return retval; -- GitLab From e4124924b5c03852e66be87d008207f70f2d1347 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Tue, 23 Nov 2021 20:00:23 +0100 Subject: [PATCH 35/45] soup3: meson: increment versions This is incomplete and needs to be coordinated. --- meson.build | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index 3d0199fc..39c8d3f1 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'libgdata', 'c', - version: '0.19.0', + version: '1.0.0', license: 'LGPL2.1+', default_options: 'buildtype=debugoptimized', meson_version: '>= 0.50.0', @@ -15,7 +15,7 @@ gdata_version_minor = ver_arr[1].to_int() gdata_version_micro = ver_arr[2].to_int() # API version -gdata_api_version_major = 0 +gdata_api_version_major = 1 gdata_api_version_minor = 0 # Define the install directories @@ -34,9 +34,9 @@ gdata_include_subdir = gdata_name / 'gdata' # 4. If any interfaces have been removed or changed since the last public release, then set age to 0. # # Note that versioning started at 2:0:0 to ensure no conflicts with e-d-s' libgdata library, whose maximum version was 1:0:0 -current = 28 +current = 30 revision = 0 -age = 6 +age = 0 gdata_soversion = '@0@.@1@.@2@'.format(current - age, age, revision) top_inc_dir = include_directories('.') -- GitLab From 635e870e607390629c5033d4521bff50e595444d Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 3 Mar 2022 23:04:25 +0100 Subject: [PATCH 36/45] soup3: use GBytes for _gdata_feed_new_from_(xml|json) --- gdata/gdata-access-handler.c | 12 ++++-------- gdata/gdata-feed.c | 12 ++++++++---- gdata/gdata-private.h | 4 ++-- gdata/gdata-service.c | 4 ++-- gdata/services/calendar/gdata-calendar-calendar.c | 8 ++------ gdata/services/documents/gdata-documents-entry.c | 6 ++---- 6 files changed, 20 insertions(+), 26 deletions(-) diff --git a/gdata/gdata-access-handler.c b/gdata/gdata-access-handler.c index 3ae921d9..5fdecee3 100644 --- a/gdata/gdata-access-handler.c +++ b/gdata/gdata-access-handler.c @@ -127,15 +127,13 @@ gdata_access_handler_real_get_rules (GDataAccessHandler *self, if (g_strcmp0 (content_type, "application/json") == 0) { /* Definitely JSON. */ g_debug("JSON content type detected."); - feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, g_bytes_get_data (body, NULL), - g_bytes_get_size (body), GDATA_TYPE_ACCESS_RULE, + feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, body, GDATA_TYPE_ACCESS_RULE, progress_callback, progress_user_data, error); } else { /* Potentially XML. Don't bother checking the Content-Type, since the parser * will fail gracefully if the response body is not valid XML. */ g_debug("XML content type detected."); - feed = _gdata_feed_new_from_xml (GDATA_TYPE_FEED, g_bytes_get_data (body, NULL), - g_bytes_get_size (body), GDATA_TYPE_ACCESS_RULE, + feed = _gdata_feed_new_from_xml (GDATA_TYPE_FEED, body, GDATA_TYPE_ACCESS_RULE, progress_callback, progress_user_data, error); } @@ -168,15 +166,13 @@ get_rules_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) if (g_strcmp0 (content_type, "application/json") == 0) { /* Definitely JSON. */ g_debug("JSON content type detected."); - feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, g_bytes_get_data (body, NULL), - g_bytes_get_size (body), GDATA_TYPE_ACCESS_RULE, + feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, body, GDATA_TYPE_ACCESS_RULE, data->progress_callback, data->progress_user_data, &error); } else { /* Potentially XML. Don't bother checking the Content-Type, since the parser * will fail gracefully if the response body is not valid XML. */ g_debug("XML content type detected."); - feed = _gdata_feed_new_from_xml (GDATA_TYPE_FEED, g_bytes_get_data (body, NULL), - g_bytes_get_size (body), GDATA_TYPE_ACCESS_RULE, + feed = _gdata_feed_new_from_xml (GDATA_TYPE_FEED, body, GDATA_TYPE_ACCESS_RULE, data->progress_callback, data->progress_user_data, &error); } diff --git a/gdata/gdata-feed.c b/gdata/gdata-feed.c index e3777543..c7bffa71 100644 --- a/gdata/gdata-feed.c +++ b/gdata/gdata-feed.c @@ -718,11 +718,13 @@ _gdata_feed_new (GType feed_type, } GDataFeed * -_gdata_feed_new_from_xml (GType feed_type, const gchar *xml, gint length, GType entry_type, +_gdata_feed_new_from_xml (GType feed_type, GBytes *xml, GType entry_type, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error) { ParseData *data; GDataFeed *feed; + gsize size = 0; + gconstpointer xmlp = g_bytes_get_data (xml, &size); g_return_val_if_fail (g_type_is_a (feed_type, GDATA_TYPE_FEED), NULL); g_return_val_if_fail (xml != NULL, NULL); @@ -730,18 +732,20 @@ _gdata_feed_new_from_xml (GType feed_type, const gchar *xml, gint length, GType g_return_val_if_fail (error == NULL || *error == NULL, NULL); data = _gdata_feed_parse_data_new (entry_type, progress_callback, progress_user_data); - feed = GDATA_FEED (_gdata_parsable_new_from_xml (feed_type, xml, length, data, error)); + feed = GDATA_FEED (_gdata_parsable_new_from_xml (feed_type, xmlp, size, data, error)); _gdata_feed_parse_data_free (data); return feed; } GDataFeed * -_gdata_feed_new_from_json (GType feed_type, const gchar *json, gint length, GType entry_type, +_gdata_feed_new_from_json (GType feed_type, GBytes *json, GType entry_type, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error) { ParseData *data; GDataFeed *feed; + gsize size = 0; + gconstpointer jsonp = g_bytes_get_data (json, &size); g_return_val_if_fail (g_type_is_a (feed_type, GDATA_TYPE_FEED), NULL); g_return_val_if_fail (json != NULL, NULL); @@ -749,7 +753,7 @@ _gdata_feed_new_from_json (GType feed_type, const gchar *json, gint length, GTyp g_return_val_if_fail (error == NULL || *error == NULL, NULL); data = _gdata_feed_parse_data_new (entry_type, progress_callback, progress_user_data); - feed = GDATA_FEED (_gdata_parsable_new_from_json (feed_type, json, length, data, error)); + feed = GDATA_FEED (_gdata_parsable_new_from_json (feed_type, jsonp, size, data, error)); _gdata_feed_parse_data_free (data); return feed; diff --git a/gdata/gdata-private.h b/gdata/gdata-private.h index 79fab4f5..3eef4e96 100644 --- a/gdata/gdata-private.h +++ b/gdata/gdata-private.h @@ -115,10 +115,10 @@ G_GNUC_INTERNAL GDataFeed *_gdata_feed_new (GType feed_type, const gchar *title, const gchar *id, gint64 updated) G_GNUC_WARN_UNUSED_RESULT; -G_GNUC_INTERNAL GDataFeed *_gdata_feed_new_from_xml (GType feed_type, const gchar *xml, gint length, GType entry_type, +G_GNUC_INTERNAL GDataFeed *_gdata_feed_new_from_xml (GType feed_type, GBytes *xml, GType entry_type, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; -G_GNUC_INTERNAL GDataFeed *_gdata_feed_new_from_json (GType feed_type, const gchar *json, gint length, GType entry_type, +G_GNUC_INTERNAL GDataFeed *_gdata_feed_new_from_json (GType feed_type, GBytes *json, GType entry_type, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC; G_GNUC_INTERNAL void _gdata_feed_add_entry (GDataFeed *self, GDataEntry *entry); diff --git a/gdata/gdata-service.c b/gdata/gdata-service.c index 24785986..70bce7d4 100644 --- a/gdata/gdata-service.c +++ b/gdata/gdata-service.c @@ -1143,13 +1143,13 @@ real_parse_feed (GDataService *self, if (content_type != NULL && strcmp (content_type, "application/json") == 0) { /* Definitely JSON. */ g_debug("JSON content type detected."); - feed = _gdata_feed_new_from_json (klass->feed_type, g_bytes_get_data (body, NULL), g_bytes_get_size (body), entry_type, + feed = _gdata_feed_new_from_json (klass->feed_type, body, entry_type, progress_callback, progress_user_data, error); } else { /* Potentially XML. Don't bother checking the Content-Type, since the parser * will fail gracefully if the response body is not valid XML. */ g_debug("XML content type detected."); - feed = _gdata_feed_new_from_xml (klass->feed_type, g_bytes_get_data (body, NULL), g_bytes_get_size (body), entry_type, + feed = _gdata_feed_new_from_xml (klass->feed_type, body, entry_type, progress_callback, progress_user_data, error); } diff --git a/gdata/services/calendar/gdata-calendar-calendar.c b/gdata/services/calendar/gdata-calendar-calendar.c index 52ba8854..dc9dd498 100644 --- a/gdata/services/calendar/gdata-calendar-calendar.c +++ b/gdata/services/calendar/gdata-calendar-calendar.c @@ -265,9 +265,7 @@ get_rules (GDataAccessHandler *self, g_assert (body && g_bytes_get_data (body, NULL)); - feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, - g_bytes_get_data (body, NULL), - g_bytes_get_size (body), + feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, body, GDATA_TYPE_CALENDAR_ACCESS_RULE, progress_callback, progress_user_data, error); @@ -323,9 +321,7 @@ get_rules_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) if (!_gdata_service_query_async_check (service, task, data->message, body, &error, data->destroy_progress_user_data, data->progress_user_data)) return; - feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, - g_bytes_get_data (body, NULL), - g_bytes_get_size (body), + feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, body, GDATA_TYPE_CALENDAR_ACCESS_RULE, data->progress_callback, data->progress_user_data, diff --git a/gdata/services/documents/gdata-documents-entry.c b/gdata/services/documents/gdata-documents-entry.c index 167e1d99..9884f1b3 100644 --- a/gdata/services/documents/gdata-documents-entry.c +++ b/gdata/services/documents/gdata-documents-entry.c @@ -366,8 +366,7 @@ get_rules (GDataAccessHandler *self, g_assert (body && g_bytes_get_data (body, NULL)); - feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, g_bytes_get_data (body, NULL), - g_bytes_get_size (body), + feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, body, GDATA_TYPE_DOCUMENTS_ACCESS_RULE, progress_callback, progress_user_data, error); @@ -392,8 +391,7 @@ get_rules_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) if (!_gdata_service_query_async_check (service, task, data->message, body, &error, data->destroy_progress_user_data, data->progress_user_data)) return; - feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, g_bytes_get_data (body, NULL), - g_bytes_get_size (body), + feed = _gdata_feed_new_from_json (GDATA_TYPE_FEED, body, GDATA_TYPE_DOCUMENTS_ACCESS_RULE, data->progress_callback, data->progress_user_data, &error); -- GitLab From 118ff278924ccd13ebc9b81b4ea15ba58f658310 Mon Sep 17 00:00:00 2001 From: Daniel Kolesa Date: Thu, 3 Mar 2022 23:50:15 +0100 Subject: [PATCH 37/45] soup3: use g_autoptr in various places --- demos/scrapbook/scrapbook.c | 24 ++-- demos/youtube/youtube-cli.c | 3 +- gdata/gdata-access-handler.c | 15 +-- gdata/gdata-batch-operation.c | 28 ++--- gdata/gdata-oauth2-authorizer.c | 54 ++------- gdata/gdata-service.c | 108 ++++-------------- .../calendar/gdata-calendar-calendar.c | 15 +-- .../documents/gdata-documents-entry.c | 15 +-- .../documents/gdata-documents-service.c | 80 +++---------- .../picasaweb/gdata-picasaweb-service.c | 15 +-- .../services/youtube/gdata-youtube-service.c | 13 +-- gdata/tests/documents.c | 9 +- gdata/tests/picasaweb.c | 6 +- gdata/tests/streams.c | 7 +- gdata/tests/youtube.c | 4 +- 15 files changed, 98 insertions(+), 298 deletions(-) diff --git a/demos/scrapbook/scrapbook.c b/demos/scrapbook/scrapbook.c index 92a8570f..caaff40e 100644 --- a/demos/scrapbook/scrapbook.c +++ b/demos/scrapbook/scrapbook.c @@ -529,12 +529,12 @@ properties_show (GtkWidget *widget, ScrapData *first) static void select_file (ScrapPUpload *self, GtkFileChooser *file_chooser) { - GFile *file; + g_autoptr(GFile) file = NULL; + g_autoptr(GFileInfo) file_info = NULL; + g_autoptr(GDataUploader) uploader = NULL; + g_autoptr(GFileInputStream) file_stream = NULL; + g_autoptr(GBytes) response = NULL; GError *error = NULL; - GFileInfo *file_info; - GDataUploader *uploader; - GFileInputStream *file_stream; - GBytes *response; file = gtk_file_chooser_get_file (file_chooser); @@ -547,31 +547,21 @@ select_file (ScrapPUpload *self, GtkFileChooser *file_chooser) uploader = gdata_picasaweb_service_upload_file (self->main_data->picasaweb_service, NULL /* for now uploading to drop box */, self->file, g_file_info_get_display_name (file_info), g_file_info_get_content_type (file_info), &error); - - g_object_unref (file_info); - g_object_unref (self->file); - self->file = NULL; + g_clear_object (&self->file); if (error != NULL) { g_print ("Error: %s\n", error->message); g_error_free (error); - - g_object_unref (file); - return; } file_stream = g_file_read (file, NULL, NULL); - g_object_unref (file); + g_clear_object (&file); gdata_uploader_set_input (uploader, G_INPUT_STREAM (file_stream)); response = gdata_uploader_send (uploader, NULL, NULL, NULL); self->file = gdata_picasaweb_service_finish_file_upload (self->main_data->picasaweb_service, response, NULL); - - g_object_unref (file_stream); - g_object_unref (uploader); - g_bytes_unref (response); } static void diff --git a/demos/youtube/youtube-cli.c b/demos/youtube/youtube-cli.c index 0043bd97..64c3c349 100644 --- a/demos/youtube/youtube-cli.c +++ b/demos/youtube/youtube-cli.c @@ -404,7 +404,7 @@ static int command_upload (int argc, char *argv[]) { GDataYouTubeService *service = NULL; - GDataUploader *uploader = NULL; + g_autoptr(GDataUploader) uploader = NULL; GError *error = NULL; gint retval = 0; const gchar *filename; @@ -531,7 +531,6 @@ done: g_clear_object (&video_file_info); g_clear_object (&video_file_stream); g_clear_object (&video_file); - g_clear_object (&uploader); g_clear_object (&service); return retval; diff --git a/gdata/gdata-access-handler.c b/gdata/gdata-access-handler.c index 5fdecee3..71ac41c8 100644 --- a/gdata/gdata-access-handler.c +++ b/gdata/gdata-access-handler.c @@ -101,10 +101,10 @@ gdata_access_handler_real_get_rules (GDataAccessHandler *self, GDataAuthorizationDomain *domain = NULL; GDataFeed *feed; GDataLink *_link; - SoupMessage *message; + g_autoptr(SoupMessage) message = NULL; SoupMessageHeaders *headers; const gchar *content_type; - GBytes *body = NULL; + g_autoptr(GBytes) body = NULL; _link = gdata_entry_look_up_link (GDATA_ENTRY (self), GDATA_LINK_ACCESS_CONTROL_LIST); g_assert (_link != NULL); @@ -137,9 +137,6 @@ gdata_access_handler_real_get_rules (GDataAccessHandler *self, progress_callback, progress_user_data, error); } - g_bytes_unref (body); - g_object_unref (message); - return feed; } @@ -147,12 +144,12 @@ static void get_rules_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GDataService *service = GDATA_SERVICE (object); - GTask *task = user_data; + g_autoptr(GTask) task = user_data; GetRulesAsyncData *data = g_task_get_task_data (task); SoupMessageHeaders *headers; const gchar *content_type; GDataFeed *feed; - GBytes *body; + g_autoptr(GBytes) body = NULL; GError *error = NULL; body = _gdata_service_send_finish (service, result, &error); @@ -176,8 +173,6 @@ get_rules_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) data->progress_callback, data->progress_user_data, &error); } - g_bytes_unref (body); - if (data->destroy_progress_user_data != NULL) { data->destroy_progress_user_data (data->progress_user_data); } @@ -186,8 +181,6 @@ get_rules_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) g_task_return_error (task, error); else g_task_return_pointer (task, feed, g_object_unref); - - g_object_unref (task); } static void diff --git a/gdata/gdata-batch-operation.c b/gdata/gdata-batch-operation.c index 44fc814b..deea029d 100644 --- a/gdata/gdata-batch-operation.c +++ b/gdata/gdata-batch-operation.c @@ -689,13 +689,13 @@ gboolean gdata_batch_operation_run (GDataBatchOperation *self, GCancellable *cancellable, GError **error) { GDataBatchOperationPrivate *priv = self->priv; - SoupMessage *message; + g_autoptr(SoupMessage) message = NULL; guint status; GHashTableIter iter; gpointer op_id; BatchOperation *op; GError *child_error = NULL; - GBytes *body; + g_autoptr(GBytes) body = NULL; GDataFeed *feed; g_return_val_if_fail (GDATA_IS_BATCH_OPERATION (self), FALSE); @@ -732,9 +732,6 @@ gdata_batch_operation_run (GDataBatchOperation *self, GCancellable *cancellable, g_bytes_get_data (body, NULL), g_bytes_get_size (body), &child_error); } - g_clear_pointer (&body, g_bytes_unref); - g_object_unref (message); - goto error; } @@ -744,8 +741,6 @@ gdata_batch_operation_run (GDataBatchOperation *self, GCancellable *cancellable, g_bytes_get_data (body, NULL), g_bytes_get_size (body), self, &child_error)); - g_bytes_unref (body); - g_object_unref (message); if (feed == NULL) goto error; @@ -767,17 +762,17 @@ error: static void run_cb (GObject *object, GAsyncResult *result, gpointer user_data) { - GTask *task = user_data; - GDataBatchOperation *self = g_task_get_task_data (task); + g_autoptr(GTask) task = user_data; + GDataBatchOperation *self = g_task_get_task_data (task); GDataBatchOperationPrivate *priv = self->priv; - GBytes *body; - GDataFeed *feed; - GError *error = NULL; + GBytes *body; + GDataFeed *feed; + GError *error = NULL; guint status; GHashTableIter iter; gpointer op_id; BatchOperation *op; - SoupMessage *message = priv->message; + g_autoptr(SoupMessage) message = priv->message; /* Check for early cancellation. */ if (g_cancellable_set_error_if_cancelled (g_task_get_cancellable (task), &error)) { @@ -803,7 +798,6 @@ run_cb (GObject *object, GAsyncResult *result, gpointer user_data) g_bytes_get_data (body, NULL), g_bytes_get_size (body), &error); } - g_clear_pointer (&body, g_bytes_unref); goto error; } @@ -813,26 +807,20 @@ run_cb (GObject *object, GAsyncResult *result, gpointer user_data) g_bytes_get_data (body, NULL), g_bytes_get_size (body), self, &error)); - g_bytes_unref (body); if (!feed) goto error; g_task_return_boolean (task, TRUE); - g_object_unref (task); - return; error: - g_object_unref (message); - /* Call the callbacks for each of our operations to notify them of the error */ g_hash_table_iter_init (&iter, priv->operations); while (g_hash_table_iter_next (&iter, &op_id, (gpointer*) &op) == TRUE) _gdata_batch_operation_run_callback (self, op, NULL, g_error_copy (error)); g_task_return_error (task, error); - g_object_unref (task); } /** diff --git a/gdata/gdata-oauth2-authorizer.c b/gdata/gdata-oauth2-authorizer.c index 809ff7cf..472c7261 100644 --- a/gdata/gdata-oauth2-authorizer.c +++ b/gdata/gdata-oauth2-authorizer.c @@ -675,10 +675,10 @@ refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, { /* See http://code.google.com/apis/accounts/docs/OAuth2.html#IAMoreToken */ GDataOAuth2AuthorizerPrivate *priv; - SoupMessage *message = NULL; /* owned */ + g_autoptr(SoupMessage) message = NULL; guint status; GError *child_error = NULL; - GBytes *body; + g_autoptr(GBytes) body = NULL; g_return_val_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self), FALSE); @@ -698,7 +698,6 @@ refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, status = soup_message_get_status (message); if (!body) { - g_object_unref (message); return FALSE; } else if (status != SOUP_STATUS_OK) { parse_grant_error (GDATA_OAUTH2_AUTHORIZER (self), @@ -706,9 +705,6 @@ refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, g_bytes_get_data (body, NULL), g_bytes_get_size (body), error); - g_bytes_unref (body); - g_object_unref (message); - return FALSE; } @@ -718,9 +714,6 @@ refresh_authorization (GDataAuthorizer *self, GCancellable *cancellable, g_bytes_get_data (body, NULL), g_bytes_get_size (body), &child_error); - g_bytes_unref (body); - g_object_unref (message); - if (child_error != NULL) { g_propagate_error (error, child_error); return FALSE; @@ -733,10 +726,10 @@ static void refresh_authorization_cb (GObject *object, GAsyncResult *result, gpointer user_data) { SoupSession *session = SOUP_SESSION (object); - SoupMessage *message = soup_session_get_async_result_message (session, result); - GTask *task = user_data; - ;GDataAuthorizer *self = g_task_get_task_data (task); - GBytes *body; + g_autoptr(SoupMessage) message = soup_session_get_async_result_message (session, result); + g_autoptr(GTask) task = user_data; + GDataAuthorizer *self = g_task_get_task_data (task); + g_autoptr(GBytes) body = NULL; guint status; GError *error = NULL; @@ -744,9 +737,7 @@ refresh_authorization_cb (GObject *object, GAsyncResult *result, gpointer user_d status = soup_message_get_status (message); if (!body) { - g_object_unref (message); g_task_return_error (task, error); - g_object_unref (task); return; } else if (status != SOUP_STATUS_OK) { parse_grant_error (GDATA_OAUTH2_AUTHORIZER (self), @@ -754,10 +745,7 @@ refresh_authorization_cb (GObject *object, GAsyncResult *result, gpointer user_d g_bytes_get_data (body, NULL), g_bytes_get_size (body), &error); - g_bytes_unref (body); - g_object_unref (message); g_task_return_error (task, error); - g_object_unref (task); return; } @@ -767,15 +755,10 @@ refresh_authorization_cb (GObject *object, GAsyncResult *result, gpointer user_d g_bytes_get_data (body, NULL), g_bytes_get_size (body), &error); - g_bytes_unref (body); - g_object_unref (message); - if (error != NULL) g_task_return_error (task, error); else g_task_return_boolean (task, TRUE); - - g_object_unref (task); } static void @@ -1235,12 +1218,12 @@ gdata_oauth2_authorizer_request_authorization (GDataOAuth2Authorizer *self, GError **error) { GDataOAuth2AuthorizerPrivate *priv; - SoupMessage *message = NULL; /* owned */ + g_autoptr(SoupMessage) message = NULL; GUri *_uri = NULL; /* owned */ gchar *request_body = NULL; /* owned */ guint status; GError *child_error = NULL; - GBytes *body; + g_autoptr(GBytes) body = NULL; g_return_val_if_fail (GDATA_IS_OAUTH2_AUTHORIZER (self), FALSE); g_return_val_if_fail (authorization_code != NULL && @@ -1287,16 +1270,12 @@ gdata_oauth2_authorizer_request_authorization (GDataOAuth2Authorizer *self, if (!body) { /* Cancelled (the error has already been set) */ - g_object_unref (message); return FALSE; } else if (status != SOUP_STATUS_OK) { parse_grant_error (self, status, soup_message_get_reason_phrase (message), g_bytes_get_data (body, NULL), g_bytes_get_size (body), error); - g_bytes_unref (body); - g_object_unref (message); - return FALSE; } @@ -1305,9 +1284,6 @@ gdata_oauth2_authorizer_request_authorization (GDataOAuth2Authorizer *self, g_bytes_get_data (body, NULL), g_bytes_get_size (body), &child_error); - g_bytes_unref (body); - g_object_unref (message); - if (child_error != NULL) { g_propagate_error (error, child_error); return FALSE; @@ -1320,8 +1296,8 @@ static void request_authorization_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) { SoupSession *session = SOUP_SESSION (object); - GTask *task = user_data; - GBytes *body; + g_autoptr(GTask) task = user_data; + g_autoptr(GBytes) body = NULL; GError *error = NULL; SoupMessage *message = soup_session_get_async_result_message (session, result); GDataOAuth2Authorizer *self = g_task_get_task_data (task); @@ -1332,23 +1308,18 @@ request_authorization_async_cb (GObject *object, GAsyncResult *result, gpointer if (!body) { /* Cancelled (the error has already been set) */ - g_object_unref (message); g_task_return_error (task, error); - g_object_unref (task); return; } else if (status != SOUP_STATUS_OK) { parse_grant_error (self, status, soup_message_get_reason_phrase (message), g_bytes_get_data (body, NULL), g_bytes_get_size (body), &error); - g_bytes_unref (body); - g_object_unref (message); if (error) { g_task_return_error (task, error); } else { g_task_return_boolean (task, FALSE); } - g_object_unref (task); return; } @@ -1357,17 +1328,12 @@ request_authorization_async_cb (GObject *object, GAsyncResult *result, gpointer g_bytes_get_data (body, NULL), g_bytes_get_size (body), &error); - g_bytes_unref (body); - g_object_unref (message); - if (error != NULL) { g_task_return_error (task, error); - g_object_unref (task); return; } g_task_return_boolean (task, TRUE); - g_object_unref (task); } /** diff --git a/gdata/gdata-service.c b/gdata/gdata-service.c index 70bce7d4..b6b40d64 100644 --- a/gdata/gdata-service.c +++ b/gdata/gdata-service.c @@ -867,11 +867,11 @@ query_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) GDataService *self = GDATA_SERVICE (object); GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self); GDataFeed *feed; - GTask *task = user_data; + g_autoptr(GTask) task = user_data; QueryAsyncData *data = g_task_get_task_data (task); SoupMessage *message = data->message; GError *error = NULL; - GBytes *body; + g_autoptr(GBytes) body = NULL; body = _gdata_service_send_finish (self, result, &error); @@ -886,8 +886,6 @@ query_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) data->progress_callback, data->progress_user_data, &error); - g_bytes_unref (body); - if (data->destroy_progress_user_data != NULL) { data->destroy_progress_user_data (data->progress_user_data); } @@ -896,8 +894,6 @@ query_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) g_task_return_error (task, error); else g_task_return_pointer (task, feed, g_object_unref); - - g_object_unref (task); } void @@ -945,14 +941,12 @@ _gdata_service_query_async_check (GDataService *self, if (!body || status == SOUP_STATUS_NOT_MODIFIED) { _gdata_service_parse_soup_error (self, error); /* Not modified (ETag has worked), or cancelled (in which case the error has been set) */ - g_clear_pointer (&body, g_bytes_unref); if (destroy_cb) destroy_cb (user_data); if (*error) g_task_return_error (task, *error); else g_task_return_pointer (task, NULL, NULL); - g_object_unref (task); return FALSE; } else if (status != SOUP_STATUS_OK) { /* Error */ @@ -962,11 +956,9 @@ _gdata_service_query_async_check (GDataService *self, soup_message_get_reason_phrase (message), g_bytes_get_data (body, NULL), g_bytes_get_size (body), error); - g_bytes_unref (body); if (destroy_cb) destroy_cb (user_data); g_task_return_error (task, *error); - g_object_unref (task); return FALSE; } return TRUE; @@ -1074,8 +1066,8 @@ SoupMessage * _gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const gchar *feed_uri, GDataQuery *query, GCancellable *cancellable, GBytes **body, GError **error) { - SoupMessage *message; - GBytes *ret; + g_autoptr(SoupMessage) message = NULL; + g_autoptr(GBytes) ret = NULL; guint status; const gchar *etag = NULL; @@ -1099,8 +1091,6 @@ _gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, cons if (!ret || status == SOUP_STATUS_NOT_MODIFIED) { _gdata_service_parse_soup_error (self, error); /* Not modified (ETag has worked), or cancelled (in which case the error has been set) */ - g_object_unref (message); - g_clear_pointer (&ret, g_bytes_unref); return NULL; } else if (status != SOUP_STATUS_OK) { /* Error */ @@ -1110,13 +1100,11 @@ _gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, cons soup_message_get_reason_phrase (message), g_bytes_get_data (ret, NULL), g_bytes_get_size (ret), error); - g_bytes_unref (ret); - g_object_unref (message); return NULL; } - *body = ret; - return message; + *body = g_bytes_ref (ret); + return g_object_ref (message); } static GDataFeed * @@ -1223,9 +1211,9 @@ gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const GCancellable *cancellable, GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error) { GDataServiceClass *klass; - SoupMessage *message; + g_autoptr(SoupMessage) message = NULL; GDataFeed *feed; - GBytes *body = NULL; + g_autoptr(GBytes) body = NULL; g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL); g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL); @@ -1256,9 +1244,6 @@ gdata_service_query (GDataService *self, GDataAuthorizationDomain *domain, const message, body, cancellable, progress_callback, progress_user_data, error); - g_bytes_unref (body); - g_object_unref (message); - return feed; } @@ -1292,9 +1277,9 @@ gdata_service_query_single_entry (GDataService *self, GDataAuthorizationDomain * GDataEntryClass *klass; GDataEntry *entry; gchar *entry_uri; - SoupMessage *message; + g_autoptr(SoupMessage) message = NULL; SoupMessageHeaders *headers; - GBytes *body = NULL; + g_autoptr(GBytes) body = NULL; const gchar *content_type; g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL); @@ -1329,8 +1314,6 @@ gdata_service_query_single_entry (GDataService *self, GDataAuthorizationDomain * entry = GDATA_ENTRY (gdata_parsable_new_from_xml (entry_type, g_bytes_get_data (body, NULL), g_bytes_get_size (body), error)); } - g_bytes_unref (body); - g_object_unref (message); g_type_class_unref (klass); return entry; @@ -1341,12 +1324,12 @@ query_single_entry_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GDataService *self = GDATA_SERVICE (object); - GTask *task = user_data; + g_autoptr(GTask) task = user_data; QueryAsyncData *data = g_task_get_task_data (task); SoupMessage *message = data->message; SoupMessageHeaders *headers; GError *error = NULL; - GBytes *body; + g_autoptr(GBytes) body = NULL; GDataEntry *entry; const gchar *content_type; @@ -1370,14 +1353,10 @@ query_single_entry_async_cb (GObject *object, GAsyncResult *result, &error)); } - g_bytes_unref (body); - if (error) g_task_return_error (task, error); else g_task_return_pointer (task, entry, g_object_unref); - - g_object_unref (task); } /** @@ -1497,12 +1476,12 @@ static void insert_entry_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GDataService *self = GDATA_SERVICE (object); - GTask *task = user_data; + g_autoptr(GTask) task = user_data; InsertEntryAsyncData *data = g_task_get_task_data (task); SoupMessage *message = data->message; GDataParsableClass *klass; guint status; - GBytes *body; + g_autoptr(GBytes) body = NULL; GError *error = NULL; GDataEntry *entry = data->entry; GDataEntry *updated_entry; @@ -1513,12 +1492,10 @@ static void insert_entry_async_cb (GObject *object, GAsyncResult *result, if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ _gdata_service_parse_soup_error (self, &error); - g_clear_pointer (&body, g_bytes_unref); if (error) g_task_return_error (task, error); else g_task_return_pointer (task, NULL, NULL); - g_object_unref (task); return; } else if (status != SOUP_STATUS_CREATED && status != SOUP_STATUS_OK) { /* Error: for XML APIs Google returns CREATED and for JSON it returns OK. */ @@ -1528,9 +1505,7 @@ static void insert_entry_async_cb (GObject *object, GAsyncResult *result, soup_message_get_reason_phrase (message), g_bytes_get_data (body, NULL), g_bytes_get_size (body), &error); - g_bytes_unref (body); g_task_return_error (task, error); - g_object_unref (task); return; } @@ -1546,14 +1521,11 @@ static void insert_entry_async_cb (GObject *object, GAsyncResult *result, g_bytes_get_data (body, NULL), g_bytes_get_size (body), &error)); } - g_bytes_unref (body); if (error) g_task_return_error (task, error); else g_task_return_pointer (task, updated_entry, g_object_unref); - - g_object_unref (task); } /** @@ -1690,11 +1662,11 @@ gdata_service_insert_entry (GDataService *self, GDataAuthorizationDomain *domain GCancellable *cancellable, GError **error) { GDataEntry *updated_entry; - SoupMessage *message; + g_autoptr(SoupMessage) message = NULL; gchar *upload_data; guint status; GDataParsableClass *klass; - GBytes *body; + g_autoptr(GBytes) body = NULL; g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL); g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL); @@ -1732,8 +1704,6 @@ gdata_service_insert_entry (GDataService *self, GDataAuthorizationDomain *domain if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ _gdata_service_parse_soup_error (self, error); - g_clear_pointer (&body, g_bytes_unref); - g_object_unref (message); return NULL; } else if (status != SOUP_STATUS_CREATED && status != SOUP_STATUS_OK) { /* Error: for XML APIs Google returns CREATED and for JSON it returns OK. */ @@ -1743,8 +1713,6 @@ gdata_service_insert_entry (GDataService *self, GDataAuthorizationDomain *domain soup_message_get_reason_phrase (message), g_bytes_get_data (body, NULL), g_bytes_get_size (body), error); - g_bytes_unref (body); - g_object_unref (message); return NULL; } @@ -1758,8 +1726,6 @@ gdata_service_insert_entry (GDataService *self, GDataAuthorizationDomain *domain g_bytes_get_data (body, NULL), g_bytes_get_size (body), error)); } - g_bytes_unref (body); - g_object_unref (message); return updated_entry; } @@ -1783,12 +1749,12 @@ static void update_entry_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GDataService *self = GDATA_SERVICE (object); - GTask *task = user_data; + g_autoptr(GTask) task = user_data; InsertEntryAsyncData *data = g_task_get_task_data (task); SoupMessage *message = data->message; GDataParsableClass *klass; guint status; - GBytes *body; + g_autoptr(GBytes) body = NULL; GError *error = NULL; GDataEntry *entry = data->entry; GDataEntry *updated_entry; @@ -1799,12 +1765,10 @@ static void update_entry_async_cb (GObject *object, GAsyncResult *result, if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ _gdata_service_parse_soup_error (self, &error); - g_clear_pointer (&body, g_bytes_unref); if (error) g_task_return_error (task, error); else g_task_return_pointer (task, NULL, NULL); - g_object_unref (task); return; } else if (status != SOUP_STATUS_OK) { /* Error */ @@ -1814,9 +1778,7 @@ static void update_entry_async_cb (GObject *object, GAsyncResult *result, soup_message_get_reason_phrase (message), g_bytes_get_data (body, NULL), g_bytes_get_size (body), &error); - g_bytes_unref (body); g_task_return_error (task, error); - g_object_unref (task); return; } @@ -1830,14 +1792,11 @@ static void update_entry_async_cb (GObject *object, GAsyncResult *result, updated_entry = GDATA_ENTRY (gdata_parsable_new_from_xml (G_OBJECT_TYPE (entry), g_bytes_get_data (body, NULL), g_bytes_get_size (body), &error)); } - g_bytes_unref (body); if (error) g_task_return_error (task, error); else g_task_return_pointer (task, updated_entry, g_object_unref); - - g_object_unref (task); } /** @@ -1966,11 +1925,11 @@ gdata_service_update_entry (GDataService *self, GDataAuthorizationDomain *domain { GDataEntry *updated_entry; GDataLink *_link; - SoupMessage *message; + g_autoptr(SoupMessage) message = NULL; gchar *upload_data; guint status; GDataParsableClass *klass; - GBytes *body; + g_autoptr(GBytes) body = NULL; g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL); g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), NULL); @@ -2007,8 +1966,6 @@ gdata_service_update_entry (GDataService *self, GDataAuthorizationDomain *domain if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ _gdata_service_parse_soup_error (self, error); - g_clear_pointer (&body, g_bytes_unref); - g_object_unref (message); return NULL; } else if (status != SOUP_STATUS_OK) { /* Error */ @@ -2018,8 +1975,6 @@ gdata_service_update_entry (GDataService *self, GDataAuthorizationDomain *domain soup_message_get_reason_phrase (message), g_bytes_get_data (body, NULL), g_bytes_get_size (body), error); - g_bytes_unref (body); - g_object_unref (message); return NULL; } @@ -2031,8 +1986,6 @@ gdata_service_update_entry (GDataService *self, GDataAuthorizationDomain *domain updated_entry = GDATA_ENTRY (gdata_parsable_new_from_xml (G_OBJECT_TYPE (entry), g_bytes_get_data (body, NULL), g_bytes_get_size (body), error)); } - g_bytes_unref (body); - g_object_unref (message); return updated_entry; } @@ -2054,11 +2007,11 @@ static void delete_entry_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GDataService *self = GDATA_SERVICE (object); - GTask *task = user_data; + g_autoptr(GTask) task = user_data; InsertEntryAsyncData *data = g_task_get_task_data (task); SoupMessage *message = data->message; guint status; - GBytes *body; + g_autoptr(GBytes) body = NULL; GError *error = NULL; body = _gdata_service_send_finish (self, result, &error); @@ -2067,12 +2020,10 @@ static void delete_entry_async_cb (GObject *object, GAsyncResult *result, if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ _gdata_service_parse_soup_error (self, &error); - g_clear_pointer (&body, g_bytes_unref); if (error) g_task_return_error (task, error); else g_task_return_boolean (task, FALSE); - g_object_unref (task); return; } else if (status != SOUP_STATUS_OK && status != SOUP_STATUS_NO_CONTENT) { /* Error */ @@ -2082,15 +2033,11 @@ static void delete_entry_async_cb (GObject *object, GAsyncResult *result, soup_message_get_reason_phrase (message), g_bytes_get_data (body, NULL), g_bytes_get_size (body), &error); - g_bytes_unref (body); g_task_return_error (task, error); - g_object_unref (task); return; } - g_bytes_unref (body); g_task_return_boolean (task, TRUE); - g_object_unref (task); } /** @@ -2206,11 +2153,11 @@ gboolean gdata_service_delete_entry (GDataService *self, GDataAuthorizationDomain *domain, GDataEntry *entry, GCancellable *cancellable, GError **error) { GDataLink *_link; - SoupMessage *message; + g_autoptr(SoupMessage) message = NULL; guint status; gchar *fixed_uri; GDataParsableClass *klass; - GBytes *body; + g_autoptr(GBytes) body = NULL; g_return_val_if_fail (GDATA_IS_SERVICE (self), FALSE); g_return_val_if_fail (domain == NULL || GDATA_IS_AUTHORIZATION_DOMAIN (domain), FALSE); @@ -2239,8 +2186,6 @@ gdata_service_delete_entry (GDataService *self, GDataAuthorizationDomain *domain if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ _gdata_service_parse_soup_error (self, error); - g_clear_pointer (&body, g_bytes_unref); - g_object_unref (message); return FALSE; } else if (status != SOUP_STATUS_OK && status != SOUP_STATUS_NO_CONTENT) { /* Error */ @@ -2250,14 +2195,9 @@ gdata_service_delete_entry (GDataService *self, GDataAuthorizationDomain *domain soup_message_get_reason_phrase (message), g_bytes_get_data (body, NULL), g_bytes_get_size (body), error); - g_bytes_unref (body); - g_object_unref (message); return FALSE; } - g_bytes_unref (body); - g_object_unref (message); - return TRUE; } diff --git a/gdata/services/calendar/gdata-calendar-calendar.c b/gdata/services/calendar/gdata-calendar-calendar.c index dc9dd498..da5c2593 100644 --- a/gdata/services/calendar/gdata-calendar-calendar.c +++ b/gdata/services/calendar/gdata-calendar-calendar.c @@ -242,8 +242,8 @@ get_rules (GDataAccessHandler *self, GDataAuthorizationDomain *domain = NULL; GDataFeed *feed; GDataLink *_link; - SoupMessage *message; - GBytes *body = NULL; + g_autoptr(SoupMessage) message = NULL; + g_autoptr(GBytes) body = NULL; GList/**/ *rules, *i; const gchar *calendar_id; @@ -297,9 +297,6 @@ get_rules (GDataAccessHandler *self, g_free (uri); } - g_bytes_unref (body); - g_object_unref (message); - return feed; } @@ -307,11 +304,11 @@ static void get_rules_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GDataService *service = GDATA_SERVICE (object); - GTask *task = user_data; + g_autoptr(GTask) task = user_data; GetRulesAsyncData *data = g_task_get_task_data (task); GDataFeed *feed; GDataLink *_link; - GBytes *body; + g_autoptr(GBytes) body = NULL; GList/**/ *rules, *i; const gchar *calendar_id; GError *error = NULL; @@ -354,8 +351,6 @@ get_rules_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) g_free (uri); } - g_bytes_unref (body); - if (data->destroy_progress_user_data != NULL) { data->destroy_progress_user_data (data->progress_user_data); } @@ -364,8 +359,6 @@ get_rules_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) g_task_return_error (task, error); else g_task_return_pointer (task, feed, g_object_unref); - - g_object_unref (task); } static void diff --git a/gdata/services/documents/gdata-documents-entry.c b/gdata/services/documents/gdata-documents-entry.c index 9884f1b3..25106cb6 100644 --- a/gdata/services/documents/gdata-documents-entry.c +++ b/gdata/services/documents/gdata-documents-entry.c @@ -348,8 +348,8 @@ get_rules (GDataAccessHandler *self, GDataAuthorizationDomain *domain = NULL; GDataFeed *feed; GDataLink *_link; - SoupMessage *message; - GBytes *body = NULL; + g_autoptr(SoupMessage) message = NULL; + g_autoptr(GBytes) body = NULL; _link = gdata_entry_look_up_link (GDATA_ENTRY (self), GDATA_LINK_ACCESS_CONTROL_LIST); g_assert (_link != NULL); @@ -370,9 +370,6 @@ get_rules (GDataAccessHandler *self, GDATA_TYPE_DOCUMENTS_ACCESS_RULE, progress_callback, progress_user_data, error); - g_bytes_unref (body); - g_object_unref (message); - return feed; } @@ -380,10 +377,10 @@ static void get_rules_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GDataService *service = GDATA_SERVICE (object); - GTask *task = user_data; + g_autoptr(GTask) task = user_data; GetRulesAsyncData *data = g_task_get_task_data (task); GDataFeed *feed; - GBytes *body; + g_autoptr(GBytes) body = NULL; GError *error = NULL; body = _gdata_service_send_finish (service, result, &error); @@ -395,8 +392,6 @@ get_rules_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) GDATA_TYPE_DOCUMENTS_ACCESS_RULE, data->progress_callback, data->progress_user_data, &error); - g_bytes_unref (body); - if (data->destroy_progress_user_data != NULL) { data->destroy_progress_user_data (data->progress_user_data); } @@ -405,8 +400,6 @@ get_rules_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) g_task_return_error (task, error); else g_task_return_pointer (task, feed, g_object_unref); - - g_object_unref (task); } static void diff --git a/gdata/services/documents/gdata-documents-service.c b/gdata/services/documents/gdata-documents-service.c index 8128bc18..cf2c48bb 100644 --- a/gdata/services/documents/gdata-documents-service.c +++ b/gdata/services/documents/gdata-documents-service.c @@ -432,8 +432,8 @@ gdata_documents_service_get_metadata (GDataDocumentsService *self, GCancellable { GDataDocumentsMetadata *metadata; const gchar *uri = "https://www.googleapis.com/drive/v2/about"; - SoupMessage *message; - GBytes *body; + g_autoptr(SoupMessage) message = NULL; + g_autoptr(GBytes) body = NULL; guint status; g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self), NULL); @@ -449,8 +449,6 @@ gdata_documents_service_get_metadata (GDataDocumentsService *self, GCancellable if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ _gdata_service_parse_soup_error (GDATA_SERVICE (self), error); - g_clear_pointer (&body, g_bytes_unref); - g_object_unref (message); return NULL; } else if (status != SOUP_STATUS_OK) { /* Error */ @@ -460,8 +458,6 @@ gdata_documents_service_get_metadata (GDataDocumentsService *self, GCancellable soup_message_get_reason_phrase (message), g_bytes_get_data (body, NULL), g_bytes_get_size (body), error); - g_bytes_unref (body); - g_object_unref (message); return NULL; } @@ -471,8 +467,6 @@ gdata_documents_service_get_metadata (GDataDocumentsService *self, GCancellable g_bytes_get_data (body, NULL), g_bytes_get_size (body), error)); - g_bytes_unref (body); - g_object_unref (message); return metadata; } @@ -482,9 +476,9 @@ get_metadata_async_cb (GObject *object, GAsyncResult *result, gpointer user_data { GDataDocumentsService *self = GDATA_DOCUMENTS_SERVICE (object); GDataDocumentsMetadata *metadata; - GTask *task = user_data; + g_autoptr(GTask) task = user_data; SoupMessage *message = g_task_get_task_data (task); - GBytes *body; + g_autoptr(GBytes) body = NULL; GError *error = NULL; guint status; @@ -494,12 +488,10 @@ get_metadata_async_cb (GObject *object, GAsyncResult *result, gpointer user_data if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ _gdata_service_parse_soup_error (GDATA_SERVICE (self), &error); - g_clear_pointer (&body, g_bytes_unref); if (error) g_task_return_error (task, error); else g_task_return_pointer (task, NULL, NULL); - g_object_unref (task); return; } else if (status != SOUP_STATUS_OK) { /* Error */ @@ -509,9 +501,7 @@ get_metadata_async_cb (GObject *object, GAsyncResult *result, gpointer user_data soup_message_get_reason_phrase (message), g_bytes_get_data (body, NULL), g_bytes_get_size (body), &error); - g_bytes_unref (body); g_task_return_error (task, error); - g_object_unref (task); return; } @@ -526,9 +516,6 @@ get_metadata_async_cb (GObject *object, GAsyncResult *result, gpointer user_data g_task_return_error (task, error); else g_task_return_pointer (task, metadata, g_object_unref); - - g_bytes_unref (body); - g_object_unref (task); } /** @@ -1217,7 +1204,7 @@ copy_document_async_cb (GObject *object, GAsyncResult *result, gpointer user_dat GDataDocumentsDocument *document; GDataDocumentsDocument *new_document; GDataEntry *parent; - GTask *task = user_data; + g_autoptr(GTask) task = user_data; GError *error = NULL; document = g_task_get_task_data (task); @@ -1226,7 +1213,6 @@ copy_document_async_cb (GObject *object, GAsyncResult *result, gpointer user_dat if (!parent) { g_task_return_error (task, error); - g_object_unref (task); return; } @@ -1237,8 +1223,6 @@ copy_document_async_cb (GObject *object, GAsyncResult *result, gpointer user_dat g_task_return_error (task, error); else g_task_return_pointer (task, new_document, g_object_unref); - - g_object_unref (task); } /** @@ -1263,7 +1247,7 @@ void gdata_documents_service_copy_document_async (GDataDocumentsService *self, GDataDocumentsDocument *document, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { - GTask *task = NULL; + g_autoptr(GTask) task = NULL; GList *i; GList *parent_folders_list; const gchar *parent_id = NULL; @@ -1280,7 +1264,6 @@ gdata_documents_service_copy_document_async (GDataDocumentsService *self, GDataD get_documents_authorization_domain ()) == FALSE) { g_task_return_error (task, g_error_new (GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED, _("You must be authenticated to copy documents."))); - g_object_unref (task); return; } @@ -1302,7 +1285,6 @@ gdata_documents_service_copy_document_async (GDataDocumentsService *self, GDataD g_task_return_error (task, g_error_new (GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NOT_FOUND, _("Parent folder not found"))); - g_object_unref (task); return; } @@ -1312,7 +1294,7 @@ gdata_documents_service_copy_document_async (GDataDocumentsService *self, GDataD GDATA_TYPE_DOCUMENTS_FOLDER, g_task_get_cancellable (task), copy_document_async_cb, - task); + g_object_ref (task)); } /** @@ -1384,9 +1366,9 @@ gdata_documents_service_add_entry_to_folder (GDataDocumentsService *self, GDataD const gchar *uri_prefix = "https://www.googleapis.com/drive/v2/files"; gchar *upload_data; gchar *uri; - SoupMessage *message; + g_autoptr(SoupMessage) message = NULL; guint status; - GBytes *body; + g_autoptr(GBytes) body = NULL; GList *l; g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self), NULL); @@ -1452,8 +1434,6 @@ gdata_documents_service_add_entry_to_folder (GDataDocumentsService *self, GDataD if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ _gdata_service_parse_soup_error (GDATA_SERVICE (self), error); - g_clear_pointer (&body, g_bytes_unref); - g_object_unref (message); return NULL; } else if (status != SOUP_STATUS_OK) { /* Error */ @@ -1463,8 +1443,6 @@ gdata_documents_service_add_entry_to_folder (GDataDocumentsService *self, GDataD soup_message_get_reason_phrase (message), g_bytes_get_data (body, NULL), g_bytes_get_size (body), error); - g_bytes_unref (body); - g_object_unref (message); return NULL; } @@ -1474,8 +1452,6 @@ gdata_documents_service_add_entry_to_folder (GDataDocumentsService *self, GDataD g_bytes_get_data (body, NULL), g_bytes_get_size (body), error)); - g_bytes_unref (body); - g_object_unref (message); return new_entry; } @@ -1498,11 +1474,11 @@ add_entry_to_folder_async_cb (GObject *object, GAsyncResult *result, gpointer us { GDataDocumentsService *self = GDATA_DOCUMENTS_SERVICE (object); GDataDocumentsEntry *new_entry; - GTask *task = user_data; + g_autoptr(GTask) task = user_data; AddEntryToFolderData *data = g_task_get_task_data (task); SoupMessage *message = data->message; GDataOperationType operation_type = data->operation_type; - GBytes *body; + g_autoptr(GBytes) body = NULL; GError *error = NULL; guint status; @@ -1512,12 +1488,10 @@ add_entry_to_folder_async_cb (GObject *object, GAsyncResult *result, gpointer us if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ _gdata_service_parse_soup_error (GDATA_SERVICE (self), &error); - g_clear_pointer (&body, g_bytes_unref); if (error) g_task_return_error (task, error); else g_task_return_pointer (task, NULL, NULL); - g_object_unref (task); return; } else if (status != SOUP_STATUS_OK) { /* Error */ @@ -1527,9 +1501,7 @@ add_entry_to_folder_async_cb (GObject *object, GAsyncResult *result, gpointer us soup_message_get_reason_phrase (message), g_bytes_get_data (body, NULL), g_bytes_get_size (body), &error); - g_bytes_unref (body); g_task_return_error (task, error); - g_object_unref (task); return; } @@ -1544,9 +1516,6 @@ add_entry_to_folder_async_cb (GObject *object, GAsyncResult *result, gpointer us g_task_return_error (task, error); else g_task_return_pointer (task, new_entry, g_object_unref); - - g_bytes_unref (body); - g_object_unref (task); } /** @@ -1714,8 +1683,8 @@ gdata_documents_service_remove_entry_from_folder (GDataDocumentsService *self, G gchar *fixed_uri, *modified_uri; guint status; gboolean req_status = TRUE; - SoupMessage *message; - GBytes *body; + g_autoptr(SoupMessage) message = NULL; + g_autoptr(GBytes) body = NULL; g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self), NULL); g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (entry), NULL); @@ -1787,8 +1756,6 @@ gdata_documents_service_remove_entry_from_folder (GDataDocumentsService *self, G if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ _gdata_service_parse_soup_error (GDATA_SERVICE (self), error); - g_clear_pointer (&body, g_bytes_unref); - g_object_unref (message); return NULL; } else if (status != SOUP_STATUS_OK && status != SOUP_STATUS_NO_CONTENT) { /* Error */ @@ -1803,9 +1770,6 @@ gdata_documents_service_remove_entry_from_folder (GDataDocumentsService *self, G req_status = FALSE; } - g_bytes_unref (body); - g_object_unref (message); - if (req_status) { /* Remove parent link from File's Data Entry */ gdata_entry_remove_link (GDATA_ENTRY (entry), folder_link); @@ -1834,9 +1798,9 @@ static void remove_entry_from_folder_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GDataDocumentsService *self = GDATA_DOCUMENTS_SERVICE (object); - GTask *task = user_data; + g_autoptr(GTask) task = user_data; RemoveEntryFromFolderData *data = g_task_get_task_data (task); - GBytes *body; + g_autoptr(GBytes) body = NULL; GError *error = NULL; guint status; @@ -1846,12 +1810,10 @@ remove_entry_from_folder_async_cb (GObject *object, GAsyncResult *result, gpoint if (!body || status == SOUP_STATUS_NONE) { /* Redirect error or cancelled */ _gdata_service_parse_soup_error (GDATA_SERVICE (self), &error); - g_clear_pointer (&body, g_bytes_unref); if (error) g_task_return_error (task, error); else g_task_return_pointer (task, NULL, NULL); - g_object_unref (task); return; } else if (status != SOUP_STATUS_OK && status != SOUP_STATUS_NO_CONTENT) { /* Error */ @@ -1863,9 +1825,7 @@ remove_entry_from_folder_async_cb (GObject *object, GAsyncResult *result, gpoint soup_message_get_reason_phrase (data->message), g_bytes_get_data (body, NULL), g_bytes_get_size (body), &error); - g_bytes_unref (body); g_task_return_error (task, error); - g_object_unref (task); return; } @@ -1873,9 +1833,6 @@ remove_entry_from_folder_async_cb (GObject *object, GAsyncResult *result, gpoint /* Remove parent link from File's Data Entry */ gdata_entry_remove_link (GDATA_ENTRY (data->entry), data->folder_link); g_task_return_pointer (task, g_object_ref (data->entry), g_object_unref); - - g_bytes_unref (body); - g_object_unref (task); } /** @@ -1901,7 +1858,7 @@ void gdata_documents_service_remove_entry_from_folder_async (GDataDocumentsService *self, GDataDocumentsEntry *entry, GDataDocumentsFolder *folder, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { - GTask *task = NULL; + g_autoptr(GTask) task = NULL; GDataParsableClass *klass; RemoveEntryFromFolderData *data; GDataAuthorizationDomain *domain; @@ -1928,7 +1885,6 @@ gdata_documents_service_remove_entry_from_folder_async (GDataDocumentsService *s g_task_return_error (task, g_error_new (GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED, _("You must be authenticated to move documents and folders."))); - g_object_unref (task); return; } @@ -1955,7 +1911,6 @@ gdata_documents_service_remove_entry_from_folder_async (GDataDocumentsService *s g_task_return_error (task, g_error_new (GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NOT_FOUND, _("Parent folder not found"))); - g_object_unref (task); return; } @@ -1984,7 +1939,8 @@ gdata_documents_service_remove_entry_from_folder_async (GDataDocumentsService *s _gdata_service_send_async (GDATA_SERVICE (self), data->message, g_task_get_cancellable (task), - remove_entry_from_folder_async_cb, task); + remove_entry_from_folder_async_cb, + g_object_ref (task)); } /** diff --git a/gdata/services/picasaweb/gdata-picasaweb-service.c b/gdata/services/picasaweb/gdata-picasaweb-service.c index dbefea3e..ace38df4 100644 --- a/gdata/services/picasaweb/gdata-picasaweb-service.c +++ b/gdata/services/picasaweb/gdata-picasaweb-service.c @@ -233,8 +233,8 @@ gdata_picasaweb_service_get_user (GDataPicasaWebService *self, const gchar *user { gchar *uri; GDataParsable *user; - SoupMessage *message; - GBytes *body = NULL; + g_autoptr(SoupMessage) message = NULL; + g_autoptr(GBytes) body = NULL; g_return_val_if_fail (GDATA_IS_PICASAWEB_SERVICE (self), NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); @@ -255,8 +255,6 @@ gdata_picasaweb_service_get_user (GDataPicasaWebService *self, const gchar *user g_assert (body && g_bytes_get_data (body, NULL)); user = gdata_parsable_new_from_xml (GDATA_TYPE_PICASAWEB_USER, g_bytes_get_data (body, NULL), g_bytes_get_size (body), error); - g_bytes_unref (body); - g_object_unref (message); return GDATA_PICASAWEB_USER (user); } @@ -278,15 +276,15 @@ static void get_user_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GDataService *self = GDATA_SERVICE (object); - GTask *task = user_data; + g_autoptr(GTask) task = user_data; SoupMessage *message = g_task_get_task_data (task); GDataParsable *user; - GBytes *body; + g_autoptr(GBytes) body = NULL; GError *error = NULL; body = _gdata_service_send_finish (self, result, &error); - if (!_gdata_service_query_async_check (self, task, message, body, &error, NULL, NULL)) + if (!_gdata_service_query_async_check (self,task, message, body, &error, NULL, NULL)) return; user = gdata_parsable_new_from_xml (GDATA_TYPE_PICASAWEB_USER, @@ -297,9 +295,6 @@ get_user_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) g_task_return_error (task, error); else g_task_return_pointer (task, user, g_object_unref); - - g_bytes_unref (body); - g_object_unref (task); } /** diff --git a/gdata/services/youtube/gdata-youtube-service.c b/gdata/services/youtube/gdata-youtube-service.c index 7c7603b0..f3d3f489 100644 --- a/gdata/services/youtube/gdata-youtube-service.c +++ b/gdata/services/youtube/gdata-youtube-service.c @@ -1084,9 +1084,9 @@ gdata_youtube_service_get_categories (GDataYouTubeService *self, GCancellable *c { const gchar *locale; gchar *uri; - SoupMessage *message; + g_autoptr(SoupMessage) message = NULL; GDataAPPCategories *categories; - GBytes *body = NULL; + g_autoptr(GBytes) body = NULL; g_return_val_if_fail (GDATA_IS_YOUTUBE_SERVICE (self), NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); @@ -1116,8 +1116,6 @@ gdata_youtube_service_get_categories (GDataYouTubeService *self, GCancellable *c g_bytes_get_data (body, NULL), g_bytes_get_size (body), GSIZE_TO_POINTER (GDATA_TYPE_YOUTUBE_CATEGORY), error)); - g_bytes_unref (body); - g_object_unref (message); return categories; } @@ -1127,10 +1125,10 @@ get_categories_async_cb (GObject *object, GAsyncResult *result, gpointer user_data) { GDataService *self = GDATA_SERVICE (object); - GTask *task = user_data; + g_autoptr(GTask) task = user_data; SoupMessage *message = g_task_get_task_data (task); GDataAPPCategories *categories; - GBytes *body; + g_autoptr(GBytes) body = NULL; GError *error = NULL; body = _gdata_service_send_finish (self, result, &error); @@ -1147,9 +1145,6 @@ get_categories_async_cb (GObject *object, GAsyncResult *result, g_task_return_error (task, error); else g_task_return_pointer (task, categories, g_object_unref); - - g_bytes_unref (body); - g_object_unref (task); } /** diff --git a/gdata/tests/documents.c b/gdata/tests/documents.c index 511d3c13..b8f4d54f 100644 --- a/gdata/tests/documents.c +++ b/gdata/tests/documents.c @@ -245,7 +245,7 @@ _set_up_temp_document (GDataDocumentsEntry *entry, GDataService *service, GFile GFileInputStream *file_stream; GDataUploader *uploader; GError *error = NULL; - GBytes *response; + g_autoptr(GBytes) response = NULL; /* Query for information on the file. */ file_info = g_file_query_info (document_file, @@ -278,7 +278,6 @@ _set_up_temp_document (GDataDocumentsEntry *entry, GDataService *service, GFile g_assert_no_error (error); g_object_unref (uploader); - g_bytes_unref (response); /* HACK: Query for the new document, as Google's servers appear to modify it behind our back when creating the document: * http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=2337. We have to wait a few seconds before trying this to allow the @@ -825,7 +824,7 @@ test_upload (UploadDocumentData *data, gconstpointer _test_params) } else { GDataUploader *uploader; GFileInputStream *file_stream; - GBytes *response; + g_autoptr(GBytes) response = NULL; /* Prepare the upload stream */ switch (test_params->resumable_type) { @@ -868,7 +867,6 @@ test_upload (UploadDocumentData *data, gconstpointer _test_params) g_assert_no_error (error); g_object_unref (uploader); - g_bytes_unref (response); g_object_unref (file_stream); } @@ -1063,7 +1061,7 @@ test_update (UpdateDocumentData *data, gconstpointer _test_params) GFile *updated_document_file; GFileInfo *file_info; gchar *path = NULL; - GBytes *response; + g_autoptr(GBytes) response = NULL; /* Prepare the updated file */ path = g_test_build_filename (G_TEST_DIST, "test_updated.odt", NULL); @@ -1114,7 +1112,6 @@ test_update (UpdateDocumentData *data, gconstpointer _test_params) g_assert_no_error (error); g_object_unref (uploader); - g_bytes_unref (response); g_object_unref (file_stream); } diff --git a/gdata/tests/picasaweb.c b/gdata/tests/picasaweb.c index 93326510..a8a42c25 100644 --- a/gdata/tests/picasaweb.c +++ b/gdata/tests/picasaweb.c @@ -407,7 +407,7 @@ upload_file (GDataPicasaWebService *service, const gchar *title, GDataPicasaWebA GFileInfo *file_info; GFileInputStream *input_stream; GDataUploader *uploader; - GBytes *response; + g_autoptr(GBytes) response = NULL; gchar *path = NULL; file = gdata_picasaweb_file_new (NULL); @@ -446,7 +446,6 @@ upload_file (GDataPicasaWebService *service, const gchar *title, GDataPicasaWebA uploaded_file = gdata_picasaweb_service_finish_file_upload (GDATA_PICASAWEB_SERVICE (service), response, NULL); g_object_unref (uploader); - g_bytes_unref (response); g_assert (GDATA_IS_PICASAWEB_FILE (uploaded_file)); @@ -1647,7 +1646,7 @@ test_upload_default_album (UploadData *data, gconstpointer service) GDataUploader *uploader; const gchar * const *tags, * const *tags2; GError *error = NULL; - GBytes *response; + g_autoptr(GBytes) response = NULL; gsize transfer_size; gdata_test_mock_server_start_trace (mock_server, "upload-default-album"); @@ -1667,7 +1666,6 @@ test_upload_default_album (UploadData *data, gconstpointer service) /* Finish off the upload */ data->updated_photo = gdata_picasaweb_service_finish_file_upload (GDATA_PICASAWEB_SERVICE (service), response, &error); - g_bytes_unref (response); g_assert_no_error (error); g_assert (GDATA_IS_PICASAWEB_FILE (data->updated_photo)); diff --git a/gdata/tests/streams.c b/gdata/tests/streams.c index 1c2d2cab..85eb580d 100644 --- a/gdata/tests/streams.c +++ b/gdata/tests/streams.c @@ -571,7 +571,7 @@ test_upload_stream_upload_no_entry_content_length (void) GDataUploader *uploader; goffset test_string_length; GBytes *body; - GBytes *response; + g_autoptr(GBytes) response = NULL; gsize transfer_size; GError *error = NULL; @@ -603,8 +603,6 @@ test_upload_stream_upload_no_entry_content_length (void) g_assert_nonnull (response); g_assert_no_error (error); - g_bytes_unref (response); - /* Kill the server and wait for it to die */ stop_server (server, main_loop); g_thread_join (thread); @@ -881,7 +879,7 @@ test_upload_stream_resumable (gconstpointer user_data) gchar *test_string; goffset test_string_length; GError *error = NULL; - GBytes *response; + g_autoptr(GBytes) response = NULL; test_params = (UploadStreamResumableTestParams*) user_data; @@ -942,7 +940,6 @@ test_upload_stream_resumable (gconstpointer user_data) /* Check we've had a successful return value */ g_assert_no_error (error); g_assert_cmpint (total_length_written, ==, test_string_length); - g_bytes_unref (response); break; default: g_assert_not_reached (); diff --git a/gdata/tests/youtube.c b/gdata/tests/youtube.c index d6dce5d4..6e996827 100644 --- a/gdata/tests/youtube.c +++ b/gdata/tests/youtube.c @@ -268,7 +268,7 @@ test_upload_simple (UploadData *data, gconstpointer service) GFileInputStream *file_stream; const gchar * const *tags, * const *tags2; gsize transfer_size; - GBytes *response; + g_autoptr(GBytes) response = NULL; GError *error = NULL; gdata_test_mock_server_start_trace (mock_server, "upload-simple"); @@ -345,7 +345,7 @@ G_STMT_START { GDataUploader *uploader = GDATA_UPLOADER (obj); const gchar * const *tags; const gchar * const *tags2; - GBytes *response; + g_autoptr(GBytes) response = NULL; gsize transfer_size; GError *upload_error = NULL; -- GitLab From bb941c08811e6810b3ff7965a403478c00f29fdf Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Wed, 13 Jul 2022 11:12:05 +0200 Subject: [PATCH 38/45] ci: Update ABI check for soup3 API changes The ABI check won't pass, but this ensures we didn't change ABI unwittingly. --- .ci/gdata.suppr | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.ci/gdata.suppr b/.ci/gdata.suppr index 53880133..ba45a518 100644 --- a/.ci/gdata.suppr +++ b/.ci/gdata.suppr @@ -1 +1,36 @@ # See https://www.sourceware.org/libabigail/manual/libabigail-concepts.html#suppression-specifications +[suppress_function] +type_kind = function +name_regexp = gdata_upload_stream_* + +[suppress_function] +type_kind = function +symbol_name_regexp = gdata_upload_stream_* + +[suppress_function] +type_kind = function-subtype-change +symbol_name = gdata_documents_service_finish_upload + +[suppress_function] +type_kind = function-subtype-change +symbol_name = gdata_documents_service_update_document + +[suppress_function] +type_kind = function-subtype-change +symbol_name = gdata_documents_service_update_document_resumable + +[suppress_function] +type_kind = function-subtype-change +symbol_name = gdata_documents_service_upload_document + +[suppress_function] +type_kind = function-subtype-change +symbol_name = gdata_documents_service_upload_document_resumable + +[suppress_function] +type_kind = function-subtype-change +symbol_name = gdata_picasaweb_service_upload_file + +[suppress_function] +type_kind = function-subtype-change +symbol_name = gdata_youtube_service_upload_video -- GitLab From 2f7ac7ef58a0850afafb44d5ef4f2d43dd38c2eb Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Wed, 13 Jul 2022 11:44:13 +0200 Subject: [PATCH 39/45] soup3: Make library parallel installable with older versions Version the library, bindings, docs, etc. so libgdata-1.0 is parallel installable with the original API. --- docs/reference/meson.build | 1 + gdata/meson.build | 10 +++++----- gdata/tests/meson.build | 4 ++-- meson.build | 3 ++- po/meson.build | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/reference/meson.build b/docs/reference/meson.build index 11ed7900..0fb04ee0 100644 --- a/docs/reference/meson.build +++ b/docs/reference/meson.build @@ -63,6 +63,7 @@ content_files += configure_file( gnome.gtkdoc( doc_module, + module_version: gdata_api_version, main_xml: doc_module + '-docs.xml', src_dir: gdata_inc_dir, ignore_headers: ignore_headers, diff --git a/gdata/meson.build b/gdata/meson.build index e21c59ea..5c24434f 100644 --- a/gdata/meson.build +++ b/gdata/meson.build @@ -131,7 +131,7 @@ symbol_map = meson.current_source_dir() / 'symbol.map' ldflags = cc.get_supported_link_arguments('-Wl,--version-script,' + symbol_map) libgdata_lib = shared_library( - 'gdata', + 'gdata-' + gdata_api_version, include_directories: incs, sources: sources + enum_headers + marshal_files, c_args: common_c_args + ['-DG_LOG_DOMAIN="@0@"'.format(gdata_name)], @@ -153,11 +153,11 @@ libgdata_dep = declare_dependency( pkgconfig.generate( name: 'libgdata', description: 'GData client library', - subdirs: 'libgdata', + subdirs: 'libgdata-@0@'.format(gdata_api_version), libraries: libgdata_lib, requires: gdata_deps, requires_private: gdata_private_deps, - filebase: 'libgdata', + filebase: 'libgdata-@0@'.format(gdata_api_version), version: gdata_version, variables: [ 'exec_prefix=${prefix}', @@ -177,7 +177,7 @@ if get_option('introspection') libgdata_gir = gnome.generate_gir( libgdata_lib, sources: sources + gir_headers + enum_headers, - nsversion: '@0@.@1@'.format(gdata_api_version_major, gdata_api_version_minor), + nsversion: gdata_api_version, namespace: 'GData', symbol_prefix: 'gdata', includes: gdata_gir_includes, @@ -187,7 +187,7 @@ if get_option('introspection') ) if get_option('vapi') - libgdata_vapi = gnome.generate_vapi(gdata_name, + libgdata_vapi = gnome.generate_vapi('gdata-' + gdata_api_version, sources: libgdata_gir[0], packages: gdata_vapi_deps, install: true, diff --git a/gdata/tests/meson.build b/gdata/tests/meson.build index 5717e494..de558757 100644 --- a/gdata/tests/meson.build +++ b/gdata/tests/meson.build @@ -1,5 +1,5 @@ -tests_execdir = gdata_libexecdir / 'installed-tests' / gdata_name -tests_metadir = gdata_datadir / 'installed-tests' / gdata_name +tests_execdir = gdata_libexecdir / 'installed-tests' / 'gdata-' + gdata_api_version +tests_metadir = gdata_datadir / 'installed-tests' / 'gdata-' + gdata_api_version tests_sources = files( 'common.c', diff --git a/meson.build b/meson.build index 39c8d3f1..7c493246 100644 --- a/meson.build +++ b/meson.build @@ -17,6 +17,7 @@ gdata_version_micro = ver_arr[2].to_int() # API version gdata_api_version_major = 1 gdata_api_version_minor = 0 +gdata_api_version = '@0@.@1@'.format(gdata_api_version_major, gdata_api_version_minor) # Define the install directories gdata_prefix = get_option('prefix') @@ -24,7 +25,7 @@ gdata_datadir = get_option('datadir') gdata_libexecdir = get_option('libexecdir') gdata_includedir = get_option('includedir') -gdata_include_subdir = gdata_name / 'gdata' +gdata_include_subdir = gdata_name + '-' + gdata_api_version / 'gdata' # Before making a release, the GDATA_LT_VERSION string should be modified. The string is of the form c:r:a. Follow these instructions sequentially: # diff --git a/po/meson.build b/po/meson.build index 082eb58f..1563435b 100644 --- a/po/meson.build +++ b/po/meson.build @@ -1 +1 @@ -i18n.gettext('gdata', preset: 'glib') +i18n.gettext('gdata-' + gdata_api_version, preset: 'glib') -- GitLab From 6ce7f04067240e71ec58489d04f39e7a0af78069 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Wed, 13 Jul 2022 11:46:48 +0200 Subject: [PATCH 40/45] ci: Reset ABI check for 1.0 API The ABI check will at least complain about the filename and soname changes. --- .ci/gdata.suppr | 35 ----------------------------------- .gitlab-ci.yml | 2 +- 2 files changed, 1 insertion(+), 36 deletions(-) diff --git a/.ci/gdata.suppr b/.ci/gdata.suppr index ba45a518..53880133 100644 --- a/.ci/gdata.suppr +++ b/.ci/gdata.suppr @@ -1,36 +1 @@ # See https://www.sourceware.org/libabigail/manual/libabigail-concepts.html#suppression-specifications -[suppress_function] -type_kind = function -name_regexp = gdata_upload_stream_* - -[suppress_function] -type_kind = function -symbol_name_regexp = gdata_upload_stream_* - -[suppress_function] -type_kind = function-subtype-change -symbol_name = gdata_documents_service_finish_upload - -[suppress_function] -type_kind = function-subtype-change -symbol_name = gdata_documents_service_update_document - -[suppress_function] -type_kind = function-subtype-change -symbol_name = gdata_documents_service_update_document_resumable - -[suppress_function] -type_kind = function-subtype-change -symbol_name = gdata_documents_service_upload_document - -[suppress_function] -type_kind = function-subtype-change -symbol_name = gdata_documents_service_upload_document_resumable - -[suppress_function] -type_kind = function-subtype-change -symbol_name = gdata_picasaweb_service_upload_file - -[suppress_function] -type_kind = function-subtype-change -symbol_name = gdata_youtube_service_upload_video diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e3bd4c66..46a8e1b9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,7 +23,7 @@ variables: OLD_ABI_DEPENDENCIES: liboauth-devel libsoup-devel - LAST_ABI_BREAK: 27fb43ff72435854984f1c4ed35deff96d3c652a + LAST_ABI_BREAK: 145dcbdca2c6e69e136fbbc7c9b690f84ced6b5e build_stable: tags: -- GitLab From 9038b187d20db451d10690c80d9a35c0a5e05cc0 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Tue, 19 Jul 2022 13:34:13 +0200 Subject: [PATCH 41/45] fixup! soup3: replace upload-stream with uploader --- gdata/gdata-uploader.c | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/gdata/gdata-uploader.c b/gdata/gdata-uploader.c index 88b619a0..6db13820 100644 --- a/gdata/gdata-uploader.c +++ b/gdata/gdata-uploader.c @@ -32,7 +32,7 @@ typedef struct { GInputStream *header_stream; gsize header_left; gint64 read_left; - gsize *total_written; + gsize total_written; } GDataUploaderInputStreamPrivate; static void gdata_uploader_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface, gpointer interface_data); @@ -85,7 +85,7 @@ gdata_uploader_input_stream_set_property (GObject *object, guint prop_id, priv->read_left = g_value_get_int64 (value); break; case PROP_TOTAL_WRITTEN: - priv->total_written = g_value_get_pointer (value); + priv->total_written = g_value_get_uint (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -108,7 +108,7 @@ gdata_uploader_input_stream_get_property (GObject *object, guint prop_id, g_value_set_int64 (value, priv->read_left); break; case PROP_TOTAL_WRITTEN: - g_value_set_pointer (value, priv->total_written); + g_value_set_uint (value, priv->total_written); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -183,8 +183,10 @@ read_internal (GInputStream *stream, g_clear_error (&rerror); } return sread; - } else if (priv->total_written) - *priv->total_written += sread; + } else { + priv->total_written += sread; + g_object_notify (G_OBJECT (istream), "total-written"); + } if (priv->read_left > 0) priv->read_left -= sread; @@ -261,8 +263,10 @@ gdata_uploader_input_stream_skip (GInputStream *stream, g_clear_error (&rerror); } return sread; - } else if (priv->total_written) - *priv->total_written += sread; + } else { + priv->total_written += sread; + g_object_notify (G_OBJECT (istream), "total-written"); + } if (priv->read_left > 0) priv->read_left -= sread; @@ -302,11 +306,12 @@ gdata_uploader_input_stream_class_init (GDataUploaderInputStreamClass *klass) g_object_class_install_property ( object_class, PROP_TOTAL_WRITTEN, - g_param_spec_pointer ("total-written", - "Total written", - "Total data bytes written", - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS)); + g_param_spec_uint ("total-written", + "Total written", + "Total data bytes written", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); } static gboolean @@ -864,9 +869,6 @@ gdata_uploader_send (GDataUploader *self, GCancellable *cancellable, priv->total_written = 0; - if (total_written) - *total_written = 0; - /* FIXME: Refresh authorization before sending message in order to prevent authorization errors during transfer. * See: https://gitlab.gnome.org/GNOME/libgdata/issues/23 */ authorizer = gdata_service_get_authorizer (priv->service); -- GitLab From cfa481ce655531eee01bdf9cfe4398c3be13f250 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Tue, 19 Jul 2022 13:37:30 +0200 Subject: [PATCH 42/45] fixup! soup3: use uploader in gdata-documents-service --- gdata/services/documents/gdata-documents-service.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gdata/services/documents/gdata-documents-service.c b/gdata/services/documents/gdata-documents-service.c index cf2c48bb..b6764977 100644 --- a/gdata/services/documents/gdata-documents-service.c +++ b/gdata/services/documents/gdata-documents-service.c @@ -856,7 +856,6 @@ _upload_checks (GDataDocumentsService *self, GDataDocumentsDocument *document, G * @slug: the filename to give to the uploaded document * @content_type: the content type of the uploaded data * @folder: (allow-none): the folder to which the document should be uploaded, or %NULL - * @cancellable: (allow-none): a #GCancellable for the entire upload stream, or %NULL * @error: a #GError, or %NULL * * Uploads a document to Google Documents, using the properties from @document and the document data written to the resulting #GDataUploader. If @@ -915,7 +914,6 @@ gdata_documents_service_upload_document (GDataDocumentsService *self, GDataDocum * @content_type: the content type of the uploaded data * @content_length: the size (in bytes) of the file being uploaded * @query: (allow-none): a query specifying parameters for the upload, or %NULL - * @cancellable: (allow-none): a #GCancellable for the entire upload stream, or %NULL * @error: a #GError, or %NULL * * Uploads a document to Google Documents, using the properties from @document and the document data written to the resulting #GDataUoloader. If @@ -990,7 +988,6 @@ _update_checks (GDataDocumentsService *self, GError **error) * @document: the #GDataDocumentsDocument to update * @slug: the filename to give to the uploaded document * @content_type: the content type of the uploaded data - * @cancellable: (allow-none): a #GCancellable for the entire upload stream, or %NULL * @error: a #GError, or %NULL * * Update the document using the properties from @document and the document data written to the resulting #GDataUploader. If the document data does @@ -1048,7 +1045,6 @@ gdata_documents_service_update_document (GDataDocumentsService *self, GDataDocum * @slug: the filename to give to the uploaded document * @content_type: the content type of the uploaded data * @content_length: the size (in bytes) of the file being uploaded - * @cancellable: (allow-none): a #GCancellable for the entire upload stream, or %NULL * @error: a #GError, or %NULL * * Update the document using the properties from @document and the document data written to the resulting #GDataUploader. If the document data does -- GitLab From 4b8dbf2f1164cbc06940aacc5c4edffe394cfdaa Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Tue, 19 Jul 2022 13:38:06 +0200 Subject: [PATCH 43/45] fixup! soup3: uploader in gdata-youtube-service --- gdata/services/youtube/gdata-youtube-service.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gdata/services/youtube/gdata-youtube-service.c b/gdata/services/youtube/gdata-youtube-service.c index f3d3f489..316f9793 100644 --- a/gdata/services/youtube/gdata-youtube-service.c +++ b/gdata/services/youtube/gdata-youtube-service.c @@ -959,7 +959,6 @@ gdata_youtube_service_query_related_async (GDataYouTubeService *self, GDataYouTu * @video: a #GDataYouTubeVideo to insert * @slug: the filename to give to the uploaded file * @content_type: the content type of the uploaded data - * @cancellable: (allow-none): a #GCancellable for the entire upload stream, or %NULL * @error: a #GError, or %NULL * * Uploads a video to YouTube, using the properties from @video and the file data written to the resulting #GDataUploadStream. @@ -1022,7 +1021,7 @@ gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo /** * gdata_youtube_service_finish_video_upload: * @self: a #GDataYouTubeService - * @respose: the response from uploader + * @response: the response from uploader * @error: a #GError, or %NULL * * Finish off a video upload operation started by gdata_youtube_service_upload_video(), parsing the result and returning the new #GDataYouTubeVideo. -- GitLab From aac820b4d0cb0d4d8a738b431594309401959c2e Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Tue, 19 Jul 2022 13:59:04 +0200 Subject: [PATCH 44/45] fixup! soup3: replace upload-stream with uploader --- gdata/gdata-uploader.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gdata/gdata-uploader.c b/gdata/gdata-uploader.c index 6db13820..1db1a88b 100644 --- a/gdata/gdata-uploader.c +++ b/gdata/gdata-uploader.c @@ -869,6 +869,9 @@ gdata_uploader_send (GDataUploader *self, GCancellable *cancellable, priv->total_written = 0; + if (total_written) + *total_written = 0; + /* FIXME: Refresh authorization before sending message in order to prevent authorization errors during transfer. * See: https://gitlab.gnome.org/GNOME/libgdata/issues/23 */ authorizer = gdata_service_get_authorizer (priv->service); -- GitLab From 411d6bded758eb696513178c0ebe9f64385943af Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Tue, 19 Jul 2022 14:43:42 +0200 Subject: [PATCH 45/45] fixup! soup3: replace upload-stream with uploader --- gdata/gdata-uploader.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/gdata/gdata-uploader.c b/gdata/gdata-uploader.c index 1db1a88b..769e985f 100644 --- a/gdata/gdata-uploader.c +++ b/gdata/gdata-uploader.c @@ -890,13 +890,15 @@ gdata_uploader_send (GDataUploader *self, GCancellable *cancellable, } if (priv->content_length == -1) { + GBytes *ret; + /* do a non-resumable upload */ if (priv->header) { /* metadata plus content */ mbody = g_object_new (GDATA_TYPE_UPLOADER_INPUT_STREAM, "base-stream", priv->body_stream, "header", priv->header, - "total-written", total_written, NULL); + NULL); soup_message_set_request_body (priv->message, NULL, mbody, -1); g_object_unref (mbody); @@ -910,10 +912,14 @@ gdata_uploader_send (GDataUploader *self, GCancellable *cancellable, total_written); } - return soup_session_send_and_read (priv->session, - priv->message, - cancellable, - error); + ret = soup_session_send_and_read (priv->session, + priv->message, + cancellable, + error); + if (ret && priv->header && total_written) + *total_written = g_bytes_get_size (ret); + + return ret; } /* resumable upload needs multiple requests */ @@ -1181,7 +1187,7 @@ send_async_cb (GTask *task) actual_body = g_object_new (GDATA_TYPE_UPLOADER_INPUT_STREAM, "base-stream", priv->body_stream, "header", priv->header, - "total-written", &priv->total_written, NULL); + NULL); soup_message_set_request_body (priv->message, NULL, actual_body, -1); } else { @@ -1266,16 +1272,19 @@ GBytes * gdata_uploader_send_finish (GDataUploader *self, GAsyncResult *result, gsize *total_written, GError **error) { + GBytes *ret; + g_return_val_if_fail (GDATA_IS_UPLOADER (self), NULL); g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); g_return_val_if_fail (g_task_is_valid (result, self), NULL); g_return_val_if_fail (g_async_result_is_tagged (result, gdata_uploader_send_async), NULL); + ret = g_task_propagate_pointer (G_TASK (result), error); if (total_written) - *total_written = GDATA_UPLOADER (g_task_get_task_data (G_TASK (result)))->priv->total_written; + *total_written = ret ? g_bytes_get_size (ret) : 0; - return g_task_propagate_pointer (G_TASK (result), error); + return ret; } /** -- GitLab