/* * Copyright (c) 2022, the SerenityOS developers. * Copyright (c) 2023, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ #include "Tree.h" #include #include #include #include #include #include #include static constexpr size_t FILES_ENCOUNTERED_UPDATE_STEP_SIZE = 25; long long int TreeNode::update_totals() { long long int result = 0; if (m_children) { for (auto& child : *m_children) { result += child.update_totals(); } m_area = result; } else { result = m_area; } return result; } void TreeNode::sort_children_by_area() const { if (m_children) { Vector* children = const_cast*>(m_children.ptr()); quick_sort(*children, [](auto& a, auto& b) { return b.m_area < a.m_area; }); } } struct QueueEntry { QueueEntry(ByteString path, TreeNode* node) : path(move(path)) , node(node) {}; ByteString path; TreeNode* node { nullptr }; }; static MountInfo* find_mount_for_path(ByteString path, Vector& mounts) { MountInfo* result = nullptr; size_t length = 0; for (auto& mount_info : mounts) { ByteString& mount_point = mount_info.mount_point; if (path.starts_with(mount_point)) { if (!result || mount_point.length() > length) { result = &mount_info; length = mount_point.length(); } } } return result; } HashMap TreeNode::populate_filesize_tree(Vector& mounts, Function on_progress) { VERIFY(!m_name.ends_with('/')); Queue queue; queue.enqueue(QueueEntry(m_name, this)); size_t files_encountered_count = 0; HashMap error_accumulator; auto log_error = [&](Error& error) { auto error_code = error.code(); int error_sum = error_accumulator.get(error_code).value_or(0); error_accumulator.set(error_code, error_sum + 1); }; StringBuilder builder = StringBuilder(); builder.append(m_name); builder.append('/'); MountInfo* root_mount_info = find_mount_for_path(builder.to_byte_string(), mounts); if (!root_mount_info) { return error_accumulator; } while (!queue.is_empty()) { QueueEntry queue_entry = queue.dequeue(); builder.clear(); builder.append(queue_entry.path); builder.append('/'); MountInfo* mount_info = find_mount_for_path(builder.to_byte_string(), mounts); if (!mount_info || (mount_info != root_mount_info && mount_info->source != root_mount_info->source)) { continue; } auto directory_or_error = Core::Directory::create(builder.string_view(), Core::Directory::CreateDirectories::No); if (directory_or_error.is_error()) { log_error(directory_or_error.error()); } else { auto directory = directory_or_error.release_value(); queue_entry.node->m_children = make>(); auto iteration_result = Core::Directory::for_each_entry(builder.string_view(), Core::DirIterator::SkipParentAndBaseDir, [&](auto& entry, auto&) -> ErrorOr { TRY(queue_entry.node->m_children->try_append(TreeNode(entry.name))); return IterationDecision::Continue; }); if (iteration_result.is_error()) log_error(iteration_result.error()); for (auto& child : *queue_entry.node->m_children) { files_encountered_count += 1; if (!(files_encountered_count % FILES_ENCOUNTERED_UPDATE_STEP_SIZE)) on_progress(files_encountered_count); builder.append(child.m_name); auto st_or_error = directory.stat(child.m_name, AT_SYMLINK_NOFOLLOW); if (st_or_error.is_error()) { log_error(st_or_error.error()); } else { auto st = st_or_error.release_value(); if (S_ISDIR(st.st_mode)) { queue.enqueue(QueueEntry(builder.to_byte_string(), &child)); } else { child.m_area = st.st_size; } } builder.trim(child.m_name.length()); } } } update_totals(); return error_accumulator; } Optional TreeNode::child_with_name(ByteString name) const { for (auto& child : *m_children) { if (child.name() == name) return child; } return {}; }