2020-05-05 23:58:22 +02:00
/*
2021-08-26 00:18:42 +02:00
* Copyright ( c ) 2020 - 2021 , Andreas Kling < kling @ serenityos . org >
2022-02-10 12:28:48 -07:00
* Copyright ( c ) 2022 , the SerenityOS developers .
2020-05-05 23:58:22 +02:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-05-05 23:58:22 +02:00
*/
# include "DownloadWidget.h"
# include <AK/NumberFormat.h>
# include <AK/StringBuilder.h>
2022-04-07 21:10:33 +04:30
# include <LibCore/Proxy.h>
2020-05-05 23:58:22 +02:00
# include <LibCore/StandardPaths.h>
2022-01-20 11:46:20 +00:00
# include <LibCore/Stream.h>
2020-05-26 12:54:58 -04:00
# include <LibDesktop/Launcher.h>
2020-05-05 23:58:22 +02:00
# include <LibGUI/BoxLayout.h>
# include <LibGUI/Button.h>
2021-05-31 18:19:13 +02:00
# include <LibGUI/CheckBox.h>
2020-07-22 15:29:51 +02:00
# include <LibGUI/ImageWidget.h>
2020-05-05 23:58:22 +02:00
# include <LibGUI/Label.h>
2020-05-12 20:30:33 +02:00
# include <LibGUI/MessageBox.h>
2021-04-13 16:18:20 +02:00
# include <LibGUI/Progressbar.h>
2020-05-05 23:58:22 +02:00
# include <LibGUI/Window.h>
2020-06-01 20:42:50 +02:00
# include <LibWeb/Loader/ResourceLoader.h>
2020-05-05 23:58:22 +02:00
2022-04-07 21:10:33 +04:30
# include <LibConfig/Client.h>
2020-05-05 23:58:22 +02:00
namespace Browser {
DownloadWidget : : DownloadWidget ( const URL & url )
: m_url ( url )
{
{
StringBuilder builder ;
builder . append ( Core : : StandardPaths : : downloads_directory ( ) ) ;
builder . append ( ' / ' ) ;
builder . append ( m_url . basename ( ) ) ;
m_destination_path = builder . to_string ( ) ;
}
2022-07-11 17:32:29 +00:00
auto close_on_finish = Config : : read_bool ( " Browser " sv , " Preferences " sv , " CloseDownloadWidgetOnFinish " sv , false ) ;
2021-05-31 18:19:13 +02:00
2020-05-05 23:58:22 +02:00
m_elapsed_timer . start ( ) ;
2022-04-30 12:06:30 +02:00
m_download = Web : : ResourceLoader : : the ( ) . connector ( ) . start_request ( " GET " , url ) ;
2021-02-23 20:42:32 +01:00
VERIFY ( m_download ) ;
2020-05-05 23:58:22 +02:00
m_download - > on_progress = [ this ] ( Optional < u32 > total_size , u32 downloaded_size ) {
did_progress ( total_size . value ( ) , downloaded_size ) ;
} ;
ProtocolServer: Stream the downloaded data if possible
This patchset makes ProtocolServer stream the downloads to its client
(LibProtocol), and as such changes the download API; a possible
download lifecycle could be as such:
notation = client->server:'>', server->client:'<', pipe activity:'*'
```
> StartDownload(GET, url, headers, {})
< Response(0, fd 8)
* {data, 1024b}
< HeadersBecameAvailable(0, response_headers, 200)
< DownloadProgress(0, 4K, 1024)
* {data, 1024b}
* {data, 1024b}
< DownloadProgress(0, 4K, 2048)
* {data, 1024b}
< DownloadProgress(0, 4K, 1024)
< DownloadFinished(0, true, 4K)
```
Since managing the received file descriptor is a pain, LibProtocol
implements `Download::stream_into(OutputStream)`, which can be used to
stream the download into any given output stream (be it a file, or
memory, or writing stuff with a delay, etc.).
Also, as some of the users of this API require all the downloaded data
upfront, LibProtocol also implements `set_should_buffer_all_input()`,
which causes the download instance to buffer all the data until the
download is complete, and to call the `on_buffered_download_finish`
hook.
2020-12-26 17:14:12 +03:30
{
2022-01-20 11:46:20 +00:00
auto file_or_error = Core : : Stream : : File : : open ( m_destination_path , Core : : Stream : : OpenMode : : Write ) ;
ProtocolServer: Stream the downloaded data if possible
This patchset makes ProtocolServer stream the downloads to its client
(LibProtocol), and as such changes the download API; a possible
download lifecycle could be as such:
notation = client->server:'>', server->client:'<', pipe activity:'*'
```
> StartDownload(GET, url, headers, {})
< Response(0, fd 8)
* {data, 1024b}
< HeadersBecameAvailable(0, response_headers, 200)
< DownloadProgress(0, 4K, 1024)
* {data, 1024b}
* {data, 1024b}
< DownloadProgress(0, 4K, 2048)
* {data, 1024b}
< DownloadProgress(0, 4K, 1024)
< DownloadFinished(0, true, 4K)
```
Since managing the received file descriptor is a pain, LibProtocol
implements `Download::stream_into(OutputStream)`, which can be used to
stream the download into any given output stream (be it a file, or
memory, or writing stuff with a delay, etc.).
Also, as some of the users of this API require all the downloaded data
upfront, LibProtocol also implements `set_should_buffer_all_input()`,
which causes the download instance to buffer all the data until the
download is complete, and to call the `on_buffered_download_finish`
hook.
2020-12-26 17:14:12 +03:30
if ( file_or_error . is_error ( ) ) {
2022-12-04 18:02:33 +00:00
GUI : : MessageBox : : show ( window ( ) , DeprecatedString : : formatted ( " Cannot open {} for writing " , m_destination_path ) , " Download failed " sv , GUI : : MessageBox : : Type : : Error ) ;
ProtocolServer: Stream the downloaded data if possible
This patchset makes ProtocolServer stream the downloads to its client
(LibProtocol), and as such changes the download API; a possible
download lifecycle could be as such:
notation = client->server:'>', server->client:'<', pipe activity:'*'
```
> StartDownload(GET, url, headers, {})
< Response(0, fd 8)
* {data, 1024b}
< HeadersBecameAvailable(0, response_headers, 200)
< DownloadProgress(0, 4K, 1024)
* {data, 1024b}
* {data, 1024b}
< DownloadProgress(0, 4K, 2048)
* {data, 1024b}
< DownloadProgress(0, 4K, 1024)
< DownloadFinished(0, true, 4K)
```
Since managing the received file descriptor is a pain, LibProtocol
implements `Download::stream_into(OutputStream)`, which can be used to
stream the download into any given output stream (be it a file, or
memory, or writing stuff with a delay, etc.).
Also, as some of the users of this API require all the downloaded data
upfront, LibProtocol also implements `set_should_buffer_all_input()`,
which causes the download instance to buffer all the data until the
download is complete, and to call the `on_buffered_download_finish`
hook.
2020-12-26 17:14:12 +03:30
window ( ) - > close ( ) ;
return ;
}
2022-01-20 11:46:20 +00:00
m_output_file_stream = file_or_error . release_value ( ) ;
ProtocolServer: Stream the downloaded data if possible
This patchset makes ProtocolServer stream the downloads to its client
(LibProtocol), and as such changes the download API; a possible
download lifecycle could be as such:
notation = client->server:'>', server->client:'<', pipe activity:'*'
```
> StartDownload(GET, url, headers, {})
< Response(0, fd 8)
* {data, 1024b}
< HeadersBecameAvailable(0, response_headers, 200)
< DownloadProgress(0, 4K, 1024)
* {data, 1024b}
* {data, 1024b}
< DownloadProgress(0, 4K, 2048)
* {data, 1024b}
< DownloadProgress(0, 4K, 1024)
< DownloadFinished(0, true, 4K)
```
Since managing the received file descriptor is a pain, LibProtocol
implements `Download::stream_into(OutputStream)`, which can be used to
stream the download into any given output stream (be it a file, or
memory, or writing stuff with a delay, etc.).
Also, as some of the users of this API require all the downloaded data
upfront, LibProtocol also implements `set_should_buffer_all_input()`,
which causes the download instance to buffer all the data until the
download is complete, and to call the `on_buffered_download_finish`
hook.
2020-12-26 17:14:12 +03:30
}
m_download - > on_finish = [ this ] ( bool success , auto ) { did_finish ( success ) ; } ;
m_download - > stream_into ( * m_output_file_stream ) ;
2020-05-05 23:58:22 +02:00
set_fill_with_background_color ( true ) ;
auto & layout = set_layout < GUI : : VerticalBoxLayout > ( ) ;
Userland+LibGUI: Add shorthand versions of the Margins constructor
This allows for typing [8] instead of [8, 8, 8, 8] to specify the same
margin on all edges, for example. The constructors follow CSS' style of
specifying margins. The added constructors are:
- Margins(int all): Sets the same margin on all edges.
- Margins(int vertical, int horizontal): Sets the first argument to top
and bottom margins, and the second argument to left and right margins.
- Margins(int top, int vertical, int bottom): Sets the first argument to
the top margin, the second argument to the left and right margins,
and the third argument to the bottom margin.
2021-08-17 00:11:38 +00:00
layout . set_margins ( 4 ) ;
2020-05-05 23:58:22 +02:00
auto & animation_container = add < GUI : : Widget > ( ) ;
2020-12-30 01:23:32 +01:00
animation_container . set_fixed_height ( 32 ) ;
2020-05-05 23:58:22 +02:00
auto & animation_layout = animation_container . set_layout < GUI : : HorizontalBoxLayout > ( ) ;
2020-06-17 21:50:39 +03:00
2021-05-31 18:20:50 +02:00
m_browser_image = animation_container . add < GUI : : ImageWidget > ( ) ;
2022-07-11 17:32:29 +00:00
m_browser_image - > load_from_file ( " /res/graphics/download-animation.gif " sv ) ;
2020-05-05 23:58:22 +02:00
animation_layout . add_spacer ( ) ;
2020-06-17 21:50:39 +03:00
2022-12-04 18:02:33 +00:00
auto & source_label = add < GUI : : Label > ( DeprecatedString : : formatted ( " From: {} " , url ) ) ;
2020-05-05 23:58:22 +02:00
source_label . set_text_alignment ( Gfx : : TextAlignment : : CenterLeft ) ;
2020-12-30 01:23:32 +01:00
source_label . set_fixed_height ( 16 ) ;
2020-05-05 23:58:22 +02:00
2021-04-13 16:18:20 +02:00
m_progressbar = add < GUI : : Progressbar > ( ) ;
m_progressbar - > set_fixed_height ( 20 ) ;
2020-05-05 23:58:22 +02:00
m_progress_label = add < GUI : : Label > ( ) ;
m_progress_label - > set_text_alignment ( Gfx : : TextAlignment : : CenterLeft ) ;
2020-12-30 01:23:32 +01:00
m_progress_label - > set_fixed_height ( 16 ) ;
2020-05-05 23:58:22 +02:00
2022-12-04 18:02:33 +00:00
auto & destination_label = add < GUI : : Label > ( DeprecatedString : : formatted ( " To: {} " , m_destination_path ) ) ;
2020-05-05 23:58:22 +02:00
destination_label . set_text_alignment ( Gfx : : TextAlignment : : CenterLeft ) ;
2020-12-30 01:23:32 +01:00
destination_label . set_fixed_height ( 16 ) ;
2020-05-05 23:58:22 +02:00
2021-05-31 18:19:13 +02:00
m_close_on_finish_checkbox = add < GUI : : CheckBox > ( " Close when finished " ) ;
m_close_on_finish_checkbox - > set_checked ( close_on_finish ) ;
2021-06-13 21:22:11 +02:00
m_close_on_finish_checkbox - > on_checked = [ & ] ( bool checked ) {
2022-07-11 17:32:29 +00:00
Config : : write_bool ( " Browser " sv , " Preferences " sv , " CloseDownloadWidgetOnFinish " sv , checked ) ;
2021-05-31 18:19:13 +02:00
} ;
2020-05-05 23:58:22 +02:00
auto & button_container = add < GUI : : Widget > ( ) ;
auto & button_container_layout = button_container . set_layout < GUI : : HorizontalBoxLayout > ( ) ;
button_container_layout . add_spacer ( ) ;
m_cancel_button = button_container . add < GUI : : Button > ( " Cancel " ) ;
2020-12-30 01:23:32 +01:00
m_cancel_button - > set_fixed_size ( 100 , 22 ) ;
2020-05-12 20:30:33 +02:00
m_cancel_button - > on_click = [ this ] ( auto ) {
2020-05-05 23:58:22 +02:00
bool success = m_download - > stop ( ) ;
2021-02-23 20:42:32 +01:00
VERIFY ( success ) ;
2020-05-05 23:58:22 +02:00
window ( ) - > close ( ) ;
} ;
m_close_button = button_container . add < GUI : : Button > ( " OK " ) ;
m_close_button - > set_enabled ( false ) ;
2020-12-30 01:23:32 +01:00
m_close_button - > set_fixed_size ( 100 , 22 ) ;
2020-05-12 20:30:33 +02:00
m_close_button - > on_click = [ this ] ( auto ) {
2020-05-05 23:58:22 +02:00
window ( ) - > close ( ) ;
} ;
}
void DownloadWidget : : did_progress ( Optional < u32 > total_size , u32 downloaded_size )
{
2021-04-13 16:18:20 +02:00
m_progressbar - > set_min ( 0 ) ;
2020-05-30 22:21:50 +02:00
if ( total_size . has_value ( ) ) {
int percent = roundf ( ( ( float ) downloaded_size / ( float ) total_size . value ( ) ) * 100.0f ) ;
window ( ) - > set_progress ( percent ) ;
2021-04-13 16:18:20 +02:00
m_progressbar - > set_max ( total_size . value ( ) ) ;
2020-05-30 22:21:50 +02:00
} else {
2021-04-13 16:18:20 +02:00
m_progressbar - > set_max ( 0 ) ;
2020-05-30 22:21:50 +02:00
}
2021-04-13 16:18:20 +02:00
m_progressbar - > set_value ( downloaded_size ) ;
2020-05-05 23:58:22 +02:00
{
StringBuilder builder ;
2022-07-11 17:32:29 +00:00
builder . append ( " Downloaded " sv ) ;
2020-05-05 23:58:22 +02:00
builder . append ( human_readable_size ( downloaded_size ) ) ;
2020-10-04 12:39:02 +02:00
builder . appendff ( " in {} sec " , m_elapsed_timer . elapsed ( ) / 1000 ) ;
2020-05-05 23:58:22 +02:00
m_progress_label - > set_text ( builder . to_string ( ) ) ;
}
{
StringBuilder builder ;
if ( total_size . has_value ( ) ) {
int percent = roundf ( ( ( float ) downloaded_size / ( float ) total_size . value ( ) ) * 100 ) ;
2020-10-04 12:39:02 +02:00
builder . appendff ( " {}% " , percent ) ;
2020-05-05 23:58:22 +02:00
} else {
builder . append ( human_readable_size ( downloaded_size ) ) ;
}
2022-07-11 17:32:29 +00:00
builder . append ( " of " sv ) ;
2020-05-05 23:58:22 +02:00
builder . append ( m_url . basename ( ) ) ;
window ( ) - > set_title ( builder . to_string ( ) ) ;
}
}
ProtocolServer: Stream the downloaded data if possible
This patchset makes ProtocolServer stream the downloads to its client
(LibProtocol), and as such changes the download API; a possible
download lifecycle could be as such:
notation = client->server:'>', server->client:'<', pipe activity:'*'
```
> StartDownload(GET, url, headers, {})
< Response(0, fd 8)
* {data, 1024b}
< HeadersBecameAvailable(0, response_headers, 200)
< DownloadProgress(0, 4K, 1024)
* {data, 1024b}
* {data, 1024b}
< DownloadProgress(0, 4K, 2048)
* {data, 1024b}
< DownloadProgress(0, 4K, 1024)
< DownloadFinished(0, true, 4K)
```
Since managing the received file descriptor is a pain, LibProtocol
implements `Download::stream_into(OutputStream)`, which can be used to
stream the download into any given output stream (be it a file, or
memory, or writing stuff with a delay, etc.).
Also, as some of the users of this API require all the downloaded data
upfront, LibProtocol also implements `set_should_buffer_all_input()`,
which causes the download instance to buffer all the data until the
download is complete, and to call the `on_buffered_download_finish`
hook.
2020-12-26 17:14:12 +03:30
void DownloadWidget : : did_finish ( bool success )
2020-05-05 23:58:22 +02:00
{
2021-01-09 00:11:15 +01:00
dbgln ( " did_finish, success={} " , success ) ;
2020-05-26 12:54:58 -04:00
2022-07-11 17:32:29 +00:00
m_browser_image - > load_from_file ( " /res/graphics/download-finished.gif " sv ) ;
2021-05-31 18:20:50 +02:00
window ( ) - > set_title ( " Download finished! " ) ;
2020-05-05 23:58:22 +02:00
m_close_button - > set_enabled ( true ) ;
2020-05-26 12:54:58 -04:00
m_cancel_button - > set_text ( " Open in Folder " ) ;
m_cancel_button - > on_click = [ this ] ( auto ) {
2022-09-29 01:30:58 +02:00
Desktop : : Launcher : : open ( URL : : create_with_file_scheme ( Core : : StandardPaths : : downloads_directory ( ) , m_url . basename ( ) ) ) ;
2020-05-26 12:54:58 -04:00
window ( ) - > close ( ) ;
} ;
m_cancel_button - > update ( ) ;
2020-05-05 23:58:22 +02:00
if ( ! success ) {
2022-12-04 18:02:33 +00:00
GUI : : MessageBox : : show ( window ( ) , DeprecatedString : : formatted ( " Download failed for some reason " ) , " Download failed " sv , GUI : : MessageBox : : Type : : Error ) ;
2020-05-05 23:58:22 +02:00
window ( ) - > close ( ) ;
return ;
}
2021-05-31 18:19:13 +02:00
if ( m_close_on_finish_checkbox - > is_checked ( ) )
window ( ) - > close ( ) ;
2020-05-05 23:58:22 +02:00
}
}