2020-05-06 00:58:22 +03:00
/*
2021-08-26 01:18:42 +03:00
* Copyright ( c ) 2020 - 2021 , Andreas Kling < kling @ serenityos . org >
2022-02-10 22:28:48 +03:00
* Copyright ( c ) 2022 , the SerenityOS developers .
2020-05-06 00:58:22 +03:00
*
2021-04-22 11:24:48 +03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-05-06 00:58:22 +03:00
*/
# include "DownloadWidget.h"
2023-05-25 00:12:40 +03:00
# include <AK/LexicalPath.h>
2020-05-06 00:58:22 +03:00
# include <AK/NumberFormat.h>
# include <AK/StringBuilder.h>
2023-06-01 15:21:06 +03:00
# include <Applications/BrowserSettings/Defaults.h>
2022-04-07 19:40:33 +03:00
# include <LibCore/Proxy.h>
2020-05-06 00:58:22 +03:00
# include <LibCore/StandardPaths.h>
2020-05-26 19:54:58 +03:00
# include <LibDesktop/Launcher.h>
2020-05-06 00:58:22 +03:00
# include <LibGUI/BoxLayout.h>
# include <LibGUI/Button.h>
2021-05-31 19:19:13 +03:00
# include <LibGUI/CheckBox.h>
2020-07-22 16:29:51 +03:00
# include <LibGUI/ImageWidget.h>
2020-05-06 00:58:22 +03:00
# include <LibGUI/Label.h>
2020-05-12 21:30:33 +03:00
# include <LibGUI/MessageBox.h>
2021-04-13 17:18:20 +03:00
# include <LibGUI/Progressbar.h>
2020-05-06 00:58:22 +03:00
# include <LibGUI/Window.h>
2020-06-01 21:42:50 +03:00
# include <LibWeb/Loader/ResourceLoader.h>
2020-05-06 00:58:22 +03:00
2022-04-07 19:40:33 +03:00
# include <LibConfig/Client.h>
2020-05-06 00:58:22 +03: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 ( ) ) ;
2022-12-06 04:12:49 +03:00
m_destination_path = builder . to_deprecated_string ( ) ;
2020-05-06 00:58:22 +03:00
}
2023-06-01 15:21:06 +03:00
auto close_on_finish = Config : : read_bool ( " Browser " sv , " Preferences " sv , " CloseDownloadWidgetOnFinish " sv , Browser : : default_close_download_widget_on_finish ) ;
2021-05-31 19:19:13 +03:00
2020-05-06 00:58:22 +03:00
m_elapsed_timer . start ( ) ;
2022-04-30 13:06:30 +03:00
m_download = Web : : ResourceLoader : : the ( ) . connector ( ) . start_request ( " GET " , url ) ;
2021-02-23 22:42:32 +03:00
VERIFY ( m_download ) ;
2023-06-11 02:56:35 +03:00
m_download - > on_progress = [ this ] ( Optional < u64 > total_size , u64 downloaded_size ) {
2020-05-06 00:58:22 +03:00
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 16:44:12 +03:00
{
2023-02-09 05:02:46 +03:00
auto file_or_error = Core : : File : : open ( m_destination_path , Core : : File : : 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 16:44:12 +03:00
if ( file_or_error . is_error ( ) ) {
2022-12-04 21:02:33 +03: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 16:44:12 +03:00
window ( ) - > close ( ) ;
return ;
}
2022-01-20 14:46:20 +03: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 16:44:12 +03:00
}
m_download - > on_finish = [ this ] ( bool success , auto ) { did_finish ( success ) ; } ;
m_download - > stream_into ( * m_output_file_stream ) ;
2020-05-06 00:58:22 +03:00
set_fill_with_background_color ( true ) ;
2023-02-17 00:07:06 +03:00
set_layout < GUI : : VerticalBoxLayout > ( 4 ) ;
2020-05-06 00:58:22 +03:00
auto & animation_container = add < GUI : : Widget > ( ) ;
2020-12-30 03:23:32 +03:00
animation_container . set_fixed_height ( 32 ) ;
2023-02-17 13:47:04 +03:00
animation_container . set_layout < GUI : : HorizontalBoxLayout > ( ) ;
2020-06-17 21:50:39 +03:00
2021-05-31 19:20:50 +03:00
m_browser_image = animation_container . add < GUI : : ImageWidget > ( ) ;
2022-07-11 20:32:29 +03:00
m_browser_image - > load_from_file ( " /res/graphics/download-animation.gif " sv ) ;
2023-08-13 19:29:05 +03:00
animation_container . add_spacer ( ) ;
2020-06-17 21:50:39 +03:00
2023-05-25 00:12:40 +03:00
auto & source_label = add < GUI : : Label > ( String : : formatted ( " File: {} " , m_url . basename ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2020-05-06 00:58:22 +03:00
source_label . set_text_alignment ( Gfx : : TextAlignment : : CenterLeft ) ;
2020-12-30 03:23:32 +03:00
source_label . set_fixed_height ( 16 ) ;
2023-05-25 00:12:40 +03:00
source_label . set_text_wrapping ( Gfx : : TextWrapping : : DontWrap ) ;
2020-05-06 00:58:22 +03:00
2021-04-13 17:18:20 +03:00
m_progressbar = add < GUI : : Progressbar > ( ) ;
m_progressbar - > set_fixed_height ( 20 ) ;
2023-06-19 19:46:18 +03:00
m_progressbar - > set_min ( 0 ) ;
2023-06-11 02:56:35 +03:00
m_progressbar - > set_max ( 100 ) ;
2020-05-06 00:58:22 +03:00
m_progress_label = add < GUI : : Label > ( ) ;
m_progress_label - > set_text_alignment ( Gfx : : TextAlignment : : CenterLeft ) ;
2020-12-30 03:23:32 +03:00
m_progress_label - > set_fixed_height ( 16 ) ;
2023-05-25 00:12:40 +03:00
m_progress_label - > set_text_wrapping ( Gfx : : TextWrapping : : DontWrap ) ;
2020-05-06 00:58:22 +03:00
2023-05-25 00:12:40 +03:00
auto destination_label_path = LexicalPath ( m_destination_path ) . dirname ( ) . to_deprecated_string ( ) ;
auto & destination_label = add < GUI : : Label > ( String : : formatted ( " To: {} " , destination_label_path ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2020-05-06 00:58:22 +03:00
destination_label . set_text_alignment ( Gfx : : TextAlignment : : CenterLeft ) ;
2020-12-30 03:23:32 +03:00
destination_label . set_fixed_height ( 16 ) ;
2023-05-25 00:12:40 +03:00
destination_label . set_text_wrapping ( Gfx : : TextWrapping : : DontWrap ) ;
2020-05-06 00:58:22 +03:00
2023-08-07 12:12:38 +03:00
m_close_on_finish_checkbox = add < GUI : : CheckBox > ( " Close when finished " _string ) ;
2021-05-31 19:19:13 +03:00
m_close_on_finish_checkbox - > set_checked ( close_on_finish ) ;
2021-06-13 22:22:11 +03:00
m_close_on_finish_checkbox - > on_checked = [ & ] ( bool checked ) {
2022-07-11 20:32:29 +03:00
Config : : write_bool ( " Browser " sv , " Preferences " sv , " CloseDownloadWidgetOnFinish " sv , checked ) ;
2021-05-31 19:19:13 +03:00
} ;
2020-05-06 00:58:22 +03:00
auto & button_container = add < GUI : : Widget > ( ) ;
2023-02-17 13:47:04 +03:00
button_container . set_layout < GUI : : HorizontalBoxLayout > ( ) ;
2023-08-13 19:29:05 +03:00
button_container . add_spacer ( ) ;
2023-08-08 05:26:17 +03:00
m_cancel_button = button_container . add < GUI : : Button > ( " Cancel " _string ) ;
2020-12-30 03:23:32 +03:00
m_cancel_button - > set_fixed_size ( 100 , 22 ) ;
2020-05-12 21:30:33 +03:00
m_cancel_button - > on_click = [ this ] ( auto ) {
2020-05-06 00:58:22 +03:00
bool success = m_download - > stop ( ) ;
2021-02-23 22:42:32 +03:00
VERIFY ( success ) ;
2020-05-06 00:58:22 +03:00
window ( ) - > close ( ) ;
} ;
2023-08-08 05:26:17 +03:00
m_close_button = button_container . add < GUI : : Button > ( " OK " _string ) ;
2020-05-06 00:58:22 +03:00
m_close_button - > set_enabled ( false ) ;
2020-12-30 03:23:32 +03:00
m_close_button - > set_fixed_size ( 100 , 22 ) ;
2020-05-12 21:30:33 +03:00
m_close_button - > on_click = [ this ] ( auto ) {
2020-05-06 00:58:22 +03:00
window ( ) - > close ( ) ;
} ;
}
2023-06-11 02:56:35 +03:00
void DownloadWidget : : did_progress ( Optional < u64 > total_size , u64 downloaded_size )
2020-05-06 00:58:22 +03:00
{
2023-06-11 02:56:35 +03:00
int percent = 0 ;
2020-05-30 23:21:50 +03:00
if ( total_size . has_value ( ) ) {
2023-06-11 02:56:35 +03:00
percent = downloaded_size * 100 / total_size . value ( ) ;
2020-05-30 23:21:50 +03:00
window ( ) - > set_progress ( percent ) ;
2023-06-11 02:56:35 +03:00
m_progressbar - > set_value ( percent ) ;
2020-05-30 23:21:50 +03:00
}
2020-05-06 00:58:22 +03:00
{
StringBuilder builder ;
2022-07-11 20:32:29 +03:00
builder . append ( " Downloaded " sv ) ;
2020-05-06 00:58:22 +03:00
builder . append ( human_readable_size ( downloaded_size ) ) ;
2023-01-02 08:38:53 +03:00
builder . appendff ( " in {} sec " , m_elapsed_timer . elapsed_time ( ) . to_seconds ( ) ) ;
2023-04-29 17:41:48 +03:00
m_progress_label - > set_text ( builder . to_string ( ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2020-05-06 00:58:22 +03:00
}
{
StringBuilder builder ;
if ( total_size . has_value ( ) ) {
2020-10-04 13:39:02 +03:00
builder . appendff ( " {}% " , percent ) ;
2020-05-06 00:58:22 +03:00
} else {
builder . append ( human_readable_size ( downloaded_size ) ) ;
}
2022-07-11 20:32:29 +03:00
builder . append ( " of " sv ) ;
2020-05-06 00:58:22 +03:00
builder . append ( m_url . basename ( ) ) ;
2022-12-06 04:12:49 +03:00
window ( ) - > set_title ( builder . to_deprecated_string ( ) ) ;
2020-05-06 00:58:22 +03:00
}
}
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 16:44:12 +03:00
void DownloadWidget : : did_finish ( bool success )
2020-05-06 00:58:22 +03:00
{
2021-01-09 02:11:15 +03:00
dbgln ( " did_finish, success={} " , success ) ;
2020-05-26 19:54:58 +03:00
2022-07-11 20:32:29 +03:00
m_browser_image - > load_from_file ( " /res/graphics/download-finished.gif " sv ) ;
2021-05-31 19:20:50 +03:00
window ( ) - > set_title ( " Download finished! " ) ;
2020-05-06 00:58:22 +03:00
m_close_button - > set_enabled ( true ) ;
2023-08-07 12:12:38 +03:00
m_cancel_button - > set_text ( " Open in Folder " _string ) ;
2020-05-26 19:54:58 +03:00
m_cancel_button - > on_click = [ this ] ( auto ) {
2022-09-29 02:30:58 +03:00
Desktop : : Launcher : : open ( URL : : create_with_file_scheme ( Core : : StandardPaths : : downloads_directory ( ) , m_url . basename ( ) ) ) ;
2020-05-26 19:54:58 +03:00
window ( ) - > close ( ) ;
} ;
m_cancel_button - > update ( ) ;
2020-05-06 00:58:22 +03:00
if ( ! success ) {
2022-12-04 21:02:33 +03:00
GUI : : MessageBox : : show ( window ( ) , DeprecatedString : : formatted ( " Download failed for some reason " ) , " Download failed " sv , GUI : : MessageBox : : Type : : Error ) ;
2020-05-06 00:58:22 +03:00
window ( ) - > close ( ) ;
return ;
}
2021-05-31 19:19:13 +03:00
if ( m_close_on_finish_checkbox - > is_checked ( ) )
window ( ) - > close ( ) ;
2020-05-06 00:58:22 +03:00
}
}