Home | History | Annotate | Download | only in webkit
      1 /*
      2  * Copyright (C) 2008 Collabora Ltd.
      3  * Copyright (C) 2009 Gustavo Noronha Silva <gns (at) gnome.org>
      4  *
      5  * This library is free software; you can redistribute it and/or
      6  * modify it under the terms of the GNU Library General Public
      7  * License as published by the Free Software Foundation; either
      8  * version 2 of the License, or (at your option) any later version.
      9  *
     10  * This library is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13  * Library General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU Library General Public License
     16  * along with this library; see the file COPYING.LIB.  If not, write to
     17  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     18  * Boston, MA 02110-1301, USA.
     19  */
     20 
     21 #include "config.h"
     22 #include "webkitdownload.h"
     23 
     24 #include "GRefPtr.h"
     25 #include "Noncopyable.h"
     26 #include "NotImplemented.h"
     27 #include "ResourceHandleClient.h"
     28 #include "ResourceHandleInternal.h"
     29 #include "ResourceRequest.h"
     30 #include "ResourceResponse.h"
     31 #include "webkitdownloadprivate.h"
     32 #include "webkitenumtypes.h"
     33 #include "webkitglobals.h"
     34 #include "webkitglobalsprivate.h"
     35 #include "webkitmarshal.h"
     36 #include "webkitnetworkrequestprivate.h"
     37 #include "webkitnetworkresponse.h"
     38 #include "webkitnetworkresponseprivate.h"
     39 #include <glib/gi18n-lib.h>
     40 #include <glib/gstdio.h>
     41 #include <wtf/text/CString.h>
     42 
     43 #ifdef ERROR
     44 #undef ERROR
     45 #endif
     46 
     47 using namespace WebKit;
     48 using namespace WebCore;
     49 
     50 /**
     51  * SECTION:webkitdownload
     52  * @short_description: Object used to communicate with the application when downloading.
     53  *
     54  * #WebKitDownload carries information about a download request,
     55  * including a #WebKitNetworkRequest object. The application may use
     56  * this object to control the download process, or to simply figure
     57  * out what is to be downloaded, and do it itself.
     58  */
     59 
     60 class DownloadClient : public ResourceHandleClient {
     61     WTF_MAKE_NONCOPYABLE(DownloadClient);
     62     public:
     63         DownloadClient(WebKitDownload*);
     64 
     65         virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&);
     66         virtual void didReceiveData(ResourceHandle*, const char*, int, int);
     67         virtual void didFinishLoading(ResourceHandle*, double);
     68         virtual void didFail(ResourceHandle*, const ResourceError&);
     69         virtual void wasBlocked(ResourceHandle*);
     70         virtual void cannotShowURL(ResourceHandle*);
     71 
     72     private:
     73         WebKitDownload* m_download;
     74 };
     75 
     76 struct _WebKitDownloadPrivate {
     77     gchar* destinationURI;
     78     gchar* suggestedFilename;
     79     guint64 currentSize;
     80     GTimer* timer;
     81     WebKitDownloadStatus status;
     82     GFileOutputStream* outputStream;
     83     DownloadClient* downloadClient;
     84     WebKitNetworkRequest* networkRequest;
     85     WebKitNetworkResponse* networkResponse;
     86     RefPtr<ResourceHandle> resourceHandle;
     87 };
     88 
     89 enum {
     90     // Normal signals.
     91     ERROR,
     92     LAST_SIGNAL
     93 };
     94 
     95 static guint webkit_download_signals[LAST_SIGNAL] = { 0 };
     96 
     97 enum {
     98     PROP_0,
     99 
    100     PROP_NETWORK_REQUEST,
    101     PROP_DESTINATION_URI,
    102     PROP_SUGGESTED_FILENAME,
    103     PROP_PROGRESS,
    104     PROP_STATUS,
    105     PROP_CURRENT_SIZE,
    106     PROP_TOTAL_SIZE,
    107     PROP_NETWORK_RESPONSE
    108 };
    109 
    110 G_DEFINE_TYPE(WebKitDownload, webkit_download, G_TYPE_OBJECT);
    111 
    112 
    113 static void webkit_download_set_response(WebKitDownload* download, const ResourceResponse& response);
    114 static void webkit_download_set_status(WebKitDownload* download, WebKitDownloadStatus status);
    115 
    116 static void webkit_download_dispose(GObject* object)
    117 {
    118     WebKitDownload* download = WEBKIT_DOWNLOAD(object);
    119     WebKitDownloadPrivate* priv = download->priv;
    120 
    121     if (priv->outputStream) {
    122         g_object_unref(priv->outputStream);
    123         priv->outputStream = NULL;
    124     }
    125 
    126     if (priv->networkRequest) {
    127         g_object_unref(priv->networkRequest);
    128         priv->networkRequest = NULL;
    129     }
    130 
    131     if (priv->networkResponse) {
    132         g_object_unref(priv->networkResponse);
    133         priv->networkResponse = NULL;
    134     }
    135 
    136     G_OBJECT_CLASS(webkit_download_parent_class)->dispose(object);
    137 }
    138 
    139 static void webkit_download_finalize(GObject* object)
    140 {
    141     WebKitDownload* download = WEBKIT_DOWNLOAD(object);
    142     WebKitDownloadPrivate* priv = download->priv;
    143 
    144     // We don't call webkit_download_cancel() because we don't want to emit
    145     // signals when finalizing an object.
    146     if (priv->resourceHandle) {
    147         if (priv->status == WEBKIT_DOWNLOAD_STATUS_STARTED) {
    148             priv->resourceHandle->setClient(0);
    149             priv->resourceHandle->cancel();
    150         }
    151         priv->resourceHandle.release();
    152     }
    153 
    154     delete priv->downloadClient;
    155 
    156     // The download object may never have _start called on it, so we
    157     // need to make sure timer is non-NULL.
    158     if (priv->timer) {
    159         g_timer_destroy(priv->timer);
    160         priv->timer = NULL;
    161     }
    162 
    163     g_free(priv->destinationURI);
    164     g_free(priv->suggestedFilename);
    165 
    166     G_OBJECT_CLASS(webkit_download_parent_class)->finalize(object);
    167 }
    168 
    169 static void webkit_download_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec)
    170 {
    171     WebKitDownload* download = WEBKIT_DOWNLOAD(object);
    172 
    173     switch(prop_id) {
    174     case PROP_NETWORK_REQUEST:
    175         g_value_set_object(value, webkit_download_get_network_request(download));
    176         break;
    177     case PROP_NETWORK_RESPONSE:
    178         g_value_set_object(value, webkit_download_get_network_response(download));
    179         break;
    180     case PROP_DESTINATION_URI:
    181         g_value_set_string(value, webkit_download_get_destination_uri(download));
    182         break;
    183     case PROP_SUGGESTED_FILENAME:
    184         g_value_set_string(value, webkit_download_get_suggested_filename(download));
    185         break;
    186     case PROP_PROGRESS:
    187         g_value_set_double(value, webkit_download_get_progress(download));
    188         break;
    189     case PROP_STATUS:
    190         g_value_set_enum(value, webkit_download_get_status(download));
    191         break;
    192     case PROP_CURRENT_SIZE:
    193         g_value_set_uint64(value, webkit_download_get_current_size(download));
    194         break;
    195     case PROP_TOTAL_SIZE:
    196         g_value_set_uint64(value, webkit_download_get_total_size(download));
    197         break;
    198     default:
    199         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    200     }
    201 }
    202 
    203 static void webkit_download_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec *pspec)
    204 {
    205     WebKitDownload* download = WEBKIT_DOWNLOAD(object);
    206     WebKitDownloadPrivate* priv = download->priv;
    207 
    208     switch(prop_id) {
    209     case PROP_NETWORK_REQUEST:
    210         priv->networkRequest = WEBKIT_NETWORK_REQUEST(g_value_dup_object(value));
    211         break;
    212     case PROP_NETWORK_RESPONSE:
    213         priv->networkResponse = WEBKIT_NETWORK_RESPONSE(g_value_dup_object(value));
    214         break;
    215     case PROP_DESTINATION_URI:
    216         webkit_download_set_destination_uri(download, g_value_get_string(value));
    217         break;
    218     case PROP_STATUS:
    219         webkit_download_set_status(download, static_cast<WebKitDownloadStatus>(g_value_get_enum(value)));
    220         break;
    221     default:
    222         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    223     }
    224 }
    225 
    226 static void webkit_download_class_init(WebKitDownloadClass* downloadClass)
    227 {
    228     GObjectClass* objectClass = G_OBJECT_CLASS(downloadClass);
    229     objectClass->dispose = webkit_download_dispose;
    230     objectClass->finalize = webkit_download_finalize;
    231     objectClass->get_property = webkit_download_get_property;
    232     objectClass->set_property = webkit_download_set_property;
    233 
    234     webkitInit();
    235 
    236     /**
    237      * WebKitDownload::error:
    238      * @download: the object on which the signal is emitted
    239      * @error_code: the corresponding error code
    240      * @error_detail: detailed error code for the error, see
    241      * #WebKitDownloadError
    242      * @reason: a string describing the error
    243      *
    244      * Emitted when @download is interrupted either by user action or by
    245      * network errors, @error_detail will take any value of
    246      * #WebKitDownloadError.
    247      *
    248      * Since: 1.1.2
    249      */
    250     webkit_download_signals[ERROR] = g_signal_new("error",
    251             G_TYPE_FROM_CLASS(downloadClass),
    252             (GSignalFlags)G_SIGNAL_RUN_LAST,
    253             0,
    254             g_signal_accumulator_true_handled,
    255             NULL,
    256             webkit_marshal_BOOLEAN__INT_INT_STRING,
    257             G_TYPE_BOOLEAN, 3,
    258             G_TYPE_INT,
    259             G_TYPE_INT,
    260             G_TYPE_STRING);
    261 
    262     // Properties.
    263 
    264     /**
    265      * WebKitDownload:network-request
    266      *
    267      * The #WebKitNetworkRequest instance associated with the download.
    268      *
    269      * Since: 1.1.2
    270      */
    271     g_object_class_install_property(objectClass,
    272                                     PROP_NETWORK_REQUEST,
    273                                     g_param_spec_object("network-request",
    274                                                         _("Network Request"),
    275                                                         _("The network request for the URI that should be downloaded"),
    276                                                         WEBKIT_TYPE_NETWORK_REQUEST,
    277                                                         (GParamFlags)(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
    278 
    279     /**
    280      * WebKitDownload:network-response
    281      *
    282      * The #WebKitNetworkResponse instance associated with the download.
    283      *
    284      * Since: 1.1.16
    285      */
    286     g_object_class_install_property(objectClass,
    287                                     PROP_NETWORK_RESPONSE,
    288                                     g_param_spec_object("network-response",
    289                                                         _("Network Response"),
    290                                                         _("The network response for the URI that should be downloaded"),
    291                                                         WEBKIT_TYPE_NETWORK_RESPONSE,
    292                                                         (GParamFlags)(WEBKIT_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)));
    293 
    294     /**
    295      * WebKitDownload:destination-uri
    296      *
    297      * The URI of the save location for this download.
    298      *
    299      * Since: 1.1.2
    300      */
    301     g_object_class_install_property(objectClass,
    302                                     PROP_DESTINATION_URI,
    303                                     g_param_spec_string("destination-uri",
    304                                                         _("Destination URI"),
    305                                                         _("The destination URI where to save the file"),
    306                                                         "",
    307                                                         WEBKIT_PARAM_READWRITE));
    308 
    309     /**
    310      * WebKitDownload:suggested-filename
    311      *
    312      * The file name suggested as default when saving
    313      *
    314      * Since: 1.1.2
    315      */
    316     g_object_class_install_property(objectClass,
    317                                     PROP_SUGGESTED_FILENAME,
    318                                     g_param_spec_string("suggested-filename",
    319                                                         _("Suggested Filename"),
    320                                                         _("The filename suggested as default when saving"),
    321                                                         "",
    322                                                         WEBKIT_PARAM_READABLE));
    323 
    324     /**
    325      * WebKitDownload:progress:
    326      *
    327      * Determines the current progress of the download. Notice that,
    328      * although the progress changes are reported as soon as possible,
    329      * the emission of the notify signal for this property is
    330      * throttled, for the benefit of download managers. If you care
    331      * about every update, use WebKitDownload:current-size.
    332      *
    333      * Since: 1.1.2
    334      */
    335     g_object_class_install_property(objectClass, PROP_PROGRESS,
    336                                     g_param_spec_double("progress",
    337                                                         _("Progress"),
    338                                                         _("Determines the current progress of the download"),
    339                                                         0.0, 1.0, 1.0,
    340                                                         WEBKIT_PARAM_READABLE));
    341 
    342     /**
    343      * WebKitDownload:status:
    344      *
    345      * Determines the current status of the download.
    346      *
    347      * Since: 1.1.2
    348      */
    349     g_object_class_install_property(objectClass, PROP_STATUS,
    350                                     g_param_spec_enum("status",
    351                                                       _("Status"),
    352                                                       _("Determines the current status of the download"),
    353                                                       WEBKIT_TYPE_DOWNLOAD_STATUS,
    354                                                       WEBKIT_DOWNLOAD_STATUS_CREATED,
    355                                                       WEBKIT_PARAM_READABLE));
    356 
    357     /**
    358      * WebKitDownload:current-size
    359      *
    360      * The length of the data already downloaded
    361      *
    362      * Since: 1.1.2
    363      */
    364     g_object_class_install_property(objectClass,
    365                                     PROP_CURRENT_SIZE,
    366                                     g_param_spec_uint64("current-size",
    367                                                         _("Current Size"),
    368                                                         _("The length of the data already downloaded"),
    369                                                         0, G_MAXUINT64, 0,
    370                                                         WEBKIT_PARAM_READABLE));
    371 
    372     /**
    373      * WebKitDownload:total-size
    374      *
    375      * The total size of the file
    376      *
    377      * Since: 1.1.2
    378      */
    379     g_object_class_install_property(objectClass,
    380                                     PROP_CURRENT_SIZE,
    381                                     g_param_spec_uint64("total-size",
    382                                                         _("Total Size"),
    383                                                         _("The total size of the file"),
    384                                                         0, G_MAXUINT64, 0,
    385                                                         WEBKIT_PARAM_READABLE));
    386 
    387     g_type_class_add_private(downloadClass, sizeof(WebKitDownloadPrivate));
    388 }
    389 
    390 static void webkit_download_init(WebKitDownload* download)
    391 {
    392     WebKitDownloadPrivate* priv = G_TYPE_INSTANCE_GET_PRIVATE(download, WEBKIT_TYPE_DOWNLOAD, WebKitDownloadPrivate);
    393     download->priv = priv;
    394 
    395     priv->downloadClient = new DownloadClient(download);
    396     priv->currentSize = 0;
    397     priv->status = WEBKIT_DOWNLOAD_STATUS_CREATED;
    398 }
    399 
    400 /**
    401  * webkit_download_new:
    402  * @request: a #WebKitNetworkRequest
    403  *
    404  * Creates a new #WebKitDownload object for the given
    405  * #WebKitNetworkRequest object.
    406  *
    407  * Returns: the new #WebKitDownload
    408  *
    409  * Since: 1.1.2
    410  */
    411 WebKitDownload* webkit_download_new(WebKitNetworkRequest* request)
    412 {
    413     g_return_val_if_fail(request, NULL);
    414 
    415     return WEBKIT_DOWNLOAD(g_object_new(WEBKIT_TYPE_DOWNLOAD, "network-request", request, NULL));
    416 }
    417 
    418 // Internal usage only
    419 WebKitDownload* webkit_download_new_with_handle(WebKitNetworkRequest* request, WebCore::ResourceHandle* handle, const WebCore::ResourceResponse& response)
    420 {
    421     g_return_val_if_fail(request, NULL);
    422 
    423     ResourceHandleInternal* d = handle->getInternal();
    424     if (d->m_soupMessage)
    425         soup_session_pause_message(webkit_get_default_session(), d->m_soupMessage.get());
    426 
    427     WebKitDownload* download = WEBKIT_DOWNLOAD(g_object_new(WEBKIT_TYPE_DOWNLOAD, "network-request", request, NULL));
    428     WebKitDownloadPrivate* priv = download->priv;
    429 
    430     handle->ref();
    431     priv->resourceHandle = handle;
    432 
    433     webkit_download_set_response(download, response);
    434 
    435     return download;
    436 }
    437 
    438 static gboolean webkit_download_open_stream_for_uri(WebKitDownload* download, const gchar* uri, gboolean append=FALSE)
    439 {
    440     g_return_val_if_fail(uri, FALSE);
    441 
    442     WebKitDownloadPrivate* priv = download->priv;
    443     GFile* file = g_file_new_for_uri(uri);
    444     GError* error = NULL;
    445 
    446     if (append)
    447         priv->outputStream = g_file_append_to(file, G_FILE_CREATE_NONE, NULL, &error);
    448     else
    449         priv->outputStream = g_file_replace(file, NULL, TRUE, G_FILE_CREATE_NONE, NULL, &error);
    450 
    451     g_object_unref(file);
    452 
    453     if (error) {
    454         gboolean handled;
    455         g_signal_emit_by_name(download, "error", 0, WEBKIT_DOWNLOAD_ERROR_DESTINATION, error->message, &handled);
    456         g_error_free(error);
    457         return FALSE;
    458     }
    459 
    460     return TRUE;
    461 }
    462 
    463 static void webkit_download_close_stream(WebKitDownload* download)
    464 {
    465     WebKitDownloadPrivate* priv = download->priv;
    466     if (priv->outputStream) {
    467         g_object_unref(priv->outputStream);
    468         priv->outputStream = NULL;
    469     }
    470 }
    471 
    472 /**
    473  * webkit_download_start:
    474  * @download: the #WebKitDownload
    475  *
    476  * Initiates the download. Notice that you must have set the
    477  * destination-uri property before calling this method.
    478  *
    479  * Since: 1.1.2
    480  */
    481 void webkit_download_start(WebKitDownload* download)
    482 {
    483     g_return_if_fail(WEBKIT_IS_DOWNLOAD(download));
    484 
    485     WebKitDownloadPrivate* priv = download->priv;
    486     g_return_if_fail(priv->destinationURI);
    487     g_return_if_fail(priv->status == WEBKIT_DOWNLOAD_STATUS_CREATED);
    488     g_return_if_fail(priv->timer == NULL);
    489 
    490     // For GTK, when downloading a file NetworkingContext is null
    491     if (!priv->resourceHandle)
    492         priv->resourceHandle = ResourceHandle::create(/* Null NetworkingContext */ NULL, core(priv->networkRequest), priv->downloadClient, false, false);
    493     else {
    494         priv->resourceHandle->setClient(priv->downloadClient);
    495 
    496         ResourceHandleInternal* d = priv->resourceHandle->getInternal();
    497         if (d->m_soupMessage)
    498             soup_session_unpause_message(webkit_get_default_session(), d->m_soupMessage.get());
    499     }
    500 
    501     priv->timer = g_timer_new();
    502     webkit_download_open_stream_for_uri(download, priv->destinationURI);
    503 }
    504 
    505 /**
    506  * webkit_download_cancel:
    507  * @download: the #WebKitDownload
    508  *
    509  * Cancels the download. Calling this will not free the
    510  * #WebKitDownload object, so you still need to call
    511  * g_object_unref() on it, if you are the owner of a reference. Notice
    512  * that cancelling the download provokes the emission of the
    513  * WebKitDownload::error signal, reporting that the download was
    514  * cancelled.
    515  *
    516  * Since: 1.1.2
    517  */
    518 void webkit_download_cancel(WebKitDownload* download)
    519 {
    520     g_return_if_fail(WEBKIT_IS_DOWNLOAD(download));
    521 
    522     WebKitDownloadPrivate* priv = download->priv;
    523 
    524     // Cancel may be called even if start was not called, so we need
    525     // to make sure timer is non-NULL.
    526     if (priv->timer)
    527         g_timer_stop(priv->timer);
    528 
    529     if (priv->resourceHandle)
    530         priv->resourceHandle->cancel();
    531 
    532     webkit_download_set_status(download, WEBKIT_DOWNLOAD_STATUS_CANCELLED);
    533 
    534     gboolean handled;
    535     g_signal_emit_by_name(download, "error", 0, WEBKIT_DOWNLOAD_ERROR_CANCELLED_BY_USER, _("User cancelled the download"), &handled);
    536 }
    537 
    538 /**
    539  * webkit_download_get_uri:
    540  * @download: the #WebKitDownload
    541  *
    542  * Convenience method to retrieve the URI from the
    543  * #WebKitNetworkRequest which is being downloaded.
    544  *
    545  * Returns: the uri
    546  *
    547  * Since: 1.1.2
    548  */
    549 const gchar* webkit_download_get_uri(WebKitDownload* download)
    550 {
    551     g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), NULL);
    552 
    553     WebKitDownloadPrivate* priv = download->priv;
    554     return webkit_network_request_get_uri(priv->networkRequest);
    555 }
    556 
    557 /**
    558  * webkit_download_get_network_request:
    559  * @download: the #WebKitDownload
    560  *
    561  * Retrieves the #WebKitNetworkRequest object that backs the download
    562  * process.
    563  *
    564  * Returns: (transfer none): the #WebKitNetworkRequest instance
    565  *
    566  * Since: 1.1.2
    567  */
    568 WebKitNetworkRequest* webkit_download_get_network_request(WebKitDownload* download)
    569 {
    570     g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), NULL);
    571 
    572     WebKitDownloadPrivate* priv = download->priv;
    573     return priv->networkRequest;
    574 }
    575 
    576 /**
    577  * webkit_download_get_network_response:
    578  * @download: the #WebKitDownload
    579  *
    580  * Retrieves the #WebKitNetworkResponse object that backs the download
    581  * process.
    582  *
    583  * Returns: (transfer none): the #WebKitNetworkResponse instance
    584  *
    585  * Since: 1.1.16
    586  */
    587 WebKitNetworkResponse* webkit_download_get_network_response(WebKitDownload* download)
    588 {
    589     g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), NULL);
    590 
    591     WebKitDownloadPrivate* priv = download->priv;
    592     return priv->networkResponse;
    593 }
    594 
    595 static void webkit_download_set_response(WebKitDownload* download, const ResourceResponse& response)
    596 {
    597     WebKitDownloadPrivate* priv = download->priv;
    598     priv->networkResponse = kitNew(response);
    599 
    600     if (!response.isNull() && !response.suggestedFilename().isEmpty())
    601         webkit_download_set_suggested_filename(download, response.suggestedFilename().utf8().data());
    602 }
    603 
    604 /**
    605  * webkit_download_get_suggested_filename:
    606  * @download: the #WebKitDownload
    607  *
    608  * Retrieves the filename that was suggested by the server, or the one
    609  * derived by WebKit from the URI.
    610  *
    611  * Returns: the suggested filename
    612  *
    613  * Since: 1.1.2
    614  */
    615 const gchar* webkit_download_get_suggested_filename(WebKitDownload* download)
    616 {
    617     g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), NULL);
    618 
    619     WebKitDownloadPrivate* priv = download->priv;
    620     if (priv->suggestedFilename)
    621         return priv->suggestedFilename;
    622 
    623     KURL url = KURL(KURL(), webkit_network_request_get_uri(priv->networkRequest));
    624     url.setQuery(String());
    625     url.removeFragmentIdentifier();
    626     priv->suggestedFilename = g_strdup(decodeURLEscapeSequences(url.lastPathComponent()).utf8().data());
    627     return priv->suggestedFilename;
    628 }
    629 
    630 // for internal use only
    631 void webkit_download_set_suggested_filename(WebKitDownload* download, const gchar* suggestedFilename)
    632 {
    633     WebKitDownloadPrivate* priv = download->priv;
    634     g_free(priv->suggestedFilename);
    635     priv->suggestedFilename = g_strdup(suggestedFilename);
    636 
    637     g_object_notify(G_OBJECT(download), "suggested-filename");
    638 }
    639 
    640 
    641 /**
    642  * webkit_download_get_destination_uri:
    643  * @download: the #WebKitDownload
    644  *
    645  * Obtains the URI to which the downloaded file will be written. This
    646  * must have been set by the application before calling
    647  * webkit_download_start(), and may be %NULL.
    648  *
    649  * Returns: the destination URI or %NULL
    650  *
    651  * Since: 1.1.2
    652  */
    653 const gchar* webkit_download_get_destination_uri(WebKitDownload* download)
    654 {
    655     g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), NULL);
    656 
    657     WebKitDownloadPrivate* priv = download->priv;
    658     return priv->destinationURI;
    659 }
    660 
    661 /**
    662  * webkit_download_set_destination_uri:
    663  * @download: the #WebKitDownload
    664  * @destination_uri: the destination URI
    665  *
    666  * Defines the URI that should be used to save the downloaded file to.
    667  *
    668  * Since: 1.1.2
    669  */
    670 void webkit_download_set_destination_uri(WebKitDownload* download, const gchar* destination_uri)
    671 {
    672     g_return_if_fail(WEBKIT_IS_DOWNLOAD(download));
    673     g_return_if_fail(destination_uri);
    674 
    675     WebKitDownloadPrivate* priv = download->priv;
    676     if (priv->destinationURI && !strcmp(priv->destinationURI, destination_uri))
    677         return;
    678 
    679     if (priv->status != WEBKIT_DOWNLOAD_STATUS_CREATED && priv->status != WEBKIT_DOWNLOAD_STATUS_CANCELLED) {
    680         ASSERT(priv->destinationURI);
    681 
    682         gboolean downloading = priv->outputStream != NULL;
    683         if (downloading)
    684             webkit_download_close_stream(download);
    685 
    686         GFile* src = g_file_new_for_uri(priv->destinationURI);
    687         GFile* dest = g_file_new_for_uri(destination_uri);
    688         GError* error = NULL;
    689 
    690         g_file_move(src, dest, G_FILE_COPY_BACKUP, NULL, NULL, NULL, &error);
    691 
    692         g_object_unref(src);
    693         g_object_unref(dest);
    694 
    695         g_free(priv->destinationURI);
    696         priv->destinationURI = g_strdup(destination_uri);
    697 
    698         if (error) {
    699             gboolean handled;
    700             g_signal_emit_by_name(download, "error", 0, WEBKIT_DOWNLOAD_ERROR_DESTINATION, error->message, &handled);
    701             g_error_free(error);
    702             return;
    703         }
    704 
    705         if (downloading) {
    706             if (!webkit_download_open_stream_for_uri(download, destination_uri, TRUE)) {
    707                 webkit_download_cancel(download);
    708                 return;
    709             }
    710         }
    711     } else {
    712         g_free(priv->destinationURI);
    713         priv->destinationURI = g_strdup(destination_uri);
    714     }
    715 
    716     // Only notify change if everything went fine.
    717     g_object_notify(G_OBJECT(download), "destination-uri");
    718 }
    719 
    720 /**
    721  * webkit_download_get_status:
    722  * @download: the #WebKitDownload
    723  *
    724  * Obtains the current status of the download, as a
    725  * #WebKitDownloadStatus.
    726  *
    727  * Returns: the current #WebKitDownloadStatus
    728  *
    729  * Since: 1.1.2
    730  */
    731 WebKitDownloadStatus webkit_download_get_status(WebKitDownload* download)
    732 {
    733     g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), WEBKIT_DOWNLOAD_STATUS_ERROR);
    734 
    735     WebKitDownloadPrivate* priv = download->priv;
    736     return priv->status;
    737 }
    738 
    739 static void webkit_download_set_status(WebKitDownload* download, WebKitDownloadStatus status)
    740 {
    741     g_return_if_fail(WEBKIT_IS_DOWNLOAD(download));
    742 
    743     WebKitDownloadPrivate* priv = download->priv;
    744     priv->status = status;
    745 
    746     g_object_notify(G_OBJECT(download), "status");
    747 }
    748 
    749 /**
    750  * webkit_download_get_total_size:
    751  * @download: the #WebKitDownload
    752  *
    753  * Returns the expected total size of the download. This is expected
    754  * because the server may provide incorrect or missing
    755  * Content-Length. Notice that this may grow over time, as it will be
    756  * always the same as current_size in the cases where current size
    757  * surpasses it.
    758  *
    759  * Returns: the expected total size of the downloaded file
    760  *
    761  * Since: 1.1.2
    762  */
    763 guint64 webkit_download_get_total_size(WebKitDownload* download)
    764 {
    765     g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 0);
    766 
    767     WebKitDownloadPrivate* priv = download->priv;
    768     SoupMessage* message = priv->networkResponse ? webkit_network_response_get_message(priv->networkResponse) : NULL;
    769 
    770     if (!message)
    771         return 0;
    772 
    773     return MAX(priv->currentSize, static_cast<guint64>(soup_message_headers_get_content_length(message->response_headers)));
    774 }
    775 
    776 /**
    777  * webkit_download_get_current_size:
    778  * @download: the #WebKitDownload
    779  *
    780  * Current already downloaded size.
    781  *
    782  * Returns: the already downloaded size
    783  *
    784  * Since: 1.1.2
    785  */
    786 guint64 webkit_download_get_current_size(WebKitDownload* download)
    787 {
    788     g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 0);
    789 
    790     WebKitDownloadPrivate* priv = download->priv;
    791     return priv->currentSize;
    792 }
    793 
    794 /**
    795  * webkit_download_get_progress:
    796  * @download: a #WebKitDownload
    797  *
    798  * Determines the current progress of the download.
    799  *
    800  * Returns: a #gdouble ranging from 0.0 to 1.0.
    801  *
    802  * Since: 1.1.2
    803  */
    804 gdouble webkit_download_get_progress(WebKitDownload* download)
    805 {
    806     g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 1.0);
    807 
    808     WebKitDownloadPrivate* priv = download->priv;
    809     if (!priv->networkResponse)
    810         return 0.0;
    811 
    812     gdouble total_size = static_cast<gdouble>(webkit_download_get_total_size(download));
    813 
    814     if (total_size == 0)
    815         return 1.0;
    816 
    817     return ((gdouble)priv->currentSize) / total_size;
    818 }
    819 
    820 /**
    821  * webkit_download_get_elapsed_time:
    822  * @download: a #WebKitDownload
    823  *
    824  * Elapsed time for the download in seconds, including any fractional
    825  * part. If the download is finished, had an error or was cancelled
    826  * this is the time between its start and the event.
    827  *
    828  * Returns: seconds since the download was started, as a #gdouble
    829  *
    830  * Since: 1.1.2
    831  */
    832 gdouble webkit_download_get_elapsed_time(WebKitDownload* download)
    833 {
    834     g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 0.0);
    835 
    836     WebKitDownloadPrivate* priv = download->priv;
    837     if (!priv->timer)
    838         return 0;
    839 
    840     return g_timer_elapsed(priv->timer, NULL);
    841 }
    842 
    843 static void webkit_download_received_data(WebKitDownload* download, const gchar* data, int length)
    844 {
    845     WebKitDownloadPrivate* priv = download->priv;
    846 
    847     if (priv->currentSize == 0)
    848         webkit_download_set_status(download, WEBKIT_DOWNLOAD_STATUS_STARTED);
    849 
    850     ASSERT(priv->outputStream);
    851 
    852     gsize bytes_written;
    853     GError* error = NULL;
    854 
    855     g_output_stream_write_all(G_OUTPUT_STREAM(priv->outputStream),
    856                               data, length, &bytes_written, NULL, &error);
    857 
    858     if (error) {
    859         gboolean handled;
    860         g_signal_emit_by_name(download, "error", 0, WEBKIT_DOWNLOAD_ERROR_DESTINATION, error->message, &handled);
    861         g_error_free(error);
    862         return;
    863     }
    864 
    865     priv->currentSize += length;
    866     g_object_notify(G_OBJECT(download), "current-size");
    867 
    868     ASSERT(priv->networkResponse);
    869     if (priv->currentSize > webkit_download_get_total_size(download))
    870         g_object_notify(G_OBJECT(download), "total-size");
    871 
    872     // Throttle progress notification to not consume high amounts of
    873     // CPU on fast links, except when the last notification occured
    874     // in more then 0.7 secs from now, or the last notified progress
    875     // is passed in 1% or we reached the end.
    876     static gdouble lastProgress = 0;
    877     static gdouble lastElapsed = 0;
    878     gdouble currentElapsed = g_timer_elapsed(priv->timer, NULL);
    879     gdouble currentProgress = webkit_download_get_progress(download);
    880 
    881     if (lastElapsed
    882         && lastProgress
    883         && (currentElapsed - lastElapsed) < 0.7
    884         && (currentProgress - lastProgress) < 0.01
    885         && currentProgress < 1.0) {
    886         return;
    887     }
    888     lastElapsed = currentElapsed;
    889     lastProgress = currentProgress;
    890 
    891     g_object_notify(G_OBJECT(download), "progress");
    892 }
    893 
    894 static void webkit_download_finished_loading(WebKitDownload* download)
    895 {
    896     webkit_download_close_stream(download);
    897 
    898     WebKitDownloadPrivate* priv = download->priv;
    899 
    900     g_timer_stop(priv->timer);
    901 
    902     g_object_notify(G_OBJECT(download), "progress");
    903     webkit_download_set_status(download, WEBKIT_DOWNLOAD_STATUS_FINISHED);
    904 }
    905 
    906 static void webkit_download_error(WebKitDownload* download, const ResourceError& error)
    907 {
    908     webkit_download_close_stream(download);
    909 
    910     WebKitDownloadPrivate* priv = download->priv;
    911     GRefPtr<WebKitDownload> protect(download);
    912 
    913     g_timer_stop(priv->timer);
    914     webkit_download_set_status(download, WEBKIT_DOWNLOAD_STATUS_ERROR);
    915 
    916     gboolean handled;
    917     g_signal_emit_by_name(download, "error", 0, WEBKIT_DOWNLOAD_ERROR_NETWORK, error.localizedDescription().utf8().data(), &handled);
    918 }
    919 
    920 DownloadClient::DownloadClient(WebKitDownload* download)
    921     : m_download(download)
    922 {
    923 }
    924 
    925 void DownloadClient::didReceiveResponse(ResourceHandle*, const ResourceResponse& response)
    926 {
    927     webkit_download_set_response(m_download, response);
    928 }
    929 
    930 void DownloadClient::didReceiveData(ResourceHandle*, const char* data, int length, int encodedDataLength)
    931 {
    932     webkit_download_received_data(m_download, data, length);
    933 }
    934 
    935 void DownloadClient::didFinishLoading(ResourceHandle*, double)
    936 {
    937     webkit_download_finished_loading(m_download);
    938 }
    939 
    940 void DownloadClient::didFail(ResourceHandle*, const ResourceError& error)
    941 {
    942     webkit_download_error(m_download, error);
    943 }
    944 
    945 void DownloadClient::wasBlocked(ResourceHandle*)
    946 {
    947     // FIXME: Implement this when we have the new frame loader signals
    948     // and error handling.
    949     notImplemented();
    950 }
    951 
    952 void DownloadClient::cannotShowURL(ResourceHandle*)
    953 {
    954     // FIXME: Implement this when we have the new frame loader signals
    955     // and error handling.
    956     notImplemented();
    957 }
    958