diff options
Diffstat (limited to 'Source/WebKit2/UIProcess/API/gtk/WebKitDownload.cpp')
-rw-r--r-- | Source/WebKit2/UIProcess/API/gtk/WebKitDownload.cpp | 510 |
1 files changed, 510 insertions, 0 deletions
diff --git a/Source/WebKit2/UIProcess/API/gtk/WebKitDownload.cpp b/Source/WebKit2/UIProcess/API/gtk/WebKitDownload.cpp new file mode 100644 index 000000000..4d4eba18d --- /dev/null +++ b/Source/WebKit2/UIProcess/API/gtk/WebKitDownload.cpp @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2012 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "WebKitDownload.h" + +#include "WebKitDownloadPrivate.h" +#include "WebKitMarshal.h" +#include "WebKitURIResponsePrivate.h" +#include <WebCore/ErrorsGtk.h> +#include <WebCore/ResourceResponse.h> +#include <glib/gi18n-lib.h> +#include <wtf/gobject/GOwnPtr.h> +#include <wtf/gobject/GRefPtr.h> + +using namespace WebKit; +using namespace WebCore; + +enum { + RECEIVED_DATA, + FINISHED, + FAILED, + DECIDE_DESTINATION, + CREATED_DESTINATION, + + LAST_SIGNAL +}; + +enum { + PROP_0, + + PROP_DESTINATION, + PROP_RESPONSE, + PROP_ESTIMATED_PROGRESS +}; + +struct _WebKitDownloadPrivate { + WKRetainPtr<WKDownloadRef> wkDownload; + + GRefPtr<WebKitURIResponse> response; + CString destinationURI; + guint64 currentSize; + bool isCancelled; + GOwnPtr<GTimer> timer; + gdouble lastProgress; + gdouble lastElapsed; +}; + +static guint signals[LAST_SIGNAL] = { 0, }; + +G_DEFINE_TYPE(WebKitDownload, webkit_download, G_TYPE_OBJECT) + +static void webkitDownloadFinalize(GObject* object) +{ + WEBKIT_DOWNLOAD(object)->priv->~WebKitDownloadPrivate(); + G_OBJECT_CLASS(webkit_download_parent_class)->finalize(object); +} + +static void webkitDownloadGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec) +{ + WebKitDownload* download = WEBKIT_DOWNLOAD(object); + + switch (propId) { + case PROP_DESTINATION: + g_value_set_string(value, webkit_download_get_destination(download)); + break; + case PROP_RESPONSE: + g_value_set_object(value, webkit_download_get_response(download)); + break; + case PROP_ESTIMATED_PROGRESS: + g_value_set_double(value, webkit_download_get_estimated_progress(download)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec); + } +} + +static gboolean webkitDownloadDecideDestination(WebKitDownload* download, const gchar* suggestedFilename) +{ + if (!download->priv->destinationURI.isNull()) + return FALSE; + GOwnPtr<char> destination(g_build_filename(g_get_user_special_dir(G_USER_DIRECTORY_DOWNLOAD), suggestedFilename, NULL)); + GOwnPtr<char> destinationURI(g_filename_to_uri(destination.get(), 0, 0)); + download->priv->destinationURI = destinationURI.get(); + g_object_notify(G_OBJECT(download), "destination"); + return TRUE; +} + +static void webkit_download_init(WebKitDownload* download) +{ + WebKitDownloadPrivate* priv = G_TYPE_INSTANCE_GET_PRIVATE(download, WEBKIT_TYPE_DOWNLOAD, WebKitDownloadPrivate); + download->priv = priv; + new (priv) WebKitDownloadPrivate(); +} + +static void webkit_download_class_init(WebKitDownloadClass* downloadClass) +{ + GObjectClass* objectClass = G_OBJECT_CLASS(downloadClass); + objectClass->get_property = webkitDownloadGetProperty; + objectClass->finalize = webkitDownloadFinalize; + + downloadClass->decide_destination = webkitDownloadDecideDestination; + + /** + * WebKitDownload:destination: + * + * The local URI to where the download will be saved. + */ + g_object_class_install_property(objectClass, + PROP_DESTINATION, + g_param_spec_string("destination", + _("Destination"), + _("The local URI to where the download will be saved"), + 0, + WEBKIT_PARAM_READABLE)); + + /** + * WebKitDownload:response: + * + * The #WebKitURIResponse associated with this download. + */ + g_object_class_install_property(objectClass, + PROP_RESPONSE, + g_param_spec_object("response", + _("Response"), + _("The response of the download"), + WEBKIT_TYPE_URI_RESPONSE, + WEBKIT_PARAM_READABLE)); + + /** + * WebKitDownload:estimated-progress: + * + * An estimate of the percent completion for the download operation. + * This value will range from 0.0 to 1.0. The value is an estimate + * based on the total number of bytes expected to be received for + * a download. + * If you need a more accurate progress information you can connect to + * #WebKitDownload::received-data signal to track the progress. + */ + g_object_class_install_property(objectClass, + PROP_ESTIMATED_PROGRESS, + g_param_spec_double("estimated-progress", + _("Estimated Progress"), + _("Determines the current progress of the download"), + 0.0, 1.0, 1.0, + WEBKIT_PARAM_READABLE)); + + /** + * WebKitDownload::received-data: + * @download: the #WebKitDownload + * @data_length: the length of data received in bytes + * + * This signal is emitted after response is received, + * every time new data has been written to the destination. It's + * useful to know the progress of the download operation. + * + * Returns: %TRUE to stop other handlers from being invoked for the event. + * %FALSE to propagate the event further. + */ + signals[RECEIVED_DATA] = + g_signal_new("received-data", + G_TYPE_FROM_CLASS(objectClass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(WebKitDownloadClass, received_data), + g_signal_accumulator_true_handled, NULL, + webkit_marshal_BOOLEAN__UINT64, + G_TYPE_BOOLEAN, 1, + G_TYPE_UINT64); + + /** + * WebKitDownload::finished: + * @download: the #WebKitDownload + * + * This signal is emitted when download finishes successfully or due to an error. + * In case of errors #WebKitDownload::failed signal is emitted before this one. + * + * Returns: %TRUE to stop other handlers from being invoked for the event. + * %FALSE to propagate the event further. + */ + signals[FINISHED] = + g_signal_new("finished", + G_TYPE_FROM_CLASS(objectClass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(WebKitDownloadClass, finished), + g_signal_accumulator_true_handled, NULL, + webkit_marshal_BOOLEAN__VOID, + G_TYPE_BOOLEAN, 0); + + /** + * WebKitDownload::failed: + * @download: the #WebKitDownload + * @error: the #GError that was triggered + * + * This signal is emitted when an error occurs during the download + * operation. The given @error, of the domain %WEBKIT_DOWNLOAD_ERROR, + * contains further details of the failure. If the download is cancelled + * with webkit_download_cancel(), this signal is emitted with error + * %WEBKIT_DOWNLOAD_ERROR_CANCELLED_BY_USER. The download operation finishes + * after an error and #WebKitDownload::finished signal is emitted after this one. + * + * Returns: %TRUE to stop other handlers from being invoked for the event. + * %FALSE to propagate the event further. + */ + signals[FAILED] = + g_signal_new("failed", + G_TYPE_FROM_CLASS(objectClass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(WebKitDownloadClass, failed), + g_signal_accumulator_true_handled, NULL, + webkit_marshal_BOOLEAN__POINTER, + G_TYPE_BOOLEAN, 1, + G_TYPE_POINTER); + + /** + * WebKitDownload::decide-destination: + * @download: the #WebKitDownload + * @suggested_filename: the filename suggested for the download + * + * This signal is emitted after response is received to + * decide a destination URI for the download. If this signal is not + * handled the file will be downloaded to %G_USER_DIRECTORY_DOWNLOAD + * directory using @suggested_filename. + * + * Returns: %TRUE to stop other handlers from being invoked for the event. + * %FALSE to propagate the event further. + */ + signals[DECIDE_DESTINATION] = + g_signal_new("decide-destination", + G_TYPE_FROM_CLASS(objectClass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(WebKitDownloadClass, decide_destination), + g_signal_accumulator_true_handled, NULL, + webkit_marshal_BOOLEAN__STRING, + G_TYPE_BOOLEAN, 1, + G_TYPE_STRING); + + /** + * WebKitDownload::created-destination: + * @download: the #WebKitDownload + * @destination: the destination URI + * + * This signal is emitted after #WebKitDownload::decide-destination and before + * #WebKitDownload::received-data to notify that destination file has been + * created successfully at @destination. + * + * Returns: %TRUE to stop other handlers from being invoked for the event. + * %FALSE to propagate the event further. + */ + signals[CREATED_DESTINATION] = + g_signal_new("created-destination", + G_TYPE_FROM_CLASS(objectClass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(WebKitDownloadClass, created_destination), + g_signal_accumulator_true_handled, NULL, + webkit_marshal_BOOLEAN__STRING, + G_TYPE_BOOLEAN, 1, + G_TYPE_STRING); + + g_type_class_add_private(downloadClass, sizeof(WebKitDownloadPrivate)); +} + +WebKitDownload* webkitDownloadCreate(WKDownloadRef wkDownload) +{ + ASSERT(wkDownload); + WebKitDownload* download = WEBKIT_DOWNLOAD(g_object_new(WEBKIT_TYPE_DOWNLOAD, NULL)); + download->priv->wkDownload = wkDownload; + return download; +} + +void webkitDownloadSetResponse(WebKitDownload* download, WebKitURIResponse* response) +{ + download->priv->response = response; + g_object_notify(G_OBJECT(download), "response"); +} + +bool webkitDownloadIsCancelled(WebKitDownload* download) +{ + return download->priv->isCancelled; +} + +void webkitDownloadNotifyProgress(WebKitDownload* download, guint64 bytesReceived) +{ + WebKitDownloadPrivate* priv = download->priv; + if (priv->isCancelled) + return; + + if (!download->priv->timer) + download->priv->timer.set(g_timer_new()); + + priv->currentSize += bytesReceived; + gboolean returnValue; + g_signal_emit(download, signals[RECEIVED_DATA], 0, bytesReceived, &returnValue); + + // Throttle progress notification to not consume high amounts of + // CPU on fast links, except when the last notification occured + // more than 0.016 secs ago (60 FPS), or the last notified progress + // is passed in 1% or we reached the end. + gdouble currentElapsed = g_timer_elapsed(priv->timer.get(), 0); + gdouble currentProgress = webkit_download_get_estimated_progress(download); + + if (priv->lastElapsed + && priv->lastProgress + && (currentElapsed - priv->lastElapsed) < 0.016 + && (currentProgress - priv->lastProgress) < 0.01 + && currentProgress < 1.0) { + return; + } + priv->lastElapsed = currentElapsed; + priv->lastProgress = currentProgress; + g_object_notify(G_OBJECT(download), "estimated-progress"); +} + +void webkitDownloadFailed(WebKitDownload* download, const ResourceError& resourceError) +{ + GOwnPtr<GError> webError(g_error_new_literal(g_quark_from_string(resourceError.domain().utf8().data()), + resourceError.errorCode(), + resourceError.localizedDescription().utf8().data())); + if (download->priv->timer) + g_timer_stop(download->priv->timer.get()); + gboolean returnValue; + g_signal_emit(download, signals[FAILED], 0, webError.get(), &returnValue); + g_signal_emit(download, signals[FINISHED], 0, &returnValue); +} + +void webkitDownloadCancelled(WebKitDownload* download) +{ + WebKitDownloadPrivate* priv = download->priv; + webkitDownloadFailed(download, downloadCancelledByUserError(priv->response ? webkitURIResponseGetResourceResponse(priv->response.get()) : ResourceResponse())); +} + +void webkitDownloadFinished(WebKitDownload* download) +{ + if (download->priv->isCancelled) { + // Since cancellation is asynchronous, didFinish might be called even + // if the download was cancelled. User cancelled the download, + // so we should fail with cancelled error even if the download + // actually finished successfully. + webkitDownloadCancelled(download); + return; + } + if (download->priv->timer) + g_timer_stop(download->priv->timer.get()); + gboolean returnValue; + g_signal_emit(download, signals[FINISHED], 0, &returnValue); +} + +CString webkitDownloadDecideDestinationWithSuggestedFilename(WebKitDownload* download, const CString& suggestedFilename) +{ + if (download->priv->isCancelled) + return ""; + gboolean returnValue; + g_signal_emit(download, signals[DECIDE_DESTINATION], 0, suggestedFilename.data(), &returnValue); + return download->priv->destinationURI; +} + +void webkitDownloadDestinationCreated(WebKitDownload* download, const CString& destinationURI) +{ + if (download->priv->isCancelled) + return; + gboolean returnValue; + g_signal_emit(download, signals[CREATED_DESTINATION], 0, destinationURI.data(), &returnValue); +} + +/** + * webkit_download_get_destination: + * @download: a #WebKitDownload + * + * Obtains the URI to which the downloaded file will be written. You + * can connect to #WebKitDownload::created-destination to make + * sure this method returns a valid destination. + * + * Returns: the destination URI or %NULL + */ +const gchar* webkit_download_get_destination(WebKitDownload* download) +{ + g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 0); + + return download->priv->destinationURI.data(); +} + +/** + * webkit_download_set_destination: + * @download: a #WebKitDownload + * @uri: the destination URI + * + * Sets the URI to which the downloaded file will be written. + * This method should be called before the download transfer + * starts or it will not have any effect on the ongoing download + * operation. To set the destination using the filename suggested + * by the server connect to #WebKitDownload::decide-destination + * signal and call webkit_download_set_destination(). If you want to + * set a fixed destination URI that doesn't depend on the suggested + * filename you can connect to notify::response signal and call + * webkit_download_set_destination(). + * If #WebKitDownload::decide-destination signal is not handled + * and destination URI is not set when the download tranfer starts, + * the file will be saved with the filename suggested by the server in + * %G_USER_DIRECTORY_DOWNLOAD directory. + */ +void webkit_download_set_destination(WebKitDownload* download, const gchar* uri) +{ + g_return_if_fail(WEBKIT_IS_DOWNLOAD(download)); + g_return_if_fail(uri); + + WebKitDownloadPrivate* priv = download->priv; + if (priv->destinationURI == uri) + return; + + priv->destinationURI = uri; + g_object_notify(G_OBJECT(download), "destination"); +} + +/** + * webkit_download_get_response: + * @download: a #WebKitDownload + * + * Retrieves the #WebKitURIResponse object that backs the download + * process. This method returns %NULL if called before the response + * is received from the server. You can connect to notify::response + * signal to be notified when the response is received. + * + * Returns: (transfer none): the #WebKitURIResponse, or %NULL if + * the response hasn't been received yet. + */ +WebKitURIResponse* webkit_download_get_response(WebKitDownload* download) +{ + g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 0); + + return download->priv->response.get(); +} + +/** + * webkit_download_cancel: + * @download: a #WebKitDownload + * + * Cancels the download. When the ongoing download + * operation is effectively cancelled the signal + * #WebKitDownload::failed is emitted with + * %WEBKIT_DOWNLOAD_ERROR_CANCELLED_BY_USER error. + */ +void webkit_download_cancel(WebKitDownload* download) +{ + g_return_if_fail(WEBKIT_IS_DOWNLOAD(download)); + + download->priv->isCancelled = true; + WKDownloadCancel(download->priv->wkDownload.get()); +} + +/** + * webkit_download_get_estimated_progress: + * @download: a #WebKitDownload + * + * Gets the value of the #WebKitDownload:estimated-progress property. + * You can monitor the estimated progress of the download operation by + * connecting to the notify::estimated-progress signal of @download. + * + * Returns: an estimate of the of the percent complete for a download + * as a range from 0.0 to 1.0. + */ +gdouble webkit_download_get_estimated_progress(WebKitDownload* download) +{ + g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 0); + + WebKitDownloadPrivate* priv = download->priv; + if (!priv->response) + return 0; + + guint64 contentLength = webkit_uri_response_get_content_length(priv->response.get()); + if (!contentLength) + return 0; + + return static_cast<gdouble>(priv->currentSize) / static_cast<gdouble>(contentLength); +} + +/** + * webkit_download_get_elapsed_time: + * @download: a #WebKitDownload + * + * Gets the elapsed time in seconds, including any fractional part. + * If the download finished, had an error or was cancelled this is + * the time between its start and the event. + * + * Returns: seconds since the download was started + */ +gdouble webkit_download_get_elapsed_time(WebKitDownload* download) +{ + g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 0); + + WebKitDownloadPrivate* priv = download->priv; + if (!priv->timer) + return 0; + + return g_timer_elapsed(priv->timer.get(), 0); +} |