mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 22:12:44 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			209 lines
		
	
	
	
		
			5.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			209 lines
		
	
	
	
		
			5.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include <AK/FileSystemPath.h>
 | |
| #include <AK/StringBuilder.h>
 | |
| #include <LibCore/CDirIterator.h>
 | |
| #include <LibGUI/GFileSystemModel.h>
 | |
| #include <dirent.h>
 | |
| #include <stdio.h>
 | |
| #include <sys/stat.h>
 | |
| #include <unistd.h>
 | |
| 
 | |
| struct GFileSystemModel::Node {
 | |
|     String name;
 | |
|     Node* parent { nullptr };
 | |
|     Vector<Node*> children;
 | |
|     enum Type {
 | |
|         Unknown,
 | |
|         Directory,
 | |
|         File
 | |
|     };
 | |
|     Type type { Unknown };
 | |
| 
 | |
|     bool has_traversed { false };
 | |
| 
 | |
|     GModelIndex index(const GFileSystemModel& model) const
 | |
|     {
 | |
|         if (!parent)
 | |
|             return model.create_index(0, 0, (void*)this);
 | |
|         for (int row = 0; row < parent->children.size(); ++row) {
 | |
|             if (parent->children[row] == this)
 | |
|                 return model.create_index(row, 0, (void*)this);
 | |
|         }
 | |
|         ASSERT_NOT_REACHED();
 | |
|     }
 | |
| 
 | |
|     void traverse_if_needed(const GFileSystemModel& model)
 | |
|     {
 | |
|         if (type != Node::Directory || has_traversed)
 | |
|             return;
 | |
|         has_traversed = true;
 | |
| 
 | |
|         auto full_path = this->full_path(model);
 | |
|         CDirIterator di(full_path, CDirIterator::SkipDots);
 | |
|         if (di.has_error()) {
 | |
|             fprintf(stderr, "CDirIterator: %s\n", di.error_string());
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         while (di.has_next()) {
 | |
|             String name = di.next_path();
 | |
|             struct stat st;
 | |
|             int rc = lstat(String::format("%s/%s", full_path.characters(), name.characters()).characters(), &st);
 | |
|             if (rc < 0) {
 | |
|                 perror("lstat");
 | |
|                 continue;
 | |
|             }
 | |
|             if (model.m_mode == DirectoriesOnly && !S_ISDIR(st.st_mode))
 | |
|                 continue;
 | |
|             auto* child = new Node;
 | |
|             child->name = name;
 | |
|             child->type = S_ISDIR(st.st_mode) ? Node::Type::Directory : Node::Type::File;
 | |
|             child->parent = this;
 | |
|             children.append(child);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void reify_if_needed(const GFileSystemModel& model)
 | |
|     {
 | |
|         traverse_if_needed(model);
 | |
|         if (type != Node::Type::Unknown)
 | |
|             return;
 | |
|         struct stat st;
 | |
|         auto full_path = this->full_path(model);
 | |
|         int rc = lstat(full_path.characters(), &st);
 | |
|         dbgprintf("lstat(%s) = %d\n", full_path.characters(), rc);
 | |
|         if (rc < 0) {
 | |
|             perror("lstat");
 | |
|             return;
 | |
|         }
 | |
|         type = S_ISDIR(st.st_mode) ? Node::Type::Directory : Node::Type::File;
 | |
|     }
 | |
| 
 | |
|     String full_path(const GFileSystemModel& model) const
 | |
|     {
 | |
|         Vector<String, 32> lineage;
 | |
|         for (auto* ancestor = parent; ancestor; ancestor = ancestor->parent) {
 | |
|             lineage.append(ancestor->name);
 | |
|         }
 | |
|         StringBuilder builder;
 | |
|         builder.append(model.root_path());
 | |
|         for (int i = lineage.size() - 1; i >= 0; --i) {
 | |
|             builder.append('/');
 | |
|             builder.append(lineage[i]);
 | |
|         }
 | |
|         builder.append('/');
 | |
|         builder.append(name);
 | |
|         return FileSystemPath(builder.to_string()).string();
 | |
|     }
 | |
| };
 | |
| 
 | |
| GModelIndex GFileSystemModel::index(const StringView& path) const
 | |
| {
 | |
|     FileSystemPath canonical_path(path);
 | |
|     const Node* node = m_root;
 | |
|     if (canonical_path.string() == "/")
 | |
|         return m_root->index(*this);
 | |
|     for (int i = 0; i < canonical_path.parts().size(); ++i) {
 | |
|         auto& part = canonical_path.parts()[i];
 | |
|         bool found = false;
 | |
|         for (auto& child : node->children) {
 | |
|             if (child->name == part) {
 | |
|                 node = child;
 | |
|                 found = true;
 | |
|                 if (i == canonical_path.parts().size() - 1)
 | |
|                     return node->index(*this);
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|         if (!found)
 | |
|             return {};
 | |
|     }
 | |
|     return {};
 | |
| }
 | |
| 
 | |
| String GFileSystemModel::path(const GModelIndex& index) const
 | |
| {
 | |
|     if (!index.is_valid())
 | |
|         return {};
 | |
|     auto& node = *(Node*)index.internal_data();
 | |
|     node.reify_if_needed(*this);
 | |
|     return node.full_path(*this);
 | |
| }
 | |
| 
 | |
| GFileSystemModel::GFileSystemModel(const StringView& root_path, Mode mode)
 | |
|     : m_root_path(FileSystemPath(root_path).string())
 | |
|     , m_mode(mode)
 | |
| {
 | |
|     m_open_folder_icon = GIcon::default_icon("filetype-folder-open");
 | |
|     m_closed_folder_icon = GIcon::default_icon("filetype-folder");
 | |
|     m_file_icon = GIcon::default_icon("filetype-unknown");
 | |
|     update();
 | |
| }
 | |
| 
 | |
| GFileSystemModel::~GFileSystemModel()
 | |
| {
 | |
| }
 | |
| 
 | |
| void GFileSystemModel::update()
 | |
| {
 | |
|     // FIXME: Support refreshing the model!
 | |
|     if (m_root)
 | |
|         return;
 | |
| 
 | |
|     m_root = new Node;
 | |
|     m_root->name = m_root_path;
 | |
|     m_root->reify_if_needed(*this);
 | |
| }
 | |
| 
 | |
| int GFileSystemModel::row_count(const GModelIndex& index) const
 | |
| {
 | |
|     if (!index.is_valid())
 | |
|         return 1;
 | |
|     auto& node = *(Node*)index.internal_data();
 | |
|     node.reify_if_needed(*this);
 | |
|     if (node.type == Node::Type::Directory)
 | |
|         return node.children.size();
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| GModelIndex GFileSystemModel::index(int row, int column, const GModelIndex& parent) const
 | |
| {
 | |
|     if (!parent.is_valid())
 | |
|         return create_index(row, column, m_root);
 | |
|     auto& node = *(Node*)parent.internal_data();
 | |
|     return create_index(row, column, node.children[row]);
 | |
| }
 | |
| 
 | |
| GModelIndex GFileSystemModel::parent_index(const GModelIndex& index) const
 | |
| {
 | |
|     if (!index.is_valid())
 | |
|         return {};
 | |
|     auto& node = *(const Node*)index.internal_data();
 | |
|     if (!node.parent) {
 | |
|         ASSERT(&node == m_root);
 | |
|         return {};
 | |
|     }
 | |
|     return node.parent->index(*this);
 | |
| }
 | |
| 
 | |
| GVariant GFileSystemModel::data(const GModelIndex& index, Role role) const
 | |
| {
 | |
|     if (!index.is_valid())
 | |
|         return {};
 | |
|     auto& node = *(const Node*)index.internal_data();
 | |
|     if (role == GModel::Role::Display)
 | |
|         return node.name;
 | |
|     if (role == GModel::Role::Icon) {
 | |
|         if (node.type == Node::Directory) {
 | |
|             if (selected_index() == index)
 | |
|                 return m_open_folder_icon;
 | |
|             return m_closed_folder_icon;
 | |
|         }
 | |
|         return m_file_icon;
 | |
|     }
 | |
|     return {};
 | |
| }
 | |
| 
 | |
| int GFileSystemModel::column_count(const GModelIndex&) const
 | |
| {
 | |
|     return 1;
 | |
| }
 | 
