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