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 "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
47using namespace WebKit;
48using 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
60class 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
76struct _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
89enum {
90    // Normal signals.
91    ERROR,
92    LAST_SIGNAL
93};
94
95static guint webkit_download_signals[LAST_SIGNAL] = { 0 };
96
97enum {
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
110G_DEFINE_TYPE(WebKitDownload, webkit_download, G_TYPE_OBJECT);
111
112
113static void webkit_download_set_response(WebKitDownload* download, const ResourceResponse& response);
114static void webkit_download_set_status(WebKitDownload* download, WebKitDownloadStatus status);
115
116static 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
139static 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
169static 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
203static 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
226static 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
390static 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 */
411WebKitDownload* 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
419WebKitDownload* 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
438static 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
463static 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 */
481void 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 */
518void 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 */
549const 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 */
568WebKitNetworkRequest* 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 */
587WebKitNetworkResponse* 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
595static 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 */
615const 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
631void 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 */
653const 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 */
670void 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 */
731WebKitDownloadStatus 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
739static 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 */
763guint64 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 */
786guint64 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 */
804gdouble 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 */
832gdouble 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
843static 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
894static 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
906static 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
920DownloadClient::DownloadClient(WebKitDownload* download)
921    : m_download(download)
922{
923}
924
925void DownloadClient::didReceiveResponse(ResourceHandle*, const ResourceResponse& response)
926{
927    webkit_download_set_response(m_download, response);
928}
929
930void DownloadClient::didReceiveData(ResourceHandle*, const char* data, int length, int encodedDataLength)
931{
932    webkit_download_received_data(m_download, data, length);
933}
934
935void DownloadClient::didFinishLoading(ResourceHandle*, double)
936{
937    webkit_download_finished_loading(m_download);
938}
939
940void DownloadClient::didFail(ResourceHandle*, const ResourceError& error)
941{
942    webkit_download_error(m_download, error);
943}
944
945void DownloadClient::wasBlocked(ResourceHandle*)
946{
947    // FIXME: Implement this when we have the new frame loader signals
948    // and error handling.
949    notImplemented();
950}
951
952void DownloadClient::cannotShowURL(ResourceHandle*)
953{
954    // FIXME: Implement this when we have the new frame loader signals
955    // and error handling.
956    notImplemented();
957}
958