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