[GLIB] Use GUniquePtr instead of GOwnPtr
[WebKit-https.git] / Source / WebKit2 / UIProcess / API / gtk / WebKitDownload.cpp
1 /*
2  * Copyright (C) 2012 Igalia S.L.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include "config.h"
21 #include "WebKitDownload.h"
22
23 #include "DownloadProxy.h"
24 #include "WebKitDownloadPrivate.h"
25 #include "WebKitMarshal.h"
26 #include "WebKitURIRequestPrivate.h"
27 #include "WebKitURIResponsePrivate.h"
28 #include <WebCore/ErrorsGtk.h>
29 #include <WebCore/ResourceResponse.h>
30 #include <glib/gi18n-lib.h>
31 #include <wtf/gobject/GRefPtr.h>
32 #include <wtf/gobject/GUniquePtr.h>
33
34 using namespace WebKit;
35 using namespace WebCore;
36
37 /**
38  * SECTION: WebKitDownload
39  * @Short_description: Object used to communicate with the application when downloading
40  * @Title: WebKitDownload
41  *
42  * #WebKitDownload carries information about a download request and
43  * response, including a #WebKitURIRequest and a #WebKitURIResponse
44  * objects. The application may use this object to control the
45  * download process, or to simply figure out what is to be downloaded,
46  * and handle the download process itself.
47  *
48  */
49
50 enum {
51     RECEIVED_DATA,
52     FINISHED,
53     FAILED,
54     DECIDE_DESTINATION,
55     CREATED_DESTINATION,
56
57     LAST_SIGNAL
58 };
59
60 enum {
61     PROP_0,
62
63     PROP_DESTINATION,
64     PROP_RESPONSE,
65     PROP_ESTIMATED_PROGRESS
66 };
67
68 struct _WebKitDownloadPrivate {
69     ~_WebKitDownloadPrivate()
70     {
71         if (webView)
72             g_object_remove_weak_pointer(G_OBJECT(webView), reinterpret_cast<void**>(&webView));
73     }
74
75     RefPtr<DownloadProxy> download;
76
77     GRefPtr<WebKitURIRequest> request;
78     GRefPtr<WebKitURIResponse> response;
79     WebKitWebView* webView;
80     CString destinationURI;
81     guint64 currentSize;
82     bool isCancelled;
83     GUniquePtr<GTimer> timer;
84     gdouble lastProgress;
85     gdouble lastElapsed;
86 };
87
88 static guint signals[LAST_SIGNAL] = { 0, };
89
90 WEBKIT_DEFINE_TYPE(WebKitDownload, webkit_download, G_TYPE_OBJECT)
91
92 static void webkitDownloadGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec)
93 {
94     WebKitDownload* download = WEBKIT_DOWNLOAD(object);
95
96     switch (propId) {
97     case PROP_DESTINATION:
98         g_value_set_string(value, webkit_download_get_destination(download));
99         break;
100     case PROP_RESPONSE:
101         g_value_set_object(value, webkit_download_get_response(download));
102         break;
103     case PROP_ESTIMATED_PROGRESS:
104         g_value_set_double(value, webkit_download_get_estimated_progress(download));
105         break;
106     default:
107         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
108     }
109 }
110
111 static gboolean webkitDownloadDecideDestination(WebKitDownload* download, const gchar* suggestedFilename)
112 {
113     if (!download->priv->destinationURI.isNull())
114         return FALSE;
115
116     GUniquePtr<char> filename(g_strdelimit(g_strdup(suggestedFilename), G_DIR_SEPARATOR_S, '_'));
117     const gchar *downloadsDir = g_get_user_special_dir(G_USER_DIRECTORY_DOWNLOAD);
118     if (!downloadsDir) {
119         // If we don't have XDG user dirs info, set just to HOME.
120         downloadsDir = g_get_home_dir();
121     }
122     GUniquePtr<char> destination(g_build_filename(downloadsDir, filename.get(), NULL));
123     GUniquePtr<char> destinationURI(g_filename_to_uri(destination.get(), 0, 0));
124     download->priv->destinationURI = destinationURI.get();
125     g_object_notify(G_OBJECT(download), "destination");
126     return TRUE;
127 }
128
129 static void webkit_download_class_init(WebKitDownloadClass* downloadClass)
130 {
131     GObjectClass* objectClass = G_OBJECT_CLASS(downloadClass);
132     objectClass->get_property = webkitDownloadGetProperty;
133
134     downloadClass->decide_destination = webkitDownloadDecideDestination;
135
136     /**
137      * WebKitDownload:destination:
138      *
139      * The local URI to where the download will be saved.
140      */
141     g_object_class_install_property(objectClass,
142                                     PROP_DESTINATION,
143                                     g_param_spec_string("destination",
144                                                         _("Destination"),
145                                                         _("The local URI to where the download will be saved"),
146                                                         0,
147                                                         WEBKIT_PARAM_READABLE));
148
149     /**
150      * WebKitDownload:response:
151      *
152      * The #WebKitURIResponse associated with this download.
153      */
154     g_object_class_install_property(objectClass,
155                                     PROP_RESPONSE,
156                                     g_param_spec_object("response",
157                                                         _("Response"),
158                                                         _("The response of the download"),
159                                                         WEBKIT_TYPE_URI_RESPONSE,
160                                                         WEBKIT_PARAM_READABLE));
161
162     /**
163      * WebKitDownload:estimated-progress:
164      *
165      * An estimate of the percent completion for the download operation.
166      * This value will range from 0.0 to 1.0. The value is an estimate
167      * based on the total number of bytes expected to be received for
168      * a download.
169      * If you need a more accurate progress information you can connect to
170      * #WebKitDownload::received-data signal to track the progress.
171      */
172     g_object_class_install_property(objectClass,
173                                     PROP_ESTIMATED_PROGRESS,
174                                     g_param_spec_double("estimated-progress",
175                                                         _("Estimated Progress"),
176                                                         _("Determines the current progress of the download"),
177                                                         0.0, 1.0, 1.0,
178                                                         WEBKIT_PARAM_READABLE));
179
180     /**
181      * WebKitDownload::received-data:
182      * @download: the #WebKitDownload
183      * @data_length: the length of data received in bytes
184      *
185      * This signal is emitted after response is received,
186      * every time new data has been written to the destination. It's
187      * useful to know the progress of the download operation.
188      */
189     signals[RECEIVED_DATA] =
190         g_signal_new("received-data",
191                      G_TYPE_FROM_CLASS(objectClass),
192                      G_SIGNAL_RUN_LAST,
193                      0, 0, 0,
194                      webkit_marshal_VOID__UINT64,
195                      G_TYPE_NONE, 1,
196                      G_TYPE_UINT64);
197
198     /**
199      * WebKitDownload::finished:
200      * @download: the #WebKitDownload
201      *
202      * This signal is emitted when download finishes successfully or due to an error.
203      * In case of errors #WebKitDownload::failed signal is emitted before this one.
204      */
205     signals[FINISHED] =
206         g_signal_new("finished",
207                      G_TYPE_FROM_CLASS(objectClass),
208                      G_SIGNAL_RUN_LAST,
209                      0, 0, 0,
210                      g_cclosure_marshal_VOID__VOID,
211                      G_TYPE_NONE, 0);
212
213     /**
214      * WebKitDownload::failed:
215      * @download: the #WebKitDownload
216      * @error: the #GError that was triggered
217      *
218      * This signal is emitted when an error occurs during the download
219      * operation. The given @error, of the domain %WEBKIT_DOWNLOAD_ERROR,
220      * contains further details of the failure. If the download is cancelled
221      * with webkit_download_cancel(), this signal is emitted with error
222      * %WEBKIT_DOWNLOAD_ERROR_CANCELLED_BY_USER. The download operation finishes
223      * after an error and #WebKitDownload::finished signal is emitted after this one.
224      */
225     signals[FAILED] =
226         g_signal_new("failed",
227                      G_TYPE_FROM_CLASS(objectClass),
228                      G_SIGNAL_RUN_LAST,
229                      0, 0, 0,
230                      g_cclosure_marshal_VOID__POINTER,
231                      G_TYPE_NONE, 1,
232                      G_TYPE_POINTER);
233
234     /**
235      * WebKitDownload::decide-destination:
236      * @download: the #WebKitDownload
237      * @suggested_filename: the filename suggested for the download
238      *
239      * This signal is emitted after response is received to
240      * decide a destination URI for the download. If this signal is not
241      * handled the file will be downloaded to %G_USER_DIRECTORY_DOWNLOAD
242      * directory using @suggested_filename.
243      *
244      * Returns: %TRUE to stop other handlers from being invoked for the event.
245      *   %FALSE to propagate the event further.
246      */
247     signals[DECIDE_DESTINATION] =
248         g_signal_new("decide-destination",
249                      G_TYPE_FROM_CLASS(objectClass),
250                      G_SIGNAL_RUN_LAST,
251                      G_STRUCT_OFFSET(WebKitDownloadClass, decide_destination),
252                      g_signal_accumulator_true_handled, NULL,
253                      webkit_marshal_BOOLEAN__STRING,
254                      G_TYPE_BOOLEAN, 1,
255                      G_TYPE_STRING);
256
257     /**
258      * WebKitDownload::created-destination:
259      * @download: the #WebKitDownload
260      * @destination: the destination URI
261      *
262      * This signal is emitted after #WebKitDownload::decide-destination and before
263      * #WebKitDownload::received-data to notify that destination file has been
264      * created successfully at @destination.
265      */
266     signals[CREATED_DESTINATION] =
267         g_signal_new(
268             "created-destination",
269             G_TYPE_FROM_CLASS(objectClass),
270             G_SIGNAL_RUN_LAST,
271             0, 0, 0,
272             g_cclosure_marshal_VOID__STRING,
273             G_TYPE_NONE, 1,
274             G_TYPE_STRING);
275 }
276
277 WebKitDownload* webkitDownloadCreate(DownloadProxy* downloadProxy)
278 {
279     ASSERT(downloadProxy);
280     WebKitDownload* download = WEBKIT_DOWNLOAD(g_object_new(WEBKIT_TYPE_DOWNLOAD, NULL));
281     download->priv->download = downloadProxy;
282     return download;
283 }
284
285 WebKitDownload* webkitDownloadCreateForRequest(DownloadProxy* downloadProxy, const ResourceRequest& request)
286 {
287     WebKitDownload* download = webkitDownloadCreate(downloadProxy);
288     download->priv->request = adoptGRef(webkitURIRequestCreateForResourceRequest(request));
289     return download;
290 }
291
292 void webkitDownloadSetResponse(WebKitDownload* download, WebKitURIResponse* response)
293 {
294     download->priv->response = response;
295     g_object_notify(G_OBJECT(download), "response");
296 }
297
298 void webkitDownloadSetWebView(WebKitDownload* download, WebKitWebView* webView)
299 {
300     download->priv->webView = webView;
301     g_object_add_weak_pointer(G_OBJECT(webView), reinterpret_cast<void**>(&download->priv->webView));
302 }
303
304 bool webkitDownloadIsCancelled(WebKitDownload* download)
305 {
306     return download->priv->isCancelled;
307 }
308
309 void webkitDownloadNotifyProgress(WebKitDownload* download, guint64 bytesReceived)
310 {
311     WebKitDownloadPrivate* priv = download->priv;
312     if (priv->isCancelled)
313         return;
314
315     if (!download->priv->timer)
316         download->priv->timer.reset(g_timer_new());
317
318     priv->currentSize += bytesReceived;
319     g_signal_emit(download, signals[RECEIVED_DATA], 0, bytesReceived);
320
321     // Throttle progress notification to not consume high amounts of
322     // CPU on fast links, except when the last notification occured
323     // more than 0.016 secs ago (60 FPS), or the last notified progress
324     // is passed in 1% or we reached the end.
325     gdouble currentElapsed = g_timer_elapsed(priv->timer.get(), 0);
326     gdouble currentProgress = webkit_download_get_estimated_progress(download);
327
328     if (priv->lastElapsed
329         && priv->lastProgress
330         && (currentElapsed - priv->lastElapsed) < 0.016
331         && (currentProgress - priv->lastProgress) < 0.01
332         && currentProgress < 1.0) {
333         return;
334     }
335     priv->lastElapsed = currentElapsed;
336     priv->lastProgress = currentProgress;
337     g_object_notify(G_OBJECT(download), "estimated-progress");
338 }
339
340 void webkitDownloadFailed(WebKitDownload* download, const ResourceError& resourceError)
341 {
342     GUniquePtr<GError> webError(g_error_new_literal(g_quark_from_string(resourceError.domain().utf8().data()),
343         resourceError.errorCode(), resourceError.localizedDescription().utf8().data()));
344     if (download->priv->timer)
345         g_timer_stop(download->priv->timer.get());
346
347     g_signal_emit(download, signals[FAILED], 0, webError.get());
348     g_signal_emit(download, signals[FINISHED], 0, NULL);
349 }
350
351 void webkitDownloadCancelled(WebKitDownload* download)
352 {
353     WebKitDownloadPrivate* priv = download->priv;
354     webkitDownloadFailed(download, downloadCancelledByUserError(priv->response ? webkitURIResponseGetResourceResponse(priv->response.get()) : ResourceResponse()));
355 }
356
357 void webkitDownloadFinished(WebKitDownload* download)
358 {
359     if (download->priv->isCancelled) {
360         // Since cancellation is asynchronous, didFinish might be called even
361         // if the download was cancelled. User cancelled the download,
362         // so we should fail with cancelled error even if the download
363         // actually finished successfully.
364         webkitDownloadCancelled(download);
365         return;
366     }
367     if (download->priv->timer)
368         g_timer_stop(download->priv->timer.get());
369     g_signal_emit(download, signals[FINISHED], 0, NULL);
370 }
371
372 CString webkitDownloadDecideDestinationWithSuggestedFilename(WebKitDownload* download, const CString& suggestedFilename)
373 {
374     if (download->priv->isCancelled)
375         return "";
376     gboolean returnValue;
377     g_signal_emit(download, signals[DECIDE_DESTINATION], 0, suggestedFilename.data(), &returnValue);
378     return download->priv->destinationURI;
379 }
380
381 void webkitDownloadDestinationCreated(WebKitDownload* download, const CString& destinationURI)
382 {
383     if (download->priv->isCancelled)
384         return;
385     g_signal_emit(download, signals[CREATED_DESTINATION], 0, destinationURI.data(), nullptr);
386 }
387
388 /**
389  * webkit_download_get_request:
390  * @download: a #WebKitDownload
391  *
392  * Retrieves the #WebKitURIRequest object that backs the download
393  * process.
394  *
395  * Returns: (transfer none): the #WebKitURIRequest of @download
396  */
397 WebKitURIRequest* webkit_download_get_request(WebKitDownload* download)
398 {
399     g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 0);
400
401     WebKitDownloadPrivate* priv = download->priv;
402     if (!priv->request)
403         priv->request = adoptGRef(webkitURIRequestCreateForResourceRequest(priv->download->request()));
404     return priv->request.get();
405 }
406
407 /**
408  * webkit_download_get_destination:
409  * @download: a #WebKitDownload
410  *
411  * Obtains the URI to which the downloaded file will be written. You
412  * can connect to #WebKitDownload::created-destination to make
413  * sure this method returns a valid destination.
414  *
415  * Returns: the destination URI or %NULL
416  */
417 const gchar* webkit_download_get_destination(WebKitDownload* download)
418 {
419     g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 0);
420
421     return download->priv->destinationURI.data();
422 }
423
424 /**
425  * webkit_download_set_destination:
426  * @download: a #WebKitDownload
427  * @uri: the destination URI
428  *
429  * Sets the URI to which the downloaded file will be written.
430  * This method should be called before the download transfer
431  * starts or it will not have any effect on the ongoing download
432  * operation. To set the destination using the filename suggested
433  * by the server connect to #WebKitDownload::decide-destination
434  * signal and call webkit_download_set_destination(). If you want to
435  * set a fixed destination URI that doesn't depend on the suggested
436  * filename you can connect to notify::response signal and call
437  * webkit_download_set_destination().
438  * If #WebKitDownload::decide-destination signal is not handled
439  * and destination URI is not set when the download tranfer starts,
440  * the file will be saved with the filename suggested by the server in
441  * %G_USER_DIRECTORY_DOWNLOAD directory.
442  */
443 void webkit_download_set_destination(WebKitDownload* download, const gchar* uri)
444 {
445     g_return_if_fail(WEBKIT_IS_DOWNLOAD(download));
446     g_return_if_fail(uri);
447
448     WebKitDownloadPrivate* priv = download->priv;
449     if (priv->destinationURI == uri)
450         return;
451
452     priv->destinationURI = uri;
453     g_object_notify(G_OBJECT(download), "destination");
454 }
455
456 /**
457  * webkit_download_get_response:
458  * @download: a #WebKitDownload
459  *
460  * Retrieves the #WebKitURIResponse object that backs the download
461  * process. This method returns %NULL if called before the response
462  * is received from the server. You can connect to notify::response
463  * signal to be notified when the response is received.
464  *
465  * Returns: (transfer none): the #WebKitURIResponse, or %NULL if
466  *     the response hasn't been received yet.
467  */
468 WebKitURIResponse* webkit_download_get_response(WebKitDownload* download)
469 {
470     g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 0);
471
472     return download->priv->response.get();
473 }
474
475 /**
476  * webkit_download_cancel:
477  * @download: a #WebKitDownload
478  *
479  * Cancels the download. When the ongoing download
480  * operation is effectively cancelled the signal
481  * #WebKitDownload::failed is emitted with
482  * %WEBKIT_DOWNLOAD_ERROR_CANCELLED_BY_USER error.
483  */
484 void webkit_download_cancel(WebKitDownload* download)
485 {
486     g_return_if_fail(WEBKIT_IS_DOWNLOAD(download));
487
488     download->priv->isCancelled = true;
489     download->priv->download->cancel();
490 }
491
492 /**
493  * webkit_download_get_estimated_progress:
494  * @download: a #WebKitDownload
495  *
496  * Gets the value of the #WebKitDownload:estimated-progress property.
497  * You can monitor the estimated progress of the download operation by
498  * connecting to the notify::estimated-progress signal of @download.
499  *
500  * Returns: an estimate of the of the percent complete for a download
501  *     as a range from 0.0 to 1.0.
502  */
503 gdouble webkit_download_get_estimated_progress(WebKitDownload* download)
504 {
505     g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 0);
506
507     WebKitDownloadPrivate* priv = download->priv;
508     if (!priv->response)
509         return 0;
510
511     guint64 contentLength = webkit_uri_response_get_content_length(priv->response.get());
512     if (!contentLength)
513         return 0;
514
515     return static_cast<gdouble>(priv->currentSize) / static_cast<gdouble>(contentLength);
516 }
517
518 /**
519  * webkit_download_get_elapsed_time:
520  * @download: a #WebKitDownload
521  *
522  * Gets the elapsed time in seconds, including any fractional part.
523  * If the download finished, had an error or was cancelled this is
524  * the time between its start and the event.
525  *
526  * Returns: seconds since the download was started
527  */
528 gdouble webkit_download_get_elapsed_time(WebKitDownload* download)
529 {
530     g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 0);
531
532     WebKitDownloadPrivate* priv = download->priv;
533     if (!priv->timer)
534         return 0;
535
536     return g_timer_elapsed(priv->timer.get(), 0);
537 }
538
539 /**
540  * webkit_download_get_received_data_length:
541  * @download: a #WebKitDownload
542  *
543  * Gets the length of the data already downloaded for @download
544  * in bytes.
545  *
546  * Returns: the amount of bytes already downloaded.
547  */
548 guint64 webkit_download_get_received_data_length(WebKitDownload* download)
549 {
550     g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 0);
551
552     return download->priv->currentSize;
553 }
554
555 /**
556  * webkit_download_get_web_view:
557  * @download: a #WebKitDownload
558  *
559  * Get the #WebKitWebView that initiated the download.
560  *
561  * Returns: (transfer none): the #WebKitWebView that initiated @download,
562  *    or %NULL if @download was not initiated by a #WebKitWebView.
563  */
564 WebKitWebView* webkit_download_get_web_view(WebKitDownload* download)
565 {
566     g_return_val_if_fail(WEBKIT_IS_DOWNLOAD(download), 0);
567
568     return download->priv->webView;
569 }