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