Serenity Operating System
1/*
2 * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2022, the SerenityOS developers.
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include "DownloadWidget.h"
9#include <AK/NumberFormat.h>
10#include <AK/StringBuilder.h>
11#include <LibCore/Proxy.h>
12#include <LibCore/StandardPaths.h>
13#include <LibDesktop/Launcher.h>
14#include <LibGUI/BoxLayout.h>
15#include <LibGUI/Button.h>
16#include <LibGUI/CheckBox.h>
17#include <LibGUI/ImageWidget.h>
18#include <LibGUI/Label.h>
19#include <LibGUI/MessageBox.h>
20#include <LibGUI/Progressbar.h>
21#include <LibGUI/Window.h>
22#include <LibWeb/Loader/ResourceLoader.h>
23
24#include <LibConfig/Client.h>
25
26namespace Browser {
27
28DownloadWidget::DownloadWidget(const URL& url)
29 : m_url(url)
30{
31 {
32 StringBuilder builder;
33 builder.append(Core::StandardPaths::downloads_directory());
34 builder.append('/');
35 builder.append(m_url.basename());
36 m_destination_path = builder.to_deprecated_string();
37 }
38
39 auto close_on_finish = Config::read_bool("Browser"sv, "Preferences"sv, "CloseDownloadWidgetOnFinish"sv, false);
40
41 m_elapsed_timer.start();
42 m_download = Web::ResourceLoader::the().connector().start_request("GET", url);
43 VERIFY(m_download);
44 m_download->on_progress = [this](Optional<u32> total_size, u32 downloaded_size) {
45 did_progress(total_size.value(), downloaded_size);
46 };
47
48 {
49 auto file_or_error = Core::File::open(m_destination_path, Core::File::OpenMode::Write);
50 if (file_or_error.is_error()) {
51 GUI::MessageBox::show(window(), DeprecatedString::formatted("Cannot open {} for writing", m_destination_path), "Download failed"sv, GUI::MessageBox::Type::Error);
52 window()->close();
53 return;
54 }
55 m_output_file_stream = file_or_error.release_value();
56 }
57
58 m_download->on_finish = [this](bool success, auto) { did_finish(success); };
59 m_download->stream_into(*m_output_file_stream);
60
61 set_fill_with_background_color(true);
62 set_layout<GUI::VerticalBoxLayout>(4);
63
64 auto& animation_container = add<GUI::Widget>();
65 animation_container.set_fixed_height(32);
66 animation_container.set_layout<GUI::HorizontalBoxLayout>();
67
68 m_browser_image = animation_container.add<GUI::ImageWidget>();
69 m_browser_image->load_from_file("/res/graphics/download-animation.gif"sv);
70 animation_container.add_spacer().release_value_but_fixme_should_propagate_errors();
71
72 auto& source_label = add<GUI::Label>(DeprecatedString::formatted("From: {}", url));
73 source_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
74 source_label.set_fixed_height(16);
75
76 m_progressbar = add<GUI::Progressbar>();
77 m_progressbar->set_fixed_height(20);
78
79 m_progress_label = add<GUI::Label>();
80 m_progress_label->set_text_alignment(Gfx::TextAlignment::CenterLeft);
81 m_progress_label->set_fixed_height(16);
82
83 auto& destination_label = add<GUI::Label>(DeprecatedString::formatted("To: {}", m_destination_path));
84 destination_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
85 destination_label.set_fixed_height(16);
86
87 m_close_on_finish_checkbox = add<GUI::CheckBox>("Close when finished"_string.release_value_but_fixme_should_propagate_errors());
88 m_close_on_finish_checkbox->set_checked(close_on_finish);
89
90 m_close_on_finish_checkbox->on_checked = [&](bool checked) {
91 Config::write_bool("Browser"sv, "Preferences"sv, "CloseDownloadWidgetOnFinish"sv, checked);
92 };
93
94 auto& button_container = add<GUI::Widget>();
95 button_container.set_layout<GUI::HorizontalBoxLayout>();
96 button_container.add_spacer().release_value_but_fixme_should_propagate_errors();
97 m_cancel_button = button_container.add<GUI::Button>("Cancel"_short_string);
98 m_cancel_button->set_fixed_size(100, 22);
99 m_cancel_button->on_click = [this](auto) {
100 bool success = m_download->stop();
101 VERIFY(success);
102 window()->close();
103 };
104
105 m_close_button = button_container.add<GUI::Button>("OK"_short_string);
106 m_close_button->set_enabled(false);
107 m_close_button->set_fixed_size(100, 22);
108 m_close_button->on_click = [this](auto) {
109 window()->close();
110 };
111}
112
113void DownloadWidget::did_progress(Optional<u32> total_size, u32 downloaded_size)
114{
115 m_progressbar->set_min(0);
116 if (total_size.has_value()) {
117 int percent = roundf(((float)downloaded_size / (float)total_size.value()) * 100.0f);
118 window()->set_progress(percent);
119 m_progressbar->set_max(total_size.value());
120 } else {
121 m_progressbar->set_max(0);
122 }
123 m_progressbar->set_value(downloaded_size);
124
125 {
126 StringBuilder builder;
127 builder.append("Downloaded "sv);
128 builder.append(human_readable_size(downloaded_size));
129 builder.appendff(" in {} sec", m_elapsed_timer.elapsed_time().to_seconds());
130 m_progress_label->set_text(builder.to_deprecated_string());
131 }
132
133 {
134 StringBuilder builder;
135 if (total_size.has_value()) {
136 int percent = roundf(((float)downloaded_size / (float)total_size.value()) * 100);
137 builder.appendff("{}%", percent);
138 } else {
139 builder.append(human_readable_size(downloaded_size));
140 }
141 builder.append(" of "sv);
142 builder.append(m_url.basename());
143 window()->set_title(builder.to_deprecated_string());
144 }
145}
146
147void DownloadWidget::did_finish(bool success)
148{
149 dbgln("did_finish, success={}", success);
150
151 m_browser_image->load_from_file("/res/graphics/download-finished.gif"sv);
152 window()->set_title("Download finished!");
153 m_close_button->set_enabled(true);
154 m_cancel_button->set_text("Open in Folder"_string.release_value_but_fixme_should_propagate_errors());
155 m_cancel_button->on_click = [this](auto) {
156 Desktop::Launcher::open(URL::create_with_file_scheme(Core::StandardPaths::downloads_directory(), m_url.basename()));
157 window()->close();
158 };
159 m_cancel_button->update();
160
161 if (!success) {
162 GUI::MessageBox::show(window(), DeprecatedString::formatted("Download failed for some reason"), "Download failed"sv, GUI::MessageBox::Type::Error);
163 window()->close();
164 return;
165 }
166
167 if (m_close_on_finish_checkbox->is_checked())
168 window()->close();
169}
170
171}