Serenity Operating System
at master 219 lines 7.8 kB view raw
1/* 2 * Copyright (c) 2021, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2022, Alexander Narsudinov <a.narsudinov@gmail.com> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include "FileOperationProgressWidget.h" 9#include "FileUtils.h" 10#include <Applications/FileManager/FileOperationProgressGML.h> 11#include <LibCore/Notifier.h> 12#include <LibGUI/Button.h> 13#include <LibGUI/ImageWidget.h> 14#include <LibGUI/Label.h> 15#include <LibGUI/MessageBox.h> 16#include <LibGUI/Progressbar.h> 17#include <LibGUI/Window.h> 18 19namespace FileManager { 20 21FileOperationProgressWidget::FileOperationProgressWidget(FileOperation operation, NonnullOwnPtr<Core::BufferedFile> helper_pipe, int helper_pipe_fd) 22 : m_operation(operation) 23 , m_helper_pipe(move(helper_pipe)) 24{ 25 load_from_gml(file_operation_progress_gml).release_value_but_fixme_should_propagate_errors(); 26 27 auto& button = *find_descendant_of_type_named<GUI::Button>("button"); 28 29 auto& file_copy_animation = *find_descendant_of_type_named<GUI::ImageWidget>("file_copy_animation"); 30 file_copy_animation.load_from_file("/res/graphics/file-flying-animation.gif"sv); 31 file_copy_animation.animate(); 32 33 auto& source_folder_icon = *find_descendant_of_type_named<GUI::ImageWidget>("source_folder_icon"); 34 source_folder_icon.load_from_file("/res/icons/32x32/filetype-folder-open.png"sv); 35 36 auto& destination_folder_icon = *find_descendant_of_type_named<GUI::ImageWidget>("destination_folder_icon"); 37 38 switch (m_operation) { 39 case FileOperation::Delete: 40 destination_folder_icon.load_from_file("/res/icons/32x32/recycle-bin.png"sv); 41 break; 42 default: 43 destination_folder_icon.load_from_file("/res/icons/32x32/filetype-folder-open.png"sv); 44 break; 45 } 46 47 button.on_click = [this](auto) { 48 close_pipe(); 49 window()->close(); 50 }; 51 52 auto& files_copied_label = *find_descendant_of_type_named<GUI::Label>("files_copied_label"); 53 auto& current_file_action_label = *find_descendant_of_type_named<GUI::Label>("current_file_action_label"); 54 55 switch (m_operation) { 56 case FileOperation::Copy: 57 files_copied_label.set_text("Copying files..."); 58 current_file_action_label.set_text("Copying: "); 59 break; 60 case FileOperation::Move: 61 files_copied_label.set_text("Moving files..."); 62 current_file_action_label.set_text("Moving: "); 63 break; 64 case FileOperation::Delete: 65 files_copied_label.set_text("Deleting files..."); 66 current_file_action_label.set_text("Deleting: "); 67 break; 68 default: 69 VERIFY_NOT_REACHED(); 70 } 71 72 m_notifier = Core::Notifier::construct(helper_pipe_fd, Core::Notifier::Read); 73 m_notifier->on_ready_to_read = [this] { 74 auto line_buffer_or_error = ByteBuffer::create_zeroed(1 * KiB); 75 if (line_buffer_or_error.is_error()) { 76 did_error("Failed to allocate ByteBuffer for reading data."sv); 77 return; 78 } 79 auto line_buffer = line_buffer_or_error.release_value(); 80 auto line_or_error = m_helper_pipe->read_line(line_buffer.bytes()); 81 if (line_or_error.is_error() || line_or_error.value().is_empty()) { 82 did_error("Read from pipe returned null."sv); 83 return; 84 } 85 86 auto line = line_or_error.release_value(); 87 88 auto parts = line.split_view(' '); 89 VERIFY(!parts.is_empty()); 90 91 if (parts[0] == "ERROR"sv) { 92 did_error(line.substring_view(6)); 93 return; 94 } 95 96 if (parts[0] == "WARN"sv) { 97 did_error(line.substring_view(5)); 98 return; 99 } 100 101 if (parts[0] == "FINISH"sv) { 102 did_finish(); 103 return; 104 } 105 106 if (parts[0] == "PROGRESS"sv) { 107 VERIFY(parts.size() >= 8); 108 did_progress( 109 parts[3].to_uint().value_or(0), 110 parts[4].to_uint().value_or(0), 111 parts[1].to_uint().value_or(0), 112 parts[2].to_uint().value_or(0), 113 parts[5].to_uint().value_or(0), 114 parts[6].to_uint().value_or(0), 115 parts[7]); 116 } 117 }; 118 119 m_elapsed_timer.start(); 120} 121 122FileOperationProgressWidget::~FileOperationProgressWidget() 123{ 124 close_pipe(); 125} 126 127void FileOperationProgressWidget::did_finish() 128{ 129 close_pipe(); 130 window()->close(); 131} 132 133void FileOperationProgressWidget::did_error(StringView message) 134{ 135 // FIXME: Communicate more with the user about errors. 136 close_pipe(); 137 GUI::MessageBox::show(window(), DeprecatedString::formatted("An error occurred: {}", message), "Error"sv, GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK); 138 window()->close(); 139} 140 141DeprecatedString FileOperationProgressWidget::estimate_time(off_t bytes_done, off_t total_byte_count) 142{ 143 i64 const elapsed_seconds = m_elapsed_timer.elapsed_time().to_seconds(); 144 145 if (bytes_done == 0 || elapsed_seconds < 3) 146 return "Estimating..."; 147 148 off_t bytes_left = total_byte_count - bytes_done; 149 int seconds_remaining = (bytes_left * elapsed_seconds) / bytes_done; 150 151 if (seconds_remaining < 30) 152 return DeprecatedString::formatted("{} seconds", 5 + seconds_remaining - seconds_remaining % 5); 153 if (seconds_remaining < 60) 154 return "About a minute"; 155 if (seconds_remaining < 90) 156 return "Over a minute"; 157 if (seconds_remaining < 120) 158 return "Less than two minutes"; 159 160 time_t minutes_remaining = seconds_remaining / 60; 161 seconds_remaining %= 60; 162 163 if (minutes_remaining < 60) { 164 if (seconds_remaining < 30) 165 return DeprecatedString::formatted("About {} minutes", minutes_remaining); 166 return DeprecatedString::formatted("Over {} minutes", minutes_remaining); 167 } 168 169 time_t hours_remaining = minutes_remaining / 60; 170 minutes_remaining %= 60; 171 172 return DeprecatedString::formatted("{} hours and {} minutes", hours_remaining, minutes_remaining); 173} 174 175void FileOperationProgressWidget::did_progress(off_t bytes_done, off_t total_byte_count, size_t files_done, size_t total_file_count, [[maybe_unused]] off_t current_file_done, [[maybe_unused]] off_t current_file_size, StringView current_filename) 176{ 177 auto& files_copied_label = *find_descendant_of_type_named<GUI::Label>("files_copied_label"); 178 auto& current_file_label = *find_descendant_of_type_named<GUI::Label>("current_file_label"); 179 auto& overall_progressbar = *find_descendant_of_type_named<GUI::Progressbar>("overall_progressbar"); 180 auto& estimated_time_label = *find_descendant_of_type_named<GUI::Label>("estimated_time_label"); 181 182 current_file_label.set_text(current_filename); 183 184 switch (m_operation) { 185 case FileOperation::Copy: 186 files_copied_label.set_text(DeprecatedString::formatted("Copying file {} of {}", files_done, total_file_count)); 187 break; 188 case FileOperation::Move: 189 files_copied_label.set_text(DeprecatedString::formatted("Moving file {} of {}", files_done, total_file_count)); 190 break; 191 case FileOperation::Delete: 192 files_copied_label.set_text(DeprecatedString::formatted("Deleting file {} of {}", files_done, total_file_count)); 193 break; 194 default: 195 VERIFY_NOT_REACHED(); 196 } 197 198 estimated_time_label.set_text(estimate_time(bytes_done, total_byte_count)); 199 200 if (total_byte_count) { 201 window()->set_progress(100.0f * bytes_done / total_byte_count); 202 overall_progressbar.set_max(total_byte_count); 203 overall_progressbar.set_value(bytes_done); 204 } 205} 206 207void FileOperationProgressWidget::close_pipe() 208{ 209 if (!m_helper_pipe) 210 return; 211 m_helper_pipe = nullptr; 212 if (m_notifier) { 213 m_notifier->set_enabled(false); 214 m_notifier->on_ready_to_read = nullptr; 215 } 216 m_notifier = nullptr; 217} 218 219}