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