| /* |
| * Copyright (C) 2008 Collabora Ltd. |
| * Copyright (C) 2009 Gustavo Noronha Silva <gns@gnome.org> |
| * |
| * 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 "GRefPtr.h" |
| #include "Noncopyable.h" |
| #include "NotImplemented.h" |
| #include "ResourceHandleClient.h" |
| #include "ResourceHandleInternal.h" |
| #include "ResourceRequest.h" |
| #include "ResourceResponse.h" |
| #include "webkitdownloadprivate.h" |
| #include "webkitenumtypes.h" |
| #include "webkitglobals.h" |
| #include "webkitglobalsprivate.h" |
| #include "webkitmarshal.h" |
| #include "webkitnetworkrequestprivate.h" |
| #include "webkitnetworkresponse.h" |
| #include "webkitnetworkresponseprivate.h" |
| #include <glib/gi18n-lib.h> |
| #include <glib/gstdio.h> |
| #include <wtf/text/CString.h> |
| |
| #ifdef ERROR |
| #undef ERROR |
| #endif |
| |
| using namespace WebKit; |
| using namespace WebCore; |
| |
| /** |
| * SECTION:webkitdownload |
| * @short_description: Object used to communicate with the application when downloading. |
| * |
| * #WebKitDownload carries information about a download request, |
| * including a #WebKitNetworkRequest object. The application may use |
| * this object to control the download process, or to simply figure |
| * out what is to be downloaded, and do it itself. |
| */ |
| |
| class DownloadClient : public ResourceHandleClient { |
| WTF_MAKE_NONCOPYABLE(DownloadClient); |
| public: |
| DownloadClient(WebKitDownload*); |
| |
| virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&); |
| virtual void didReceiveData(ResourceHandle*, const char*, int, int); |
| virtual void didFinishLoading(ResourceHandle*, double); |
| virtual void didFail(ResourceHandle*, const ResourceError&); |
| virtual void wasBlocked(ResourceHandle*); |
| virtual void cannotShowURL(ResourceHandle*); |
| |
| private: |
| WebKitDownload* m_download; |
| }; |
| |
| struct _WebKitDownloadPrivate { |
| gchar* destinationURI; |
| gchar* suggestedFilename; |
| guint64 currentSize; |
| GTimer* timer; |
| WebKitDownloadStatus status; |
| GFileOutputStream* outputStream; |
| DownloadClient* downloadClient; |
| WebKitNetworkRequest* networkRequest; |
| WebKitNetworkResponse* networkResponse; |
| RefPtr<ResourceHandle> resourceHandle; |
| }; |
| |
| enum { |
| // Normal signals. |
| ERROR, |
| LAST_SIGNAL |
| }; |
| |
| static guint webkit_download_signals[LAST_SIGNAL] = { 0 }; |
| |
| enum { |
| PROP_0, |
| |
| PROP_NETWORK_REQUEST, |
| PROP_DESTINATION_URI, |
| PROP_SUGGESTED_FILENAME, |
| PROP_PROGRESS, |
| PROP_STATUS, |
| PROP_CURRENT_SIZE, |
| PROP_TOTAL_SIZE, |
| PROP_NETWORK_RESPONSE |
| }; |
| |
| G_DEFINE_TYPE(WebKitDownload, webkit_download, G_TYPE_OBJECT); |
| |
| |
| static void webkit_download_set_response(WebKitDownload* download, const ResourceResponse& response); |
| static void webkit_download_set_status(WebKitDownload* download, WebKitDownloadStatus status); |
| |
| static void webkit_download_dispose(GObject* object) |
| { |
| WebKitDownload* download = WEBKIT_DOWNLOAD(object); |
| WebKitDownloadPrivate* priv = download->priv; |
| |
| if (priv->outputStream) { |
| g_object_unref(priv->outputStream); |
| priv->outputStream = NULL; |
| } |
| |
| if (priv->networkRequest) { |
| g_object_unref(priv->networkRequest); |
| priv->networkRequest = NULL; |
| } |
| |
| if (priv->networkResponse) { |
| g_object_unref(priv->networkResponse); |
| priv->networkResponse = NULL; |
| } |
| |
| G_OBJECT_CLASS(webkit_download_parent_class)->dispose(object); |
| } |
| |
| static void webkit_download_finalize(GObject* object) |
| { |
| WebKitDownload* download = WEBKIT_DOWNLOAD(object); |
| WebKitDownloadPrivate* priv = download->priv; |
| |
| // We don't call webkit_download_cancel() because we don't want to emit |
| // signals when finalizing an object. |
| if (priv->resourceHandle) { |
| if (priv->status == WEBKIT_DOWNLOAD_STATUS_STARTED) { |
| priv->resourceHandle->setClient(0); |
| priv->resourceHandle->cancel(); |
| } |
| priv->resourceHandle.release(); |
| } |
| |
| delete priv->downloadClient; |
| |
| // The download object may never have _start called on it, so we |
| // need to make sure timer is non-NULL. |
| if (priv->timer) { |
| g_timer_destroy(priv->timer); |
| priv->timer = NULL; |
| } |
| |
| g_free(priv->destinationURI); |
| g_free(priv->suggestedFilename); |
| |
| G_OBJECT_CLASS(webkit_download_parent_class)->finalize(object); |
| } |
| |
| static void webkit_download_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec) |
| { |
| WebKitDownload* download = WEBKIT_DOWNLOAD(object); |
| |
| switch(prop_id) { |
| case PROP_NETWORK_REQUEST: |
| g_value_set_object(value, webkit_download_get_network_request(download)); |
| break; |
| case PROP_NETWORK_RESPONSE: |
| g_value_set_object(value, webkit_download_get_network_response(download)); |
| break; |
| case PROP_DESTINATION_URI: |
| g_value_set_string(value, webkit_download_get_destination_uri(download)); |
| break; |
| case PROP_SUGGESTED_FILENAME: |
| g_value_set_string(value, webkit_download_get_suggested_filename(download)); |
| break; |
| case PROP_PROGRESS: |
| g_value_set_double(value, webkit_download_get_progress(download)); |
| break; |
| case PROP_STATUS: |
| g_value_set_enum(value, webkit_download_get_status(download)); |
| break; |
| case PROP_CURRENT_SIZE: |
| g_value_set_uint64(value, webkit_download_get_current_size(download)); |
| break; |
| case PROP_TOTAL_SIZE: |
| g_value_set_uint64(value, webkit_download_get_total_size(download)); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); |
| } |
| } |
| |
| static void webkit_download_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec *pspec) |
| { |
| WebKitDownload* download = WEBKIT_DOWNLOAD(object); |
| WebKitDownloadPrivate* priv = download->priv; |
| |
| switch(prop_id) { |
| case PROP_NETWORK_REQUEST: |
| priv->networkRequest = WEBKIT_NETWORK_REQUEST(g_value_dup_object(value)); |
| break; |
| case PROP_NETWORK_RESPONSE: |
| priv->networkResponse = WEBKIT_NETWORK_RESPONSE(g_value_dup_object(value)); |
| break; |
| case PROP_DESTINATION_URI: |
| webkit_download_set_destination_uri(download, g_value_get_string(value)); |
| break; |
| case PROP_STATUS: |
| webkit_download_set_status(download, static_cast<WebKitDownloadStatus>(g_value_get_enum(value))); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); |
| } |
| } |
| |
| static void webkit_download_class_init(WebKitDownloadClass* downloadClass) |
| { |
| GObjectClass* objectClass = G_OBJECT_CLASS(downloadClass); |
| objectClass->dispose = webkit_download_dispose; |
| objectClass->finalize = webkit_download_finalize; |
| objectClass->get_property = webkit_download_get_property; |
| objectClass->set_property = webkit_download_set_property; |
| |
| webkitInit(); |
| |
| /** |
| * WebKitDownload::error: |
| * @download: the object on which the signal is emitted |
| * @error_code: the corresponding error code |
| * @error_detail: detailed error code for the error, see |
| * #WebKitDownloadError |
| * @reason: a string describing the error |
| * |
| * Emitted when @download is interrupted either by user action or by |
| * network errors, @error_detail will take any value of |
| * #WebKitDownloadError. |
| * |
| * Since: 1.1.2 |
| */ |
| webkit_download_signals[ERROR] = g_signal_new("error", |
| G_TYPE_FROM_CLASS(downloadClass), |
| (GSignalFlags)G_SIGNAL_RUN_LAST, |
| 0, |
| g_signal_accumulator_true_handled, |
| NULL, |
| webkit_marshal_BOOLEAN__INT_INT_STRING, |
| G_TYPE_BOOLEAN, 3, |
| G_TYPE_INT, |
| G_TYPE_INT, |
| G_TYPE_STRING); |
| |
| // Properties. |
| |
| /** |
| * WebKitDownload:network-request |
| * |
| * The #WebKitNetworkRequest instance associated with the download. |
| * |
| * Since: 1.1.2 |
| */ |
| g_object_class_install_property(objectClass, |
| PROP_NETWORK_REQUEST, |
| g_param_spec_object("network-request", |
| _("Network Request"), |
| _("The network request for the URI that should be downloaded"), |
| WEBKIT_TYPE_NETWORK_REQUEST, |
| (GParamFlags)(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY))); |
| |
| /** |
| * WebKitDownload:network-response |
| * |
| * The #WebKitNetworkResponse instance associated with the download. |
| * |
| * Since: 1.1.16 |
| */ |
| g_object_class_install_property(objectClass, |
| PROP_NETWORK_RESPONSE, |
| g_param_spec_object("network-response", |
| _("Network Response"), |
| _("The network response for the URI that should be downloaded"), |
| WEBKIT_TYPE_NETWORK_RESPONSE, |
| (GParamFlags)(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY))); |
| |
| /** |
| * WebKitDownload:destination-uri |
| * |
| * The URI of the save location for this download. |
| * |
| * Since: 1.1.2 |
| */ |
| g_object_class_install_property(objectClass, |
| PROP_DESTINATION_URI, |
| g_param_spec_string("destination-uri", |
| _("Destination URI"), |
| _("The destination URI where to save the file"), |
| "", |
| WEBKIT_PARAM_READWRITE)); |
| |
| /** |
| * WebKitDownload:suggested-filename |
| * |
| * The file name suggested as default when saving |
| * |
| * Since: 1.1.2 |
| */ |
| g_object_class_install_property(objectClass, |
| PROP_SUGGESTED_FILENAME, |
| g_param_spec_string("suggested-filename", |
| _("Suggested Filename"), |
| _("The filename suggested as default when saving"), |
| "", |
| WEBKIT_PARAM_READABLE)); |
| |
| /** |
| * WebKitDownload:progress: |
| * |
| * Determines the current progress of the download. Notice that, |
| * although the progress changes are reported as soon as possible, |
| * the emission of the notify signal for this property is |
| * throttled, for the benefit of download managers. If you care |
| * about every update, use WebKitDownload:current-size. |
| * |
| * Since: 1.1.2 |
| */ |
| g_object_class_install_property(objectClass, PROP_PROGRESS, |
| g_param_spec_double("progress", |
| _("Progress"), |
| _("Determines the current progress of the download"), |
| 0.0, 1.0, 1.0, |
| WEBKIT_PARAM_READABLE)); |
| |
| /** |
| * WebKitDownload:status: |
| * |
| * Determines the current status of the download. |
| * |
| * Since: 1.1.2 |
| */ |
| g_object_class_install_property(objectClass, PROP_STATUS, |
| g_param_spec_enum("status", |
| _("Status"), |
| _("Determines the current status of the download"), |
| WEBKIT_TYPE_DOWNLOAD_STATUS, |
| WEBKIT_DOWNLOAD_STATUS_CREATED, |
| WEBKIT_PARAM_READABLE)); |
| |
| /** |
| * WebKitDownload:current-size |
| * |
| * The length of the data already downloaded |
| * |
| * Since: 1.1.2 |
| */ |
| g_object_class_install_property(objectClass, |
| PROP_CURRENT_SIZE, |
| g_param_spec_uint64("current-size", |
| _("Current Size"), |
| _("The length of the data already downloaded"), |
| 0, G_MAXUINT64, 0, |
| WEBKIT_PARAM_READABLE)); |
| |
| /** |
| * WebKitDownload:total-size |
| * |
| * The total size of the file |
| * |
| * Since: 1.1.2 |
| */ |
| g_object_class_install_property(objectClass, |
| PROP_CURRENT_SIZE, |
| g_param_spec_uint64("total-size", |
| _("Total Size"), |
| _("The total size of the file"), |
| 0, G_MAXUINT64, 0, |
| WEBKIT_PARAM_READABLE)); |
| |
| g_type_class_add_private(downloadClass, sizeof(WebKitDownloadPrivate)); |
| } |
| |
| static void webkit_download_init(WebKitDownload* download) |
| { |
| WebKitDownloadPrivate* priv = G_TYPE_INSTANCE_GET_PRIVATE(download, WEBKIT_TYPE_DOWNLOAD, WebKitDownloadPrivate); |
| download->priv = priv; |
| |
| priv->downloadClient = new DownloadClient(download); |
| priv->currentSize = 0; |
| priv->status = WEBKIT_DOWNLOAD_STATUS_CREATED; |
| } |
| |
| /** |
| * webkit_download_new: |
| * @request: a #WebKitNetworkRequest |
| * |
| * Creates a new #WebKitDownload object for the given |
| * #WebKitNetworkRequest object. |
| * |
| * Returns: the new #WebKitDownload |
| * |
| * Since: 1.1.2 |
| */ |
| WebKitDownload* webkit_download_new(WebKitNetworkRequest* request) |
| { |
| g_return_val_if_fail(request, NULL); |
| |
| return WEBKIT_DOWNLOAD(g_object_new(WEBKIT_TYPE_DOWNLOAD, "network-request", request, NULL)); |
| } |
| |
| // Internal usage only |
| WebKitDownload* webkit_download_new_with_handle(WebKitNetworkRequest* request, WebCore::ResourceHandle* handle, const WebCore::ResourceResponse& response) |
| { |
| g_return_val_if_fail(request, NULL); |
| |
| ResourceHandleInternal* d = handle->getInternal(); |
| if (d->m_soupMessage) |
| soup_session_pause_message(webkit_get_default_session(), d->m_soupMessage.get()); |
| |
| WebKitDownload* download = WEBKIT_DOWNLOAD(g_object_new(WEBKIT_TYPE_DOWNLOAD, "network-request", request, NULL)); |
| WebKitDownloadPrivate* priv = download->priv; |
| |
| handle->ref(); |
| priv->resourceHandle = handle; |
| |
| webkit_download_set_response(download, response); |
| |
| return download; |
| } |
| |
| static gboolean webkit_download_open_stream_for_uri(WebKitDownload* download, const gchar* uri, gboolean append=FALSE) |
| { |
| g_return_val_if_fail(uri, FALSE); |
| |
| WebKitDownloadPrivate* priv = download->priv; |
| GFile* file = g_file_new_for_uri(uri); |
| GError* error = NULL; |
| |
| if (append) |
| priv->outputStream = g_file_append_to(file, G_FILE_CREATE_NONE, NULL, &error); |
| else |
| priv->outputStream = g_file_replace(file, NULL, TRUE, G_FILE_CREATE_NONE, NULL, &error); |
| |
| g_object_unref(file); |
| |
| if (error) { |
| gboolean handled; |
| g_signal_emit_by_name(download, "error", 0, WEBKIT_DOWNLOAD_ERROR_DESTINATION, error->message, &handled); |
| g_error_free(error); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static void webkit_download_close_stream(WebKitDownload* download) |
| { |
| WebKitDownloadPrivate* priv = download->priv; |
| if (priv->outputStream) { |
| g_object_unref(priv->outputStream); |
| priv->outputStream = NULL; |
| } |
| } |
| |
| /** |
| * webkit_download_start: |
| * @download: the #WebKitDownload |
| * |
| * Initiates the download. Notice that you must have set the |
| * destination-uri property before calling this method. |
| * |
| * Since: 1.1.2 |
| */ |
| void webkit_download_start(WebKitDownload* download) |
| { |
| g_return_if_fail(WEBKIT_IS_DOWNLOAD(download)); |
| |
| WebKitDownloadPrivate* priv = download->priv; |
| g_return_if_fail(priv->destinationURI); |
| g_return_if_fail(priv->status == WEBKIT_DOWNLOAD_STATUS_CREATED); |
| g_return_if_fail(priv->timer == NULL); |
| |
| // For GTK, when downloading a file NetworkingContext is null |
| if (!priv->resourceHandle) |
| priv->resourceHandle = ResourceHandle::create(/* Null NetworkingContext */ NULL, core(priv->networkRequest), priv->downloadClient, false, false); |
| else { |
| priv->resourceHandle->setClient(priv->downloadClient); |
| |
| ResourceHandleInternal* d = priv->resourceHandle->getInternal(); |
| if (d->m_soupMessage) |
| soup_session_unpause_message(webkit_get_default_session(), d->m_soupMessage.get()); |
| } |
| |
| priv->timer = g_timer_new(); |
| webkit_download_open_stream_for_uri(download, priv->destinationURI); |
| } |
| |
| /** |
| * webkit_download_cancel: |
| * @download: the #WebKitDownload |
| * |
| * Cancels the download. Calling this will not free the |
| * #WebKitDownload object, so you still need to call |
| * g_object_unref() on it, if you are the owner of a reference. Notice |
| * that cancelling the download provokes the emission of the |
| * WebKitDownload::error signal, reporting that the download was |
| * cancelled. |
| * |
| * Since: 1.1.2 |
| */ |
| void webkit_download_cancel(WebKitDownload* download) |
| { |
| g_return_if_fail(WEBKIT_IS_DOWNLOAD(download)); |
| |
| WebKitDownloadPrivate* priv = download->priv; |
| |
| // Cancel may be called even if start was not called, so we need |
| // to make sure timer is non-NULL. |
| if (priv->timer) |
| g_timer_stop(priv->timer); |
| |
| if (priv->resourceHandle) |
| priv->resourceHandle->cancel(); |
| |
| webkit_download_set_status(download, WEBKIT_DOWNLOAD_STATUS_CANCELLED); |
| |
| gboolean handled; |
| g_signal_emit_by_name(download, "error", 0, WEBKIT_DOWNLOAD_ERROR_CANCELLED_BY_USER, _("User cancelled the download"), &handled); |
| } |
| |
| /** |
| * webkit_download_get_uri: |
| * @download: the #WebKitDownload |
| * |
| * Convenience method to retrieve the URI from the |
| * #WebKitNetworkRequest which is being downloaded. |
| * |
| * Returns: the uri |
| * |
| * Since: 1.1.2 |
| */ |
| const gchar* webkit_download_get_uri(WebKitDownload* download) |
| { |
| g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), NULL); |
| |
| WebKitDownloadPrivate* priv = download->priv; |
| return webkit_network_request_get_uri(priv->networkRequest); |
| } |
| |
| /** |
| * webkit_download_get_network_request: |
| * @download: the #WebKitDownload |
| * |
| * Retrieves the #WebKitNetworkRequest object that backs the download |
| * process. |
| * |
| * Returns: (transfer none): the #WebKitNetworkRequest instance |
| * |
| * Since: 1.1.2 |
| */ |
| WebKitNetworkRequest* webkit_download_get_network_request(WebKitDownload* download) |
| { |
| g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), NULL); |
| |
| WebKitDownloadPrivate* priv = download->priv; |
| return priv->networkRequest; |
| } |
| |
| /** |
| * webkit_download_get_network_response: |
| * @download: the #WebKitDownload |
| * |
| * Retrieves the #WebKitNetworkResponse object that backs the download |
| * process. |
| * |
| * Returns: (transfer none): the #WebKitNetworkResponse instance |
| * |
| * Since: 1.1.16 |
| */ |
| WebKitNetworkResponse* webkit_download_get_network_response(WebKitDownload* download) |
| { |
| g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), NULL); |
| |
| WebKitDownloadPrivate* priv = download->priv; |
| return priv->networkResponse; |
| } |
| |
| static void webkit_download_set_response(WebKitDownload* download, const ResourceResponse& response) |
| { |
| WebKitDownloadPrivate* priv = download->priv; |
| priv->networkResponse = kitNew(response); |
| |
| if (!response.isNull() && !response.suggestedFilename().isEmpty()) |
| webkit_download_set_suggested_filename(download, response.suggestedFilename().utf8().data()); |
| } |
| |
| /** |
| * webkit_download_get_suggested_filename: |
| * @download: the #WebKitDownload |
| * |
| * Retrieves the filename that was suggested by the server, or the one |
| * derived by WebKit from the URI. |
| * |
| * Returns: the suggested filename |
| * |
| * Since: 1.1.2 |
| */ |
| const gchar* webkit_download_get_suggested_filename(WebKitDownload* download) |
| { |
| g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), NULL); |
| |
| WebKitDownloadPrivate* priv = download->priv; |
| if (priv->suggestedFilename) |
| return priv->suggestedFilename; |
| |
| KURL url = KURL(KURL(), webkit_network_request_get_uri(priv->networkRequest)); |
| url.setQuery(String()); |
| url.removeFragmentIdentifier(); |
| priv->suggestedFilename = g_strdup(decodeURLEscapeSequences(url.lastPathComponent()).utf8().data()); |
| return priv->suggestedFilename; |
| } |
| |
| // for internal use only |
| void webkit_download_set_suggested_filename(WebKitDownload* download, const gchar* suggestedFilename) |
| { |
| WebKitDownloadPrivate* priv = download->priv; |
| g_free(priv->suggestedFilename); |
| priv->suggestedFilename = g_strdup(suggestedFilename); |
| |
| g_object_notify(G_OBJECT(download), "suggested-filename"); |
| } |
| |
| |
| /** |
| * webkit_download_get_destination_uri: |
| * @download: the #WebKitDownload |
| * |
| * Obtains the URI to which the downloaded file will be written. This |
| * must have been set by the application before calling |
| * webkit_download_start(), and may be %NULL. |
| * |
| * Returns: the destination URI or %NULL |
| * |
| * Since: 1.1.2 |
| */ |
| const gchar* webkit_download_get_destination_uri(WebKitDownload* download) |
| { |
| g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), NULL); |
| |
| WebKitDownloadPrivate* priv = download->priv; |
| return priv->destinationURI; |
| } |
| |
| /** |
| * webkit_download_set_destination_uri: |
| * @download: the #WebKitDownload |
| * @destination_uri: the destination URI |
| * |
| * Defines the URI that should be used to save the downloaded file to. |
| * |
| * Since: 1.1.2 |
| */ |
| void webkit_download_set_destination_uri(WebKitDownload* download, const gchar* destination_uri) |
| { |
| g_return_if_fail(WEBKIT_IS_DOWNLOAD(download)); |
| g_return_if_fail(destination_uri); |
| |
| WebKitDownloadPrivate* priv = download->priv; |
| if (priv->destinationURI && !strcmp(priv->destinationURI, destination_uri)) |
| return; |
| |
| if (priv->status != WEBKIT_DOWNLOAD_STATUS_CREATED && priv->status != WEBKIT_DOWNLOAD_STATUS_CANCELLED) { |
| ASSERT(priv->destinationURI); |
| |
| gboolean downloading = priv->outputStream != NULL; |
| if (downloading) |
| webkit_download_close_stream(download); |
| |
| GFile* src = g_file_new_for_uri(priv->destinationURI); |
| GFile* dest = g_file_new_for_uri(destination_uri); |
| GError* error = NULL; |
| |
| g_file_move(src, dest, G_FILE_COPY_BACKUP, NULL, NULL, NULL, &error); |
| |
| g_object_unref(src); |
| g_object_unref(dest); |
| |
| g_free(priv->destinationURI); |
| priv->destinationURI = g_strdup(destination_uri); |
| |
| if (error) { |
| gboolean handled; |
| g_signal_emit_by_name(download, "error", 0, WEBKIT_DOWNLOAD_ERROR_DESTINATION, error->message, &handled); |
| g_error_free(error); |
| return; |
| } |
| |
| if (downloading) { |
| if (!webkit_download_open_stream_for_uri(download, destination_uri, TRUE)) { |
| webkit_download_cancel(download); |
| return; |
| } |
| } |
| } else { |
| g_free(priv->destinationURI); |
| priv->destinationURI = g_strdup(destination_uri); |
| } |
| |
| // Only notify change if everything went fine. |
| g_object_notify(G_OBJECT(download), "destination-uri"); |
| } |
| |
| /** |
| * webkit_download_get_status: |
| * @download: the #WebKitDownload |
| * |
| * Obtains the current status of the download, as a |
| * #WebKitDownloadStatus. |
| * |
| * Returns: the current #WebKitDownloadStatus |
| * |
| * Since: 1.1.2 |
| */ |
| WebKitDownloadStatus webkit_download_get_status(WebKitDownload* download) |
| { |
| g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), WEBKIT_DOWNLOAD_STATUS_ERROR); |
| |
| WebKitDownloadPrivate* priv = download->priv; |
| return priv->status; |
| } |
| |
| static void webkit_download_set_status(WebKitDownload* download, WebKitDownloadStatus status) |
| { |
| g_return_if_fail(WEBKIT_IS_DOWNLOAD(download)); |
| |
| WebKitDownloadPrivate* priv = download->priv; |
| priv->status = status; |
| |
| g_object_notify(G_OBJECT(download), "status"); |
| } |
| |
| /** |
| * webkit_download_get_total_size: |
| * @download: the #WebKitDownload |
| * |
| * Returns the expected total size of the download. This is expected |
| * because the server may provide incorrect or missing |
| * Content-Length. Notice that this may grow over time, as it will be |
| * always the same as current_size in the cases where current size |
| * surpasses it. |
| * |
| * Returns: the expected total size of the downloaded file |
| * |
| * Since: 1.1.2 |
| */ |
| guint64 webkit_download_get_total_size(WebKitDownload* download) |
| { |
| g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 0); |
| |
| WebKitDownloadPrivate* priv = download->priv; |
| SoupMessage* message = priv->networkResponse ? webkit_network_response_get_message(priv->networkResponse) : NULL; |
| |
| if (!message) |
| return 0; |
| |
| return MAX(priv->currentSize, static_cast<guint64>(soup_message_headers_get_content_length(message->response_headers))); |
| } |
| |
| /** |
| * webkit_download_get_current_size: |
| * @download: the #WebKitDownload |
| * |
| * Current already downloaded size. |
| * |
| * Returns: the already downloaded size |
| * |
| * Since: 1.1.2 |
| */ |
| guint64 webkit_download_get_current_size(WebKitDownload* download) |
| { |
| g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 0); |
| |
| WebKitDownloadPrivate* priv = download->priv; |
| return priv->currentSize; |
| } |
| |
| /** |
| * webkit_download_get_progress: |
| * @download: a #WebKitDownload |
| * |
| * Determines the current progress of the download. |
| * |
| * Returns: a #gdouble ranging from 0.0 to 1.0. |
| * |
| * Since: 1.1.2 |
| */ |
| gdouble webkit_download_get_progress(WebKitDownload* download) |
| { |
| g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 1.0); |
| |
| WebKitDownloadPrivate* priv = download->priv; |
| if (!priv->networkResponse) |
| return 0.0; |
| |
| gdouble total_size = static_cast<gdouble>(webkit_download_get_total_size(download)); |
| |
| if (total_size == 0) |
| return 1.0; |
| |
| return ((gdouble)priv->currentSize) / total_size; |
| } |
| |
| /** |
| * webkit_download_get_elapsed_time: |
| * @download: a #WebKitDownload |
| * |
| * Elapsed time for the download in seconds, including any fractional |
| * part. If the download is finished, had an error or was cancelled |
| * this is the time between its start and the event. |
| * |
| * Returns: seconds since the download was started, as a #gdouble |
| * |
| * Since: 1.1.2 |
| */ |
| gdouble webkit_download_get_elapsed_time(WebKitDownload* download) |
| { |
| g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 0.0); |
| |
| WebKitDownloadPrivate* priv = download->priv; |
| if (!priv->timer) |
| return 0; |
| |
| return g_timer_elapsed(priv->timer, NULL); |
| } |
| |
| static void webkit_download_received_data(WebKitDownload* download, const gchar* data, int length) |
| { |
| WebKitDownloadPrivate* priv = download->priv; |
| |
| if (priv->currentSize == 0) |
| webkit_download_set_status(download, WEBKIT_DOWNLOAD_STATUS_STARTED); |
| |
| ASSERT(priv->outputStream); |
| |
| gsize bytes_written; |
| GError* error = NULL; |
| |
| g_output_stream_write_all(G_OUTPUT_STREAM(priv->outputStream), |
| data, length, &bytes_written, NULL, &error); |
| |
| if (error) { |
| gboolean handled; |
| g_signal_emit_by_name(download, "error", 0, WEBKIT_DOWNLOAD_ERROR_DESTINATION, error->message, &handled); |
| g_error_free(error); |
| return; |
| } |
| |
| priv->currentSize += length; |
| g_object_notify(G_OBJECT(download), "current-size"); |
| |
| ASSERT(priv->networkResponse); |
| if (priv->currentSize > webkit_download_get_total_size(download)) |
| g_object_notify(G_OBJECT(download), "total-size"); |
| |
| // Throttle progress notification to not consume high amounts of |
| // CPU on fast links, except when the last notification occured |
| // in more then 0.7 secs from now, or the last notified progress |
| // is passed in 1% or we reached the end. |
| static gdouble lastProgress = 0; |
| static gdouble lastElapsed = 0; |
| gdouble currentElapsed = g_timer_elapsed(priv->timer, NULL); |
| gdouble currentProgress = webkit_download_get_progress(download); |
| |
| if (lastElapsed |
| && lastProgress |
| && (currentElapsed - lastElapsed) < 0.7 |
| && (currentProgress - lastProgress) < 0.01 |
| && currentProgress < 1.0) { |
| return; |
| } |
| lastElapsed = currentElapsed; |
| lastProgress = currentProgress; |
| |
| g_object_notify(G_OBJECT(download), "progress"); |
| } |
| |
| static void webkit_download_finished_loading(WebKitDownload* download) |
| { |
| webkit_download_close_stream(download); |
| |
| WebKitDownloadPrivate* priv = download->priv; |
| |
| g_timer_stop(priv->timer); |
| |
| g_object_notify(G_OBJECT(download), "progress"); |
| webkit_download_set_status(download, WEBKIT_DOWNLOAD_STATUS_FINISHED); |
| } |
| |
| static void webkit_download_error(WebKitDownload* download, const ResourceError& error) |
| { |
| webkit_download_close_stream(download); |
| |
| WebKitDownloadPrivate* priv = download->priv; |
| GRefPtr<WebKitDownload> protect(download); |
| |
| g_timer_stop(priv->timer); |
| webkit_download_set_status(download, WEBKIT_DOWNLOAD_STATUS_ERROR); |
| |
| gboolean handled; |
| g_signal_emit_by_name(download, "error", 0, WEBKIT_DOWNLOAD_ERROR_NETWORK, error.localizedDescription().utf8().data(), &handled); |
| } |
| |
| DownloadClient::DownloadClient(WebKitDownload* download) |
| : m_download(download) |
| { |
| } |
| |
| void DownloadClient::didReceiveResponse(ResourceHandle*, const ResourceResponse& response) |
| { |
| webkit_download_set_response(m_download, response); |
| } |
| |
| void DownloadClient::didReceiveData(ResourceHandle*, const char* data, int length, int encodedDataLength) |
| { |
| webkit_download_received_data(m_download, data, length); |
| } |
| |
| void DownloadClient::didFinishLoading(ResourceHandle*, double) |
| { |
| webkit_download_finished_loading(m_download); |
| } |
| |
| void DownloadClient::didFail(ResourceHandle*, const ResourceError& error) |
| { |
| webkit_download_error(m_download, error); |
| } |
| |
| void DownloadClient::wasBlocked(ResourceHandle*) |
| { |
| // FIXME: Implement this when we have the new frame loader signals |
| // and error handling. |
| notImplemented(); |
| } |
| |
| void DownloadClient::cannotShowURL(ResourceHandle*) |
| { |
| // FIXME: Implement this when we have the new frame loader signals |
| // and error handling. |
| notImplemented(); |
| } |