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