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