diff options
author | Tim Ledbetter <timledbetter@gmail.com> | 2023-02-01 17:30:35 +0000 |
---|---|---|
committer | Sam Atkins <atkinssj@gmail.com> | 2023-02-11 19:32:07 +0000 |
commit | baaf97787b075cfebde260f06d6c25a7e3bc4d93 (patch) | |
tree | 0b91634db3975e6ed3d76778cfd424108add7033 /Userland/Applications/FileManager | |
parent | d2e1f6ff5754b6a9e2f173bfdfd9b968d9f207d1 (diff) | |
download | serenity-baaf97787b075cfebde260f06d6c25a7e3bc4d93.zip |
FileManager: Show directory size and file count in PropertiesWindow
When displaying properties for a directory, the PropertiesWindow now
shows: the total number of files, the total number of subdirectories,
and the total size of all files, in bytes.
These numbers are calculated on a background thread, and current
progress is displayed to the user every 100ms.
Diffstat (limited to 'Userland/Applications/FileManager')
4 files changed, 105 insertions, 6 deletions
diff --git a/Userland/Applications/FileManager/CMakeLists.txt b/Userland/Applications/FileManager/CMakeLists.txt index aa347767e2..be9447af5b 100644 --- a/Userland/Applications/FileManager/CMakeLists.txt +++ b/Userland/Applications/FileManager/CMakeLists.txt @@ -25,4 +25,4 @@ set(GENERATED_SOURCES ) serenity_app(FileManager ICON app-file-manager) -target_link_libraries(FileManager PRIVATE LibCore LibGfx LibGUI LibDesktop LibConfig LibMain) +target_link_libraries(FileManager PRIVATE LibCore LibGfx LibGUI LibDesktop LibConfig LibMain LibThreading) diff --git a/Userland/Applications/FileManager/PropertiesWindow.cpp b/Userland/Applications/FileManager/PropertiesWindow.cpp index e7c3628e10..310f218137 100644 --- a/Userland/Applications/FileManager/PropertiesWindow.cpp +++ b/Userland/Applications/FileManager/PropertiesWindow.cpp @@ -10,6 +10,7 @@ #include <AK/NumberFormat.h> #include <Applications/FileManager/DirectoryView.h> #include <Applications/FileManager/PropertiesWindowGeneralTabGML.h> +#include <LibCore/DirIterator.h> #include <LibCore/System.h> #include <LibDesktop/Launcher.h> #include <LibGUI/BoxLayout.h> @@ -118,8 +119,8 @@ ErrorOr<void> PropertiesWindow::create_widgets(bool disable_rename) general_tab->remove_child(*link_location_widget); } - auto* size = general_tab->find_descendant_of_type_named<GUI::Label>("size"); - size->set_text(human_readable_size_long(st.st_size)); + m_size_label = general_tab->find_descendant_of_type_named<GUI::Label>("size"); + m_size_label->set_text(S_ISDIR(st.st_mode) ? "Calculating..." : human_readable_size_long(st.st_size)); auto* owner = general_tab->find_descendant_of_type_named<GUI::Label>("owner"); owner->set_text(DeprecatedString::formatted("{} ({})", owner_name, st.st_uid)); @@ -169,6 +170,17 @@ ErrorOr<void> PropertiesWindow::create_widgets(bool disable_rename) m_apply_button->on_click = [this](auto) { apply_changes(); }; m_apply_button->set_enabled(false); + if (S_ISDIR(m_old_mode)) { + m_directory_statistics_calculator = make_ref_counted<DirectoryStatisticsCalculator>(m_path); + m_directory_statistics_calculator->on_update = [this, origin_event_loop = &Core::EventLoop::current()](off_t total_size_in_bytes, size_t file_count, size_t directory_count) { + origin_event_loop->deferred_invoke([=, weak_this = make_weak_ptr<PropertiesWindow>()] { + if (auto strong_this = weak_this.strong_ref()) + strong_this->m_size_label->set_text(DeprecatedString::formatted("{}\n{} files, {} subdirectories", human_readable_size_long(total_size_in_bytes), file_count, directory_count)); + }); + }; + m_directory_statistics_calculator->start(); + } + update(); return {}; } @@ -262,3 +274,69 @@ ErrorOr<NonnullRefPtr<GUI::Button>> PropertiesWindow::make_button(DeprecatedStri button->set_fixed_size(70, 22); return button; } + +void PropertiesWindow::close() +{ + GUI::Window::close(); + if (m_directory_statistics_calculator) + m_directory_statistics_calculator->stop(); +} + +PropertiesWindow::DirectoryStatisticsCalculator::DirectoryStatisticsCalculator(DeprecatedString path) +{ + m_work_queue.enqueue(path); +} + +void PropertiesWindow::DirectoryStatisticsCalculator::start() +{ + using namespace AK::TimeLiterals; + VERIFY(!m_background_action); + + m_background_action = Threading::BackgroundAction<int>::construct( + [this, strong_this = NonnullRefPtr(*this)](auto& task) { + auto timer = Core::ElapsedTimer(); + while (!m_work_queue.is_empty()) { + auto base_directory = m_work_queue.dequeue(); + Core::DirIterator di(base_directory, Core::DirIterator::SkipParentAndBaseDir); + while (di.has_next()) { + if (task.is_cancelled()) + return ECANCELED; + + auto path = di.next_path(); + struct stat st = {}; + if (fstatat(di.fd(), path.characters(), &st, AT_SYMLINK_NOFOLLOW) < 0) { + perror("fstatat"); + continue; + } + + if (S_ISDIR(st.st_mode)) { + auto full_path = LexicalPath::join("/"sv, base_directory, path).string(); + m_directory_count++; + m_work_queue.enqueue(full_path); + } else if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { + m_file_count++; + m_total_size_in_bytes += st.st_size; + } + + // Show the first update, then show any subsequent updates every 100ms. + if (!task.is_cancelled() && on_update && (!timer.is_valid() || timer.elapsed_time() > 100_ms)) { + timer.start(); + on_update(m_total_size_in_bytes, m_file_count, m_directory_count); + } + } + } + return ESUCCESS; + }, + [this](auto result) -> ErrorOr<void> { + if (on_update && result == ESUCCESS) + on_update(m_total_size_in_bytes, m_file_count, m_directory_count); + + return {}; + }); +} + +void PropertiesWindow::DirectoryStatisticsCalculator::stop() +{ + VERIFY(m_background_action); + m_background_action->cancel(); +} diff --git a/Userland/Applications/FileManager/PropertiesWindow.h b/Userland/Applications/FileManager/PropertiesWindow.h index 53262aad78..ae6ab20819 100644 --- a/Userland/Applications/FileManager/PropertiesWindow.h +++ b/Userland/Applications/FileManager/PropertiesWindow.h @@ -7,6 +7,7 @@ #pragma once +#include <AK/Queue.h> #include <LibCore/File.h> #include <LibGUI/Button.h> #include <LibGUI/Dialog.h> @@ -14,6 +15,7 @@ #include <LibGUI/ImageWidget.h> #include <LibGUI/Label.h> #include <LibGUI/TextBox.h> +#include <LibThreading/BackgroundAction.h> class PropertiesWindow final : public GUI::Window { C_OBJECT(PropertiesWindow); @@ -22,6 +24,8 @@ public: static ErrorOr<NonnullRefPtr<PropertiesWindow>> try_create(DeprecatedString const& path, bool disable_rename, Window* parent = nullptr); virtual ~PropertiesWindow() override = default; + virtual void close() final; + private: PropertiesWindow(DeprecatedString const& path, Window* parent = nullptr); ErrorOr<void> create_widgets(bool disable_rename); @@ -38,6 +42,21 @@ private: mode_t execute; }; + class DirectoryStatisticsCalculator final : public RefCounted<DirectoryStatisticsCalculator> { + public: + DirectoryStatisticsCalculator(DeprecatedString path); + void start(); + void stop(); + Function<void(off_t total_size_in_bytes, size_t file_count, size_t directory_count)> on_update; + + private: + off_t m_total_size_in_bytes { 0 }; + size_t m_file_count { 0 }; + size_t m_directory_count { 0 }; + RefPtr<Threading::BackgroundAction<int>> m_background_action; + Queue<DeprecatedString> m_work_queue; + }; + static DeprecatedString const get_description(mode_t const mode) { if (S_ISREG(mode)) @@ -70,6 +89,8 @@ private: RefPtr<GUI::Button> m_apply_button; RefPtr<GUI::TextBox> m_name_box; RefPtr<GUI::ImageWidget> m_icon; + RefPtr<GUI::Label> m_size_label; + RefPtr<DirectoryStatisticsCalculator> m_directory_statistics_calculator; DeprecatedString m_name; DeprecatedString m_parent_path; DeprecatedString m_path; diff --git a/Userland/Applications/FileManager/PropertiesWindowGeneralTab.gml b/Userland/Applications/FileManager/PropertiesWindowGeneralTab.gml index 277dccf719..051bcf84ef 100644 --- a/Userland/Applications/FileManager/PropertiesWindowGeneralTab.gml +++ b/Userland/Applications/FileManager/PropertiesWindowGeneralTab.gml @@ -83,21 +83,21 @@ } @GUI::Widget { - fixed_height: 14 + fixed_height: 28 layout: @GUI::HorizontalBoxLayout { spacing: 12 } @GUI::Label { text: "Size:" - text_alignment: "CenterLeft" + text_alignment: "TopLeft" fixed_width: 80 } @GUI::Label { name: "size" text: "5.9 KiB (6097 bytes)" - text_alignment: "CenterLeft" + text_alignment: "TopLeft" } } |