mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 08:07:44 +00:00
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.
This commit is contained in:
parent
d2e1f6ff57
commit
baaf97787b
4 changed files with 105 additions and 6 deletions
|
@ -25,4 +25,4 @@ set(GENERATED_SOURCES
|
||||||
)
|
)
|
||||||
|
|
||||||
serenity_app(FileManager ICON app-file-manager)
|
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)
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <AK/NumberFormat.h>
|
#include <AK/NumberFormat.h>
|
||||||
#include <Applications/FileManager/DirectoryView.h>
|
#include <Applications/FileManager/DirectoryView.h>
|
||||||
#include <Applications/FileManager/PropertiesWindowGeneralTabGML.h>
|
#include <Applications/FileManager/PropertiesWindowGeneralTabGML.h>
|
||||||
|
#include <LibCore/DirIterator.h>
|
||||||
#include <LibCore/System.h>
|
#include <LibCore/System.h>
|
||||||
#include <LibDesktop/Launcher.h>
|
#include <LibDesktop/Launcher.h>
|
||||||
#include <LibGUI/BoxLayout.h>
|
#include <LibGUI/BoxLayout.h>
|
||||||
|
@ -118,8 +119,8 @@ ErrorOr<void> PropertiesWindow::create_widgets(bool disable_rename)
|
||||||
general_tab->remove_child(*link_location_widget);
|
general_tab->remove_child(*link_location_widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* size = general_tab->find_descendant_of_type_named<GUI::Label>("size");
|
m_size_label = general_tab->find_descendant_of_type_named<GUI::Label>("size");
|
||||||
size->set_text(human_readable_size_long(st.st_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");
|
auto* owner = general_tab->find_descendant_of_type_named<GUI::Label>("owner");
|
||||||
owner->set_text(DeprecatedString::formatted("{} ({})", owner_name, st.st_uid));
|
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->on_click = [this](auto) { apply_changes(); };
|
||||||
m_apply_button->set_enabled(false);
|
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();
|
update();
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -262,3 +274,69 @@ ErrorOr<NonnullRefPtr<GUI::Button>> PropertiesWindow::make_button(DeprecatedStri
|
||||||
button->set_fixed_size(70, 22);
|
button->set_fixed_size(70, 22);
|
||||||
return button;
|
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();
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Queue.h>
|
||||||
#include <LibCore/File.h>
|
#include <LibCore/File.h>
|
||||||
#include <LibGUI/Button.h>
|
#include <LibGUI/Button.h>
|
||||||
#include <LibGUI/Dialog.h>
|
#include <LibGUI/Dialog.h>
|
||||||
|
@ -14,6 +15,7 @@
|
||||||
#include <LibGUI/ImageWidget.h>
|
#include <LibGUI/ImageWidget.h>
|
||||||
#include <LibGUI/Label.h>
|
#include <LibGUI/Label.h>
|
||||||
#include <LibGUI/TextBox.h>
|
#include <LibGUI/TextBox.h>
|
||||||
|
#include <LibThreading/BackgroundAction.h>
|
||||||
|
|
||||||
class PropertiesWindow final : public GUI::Window {
|
class PropertiesWindow final : public GUI::Window {
|
||||||
C_OBJECT(PropertiesWindow);
|
C_OBJECT(PropertiesWindow);
|
||||||
|
@ -22,6 +24,8 @@ public:
|
||||||
static ErrorOr<NonnullRefPtr<PropertiesWindow>> try_create(DeprecatedString const& path, bool disable_rename, Window* parent = nullptr);
|
static ErrorOr<NonnullRefPtr<PropertiesWindow>> try_create(DeprecatedString const& path, bool disable_rename, Window* parent = nullptr);
|
||||||
virtual ~PropertiesWindow() override = default;
|
virtual ~PropertiesWindow() override = default;
|
||||||
|
|
||||||
|
virtual void close() final;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PropertiesWindow(DeprecatedString const& path, Window* parent = nullptr);
|
PropertiesWindow(DeprecatedString const& path, Window* parent = nullptr);
|
||||||
ErrorOr<void> create_widgets(bool disable_rename);
|
ErrorOr<void> create_widgets(bool disable_rename);
|
||||||
|
@ -38,6 +42,21 @@ private:
|
||||||
mode_t execute;
|
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)
|
static DeprecatedString const get_description(mode_t const mode)
|
||||||
{
|
{
|
||||||
if (S_ISREG(mode))
|
if (S_ISREG(mode))
|
||||||
|
@ -70,6 +89,8 @@ private:
|
||||||
RefPtr<GUI::Button> m_apply_button;
|
RefPtr<GUI::Button> m_apply_button;
|
||||||
RefPtr<GUI::TextBox> m_name_box;
|
RefPtr<GUI::TextBox> m_name_box;
|
||||||
RefPtr<GUI::ImageWidget> m_icon;
|
RefPtr<GUI::ImageWidget> m_icon;
|
||||||
|
RefPtr<GUI::Label> m_size_label;
|
||||||
|
RefPtr<DirectoryStatisticsCalculator> m_directory_statistics_calculator;
|
||||||
DeprecatedString m_name;
|
DeprecatedString m_name;
|
||||||
DeprecatedString m_parent_path;
|
DeprecatedString m_parent_path;
|
||||||
DeprecatedString m_path;
|
DeprecatedString m_path;
|
||||||
|
|
|
@ -83,21 +83,21 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@GUI::Widget {
|
@GUI::Widget {
|
||||||
fixed_height: 14
|
fixed_height: 28
|
||||||
layout: @GUI::HorizontalBoxLayout {
|
layout: @GUI::HorizontalBoxLayout {
|
||||||
spacing: 12
|
spacing: 12
|
||||||
}
|
}
|
||||||
|
|
||||||
@GUI::Label {
|
@GUI::Label {
|
||||||
text: "Size:"
|
text: "Size:"
|
||||||
text_alignment: "CenterLeft"
|
text_alignment: "TopLeft"
|
||||||
fixed_width: 80
|
fixed_width: 80
|
||||||
}
|
}
|
||||||
|
|
||||||
@GUI::Label {
|
@GUI::Label {
|
||||||
name: "size"
|
name: "size"
|
||||||
text: "5.9 KiB (6097 bytes)"
|
text: "5.9 KiB (6097 bytes)"
|
||||||
text_alignment: "CenterLeft"
|
text_alignment: "TopLeft"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue