GFileSystemModel.cpp 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. #include <AK/FileSystemPath.h>
  2. #include <AK/StringBuilder.h>
  3. #include <LibCore/CDirIterator.h>
  4. #include <LibGUI/GFileSystemModel.h>
  5. #include <dirent.h>
  6. #include <stdio.h>
  7. #include <sys/stat.h>
  8. #include <unistd.h>
  9. struct GFileSystemModel::Node {
  10. String name;
  11. Node* parent { nullptr };
  12. Vector<Node*> children;
  13. enum Type {
  14. Unknown,
  15. Directory,
  16. File
  17. };
  18. Type type { Unknown };
  19. bool has_traversed { false };
  20. GModelIndex index(const GFileSystemModel& model) const
  21. {
  22. if (!parent)
  23. return model.create_index(0, 0, const_cast<Node*>(this));
  24. for (int row = 0; row < parent->children.size(); ++row) {
  25. if (parent->children[row] == this)
  26. return model.create_index(row, 0, const_cast<Node*>(this));
  27. }
  28. ASSERT_NOT_REACHED();
  29. }
  30. void traverse_if_needed(const GFileSystemModel& model)
  31. {
  32. if (type != Node::Directory || has_traversed)
  33. return;
  34. has_traversed = true;
  35. auto full_path = this->full_path(model);
  36. CDirIterator di(full_path, CDirIterator::SkipDots);
  37. if (di.has_error()) {
  38. fprintf(stderr, "CDirIterator: %s\n", di.error_string());
  39. return;
  40. }
  41. while (di.has_next()) {
  42. String name = di.next_path();
  43. struct stat st;
  44. int rc = lstat(String::format("%s/%s", full_path.characters(), name.characters()).characters(), &st);
  45. if (rc < 0) {
  46. perror("lstat");
  47. continue;
  48. }
  49. if (model.m_mode == DirectoriesOnly && !S_ISDIR(st.st_mode))
  50. continue;
  51. auto* child = new Node;
  52. child->name = name;
  53. child->type = S_ISDIR(st.st_mode) ? Node::Type::Directory : Node::Type::File;
  54. child->parent = this;
  55. children.append(child);
  56. }
  57. }
  58. void reify_if_needed(const GFileSystemModel& model)
  59. {
  60. traverse_if_needed(model);
  61. if (type != Node::Type::Unknown)
  62. return;
  63. struct stat st;
  64. auto full_path = this->full_path(model);
  65. int rc = lstat(full_path.characters(), &st);
  66. dbgprintf("lstat(%s) = %d\n", full_path.characters(), rc);
  67. if (rc < 0) {
  68. perror("lstat");
  69. return;
  70. }
  71. type = S_ISDIR(st.st_mode) ? Node::Type::Directory : Node::Type::File;
  72. }
  73. String full_path(const GFileSystemModel& model) const
  74. {
  75. Vector<String, 32> lineage;
  76. for (auto* ancestor = parent; ancestor; ancestor = ancestor->parent) {
  77. lineage.append(ancestor->name);
  78. }
  79. StringBuilder builder;
  80. builder.append(model.root_path());
  81. for (int i = lineage.size() - 1; i >= 0; --i) {
  82. builder.append('/');
  83. builder.append(lineage[i]);
  84. }
  85. builder.append('/');
  86. builder.append(name);
  87. return FileSystemPath(builder.to_string()).string();
  88. }
  89. };
  90. GModelIndex GFileSystemModel::index(const StringView& path) const
  91. {
  92. FileSystemPath canonical_path(path);
  93. const Node* node = m_root;
  94. if (canonical_path.string() == "/")
  95. return m_root->index(*this);
  96. for (int i = 0; i < canonical_path.parts().size(); ++i) {
  97. auto& part = canonical_path.parts()[i];
  98. bool found = false;
  99. for (auto& child : node->children) {
  100. if (child->name == part) {
  101. node = child;
  102. found = true;
  103. if (i == canonical_path.parts().size() - 1)
  104. return node->index(*this);
  105. break;
  106. }
  107. }
  108. if (!found)
  109. return {};
  110. }
  111. return {};
  112. }
  113. String GFileSystemModel::path(const GModelIndex& index) const
  114. {
  115. if (!index.is_valid())
  116. return {};
  117. auto& node = *(Node*)index.internal_data();
  118. node.reify_if_needed(*this);
  119. return node.full_path(*this);
  120. }
  121. GFileSystemModel::GFileSystemModel(const StringView& root_path, Mode mode)
  122. : m_root_path(FileSystemPath(root_path).string())
  123. , m_mode(mode)
  124. {
  125. m_open_folder_icon = GIcon::default_icon("filetype-folder-open");
  126. m_closed_folder_icon = GIcon::default_icon("filetype-folder");
  127. m_file_icon = GIcon::default_icon("filetype-unknown");
  128. update();
  129. }
  130. GFileSystemModel::~GFileSystemModel()
  131. {
  132. }
  133. void GFileSystemModel::update()
  134. {
  135. // FIXME: Support refreshing the model!
  136. if (m_root)
  137. return;
  138. m_root = new Node;
  139. m_root->name = m_root_path;
  140. m_root->reify_if_needed(*this);
  141. }
  142. int GFileSystemModel::row_count(const GModelIndex& index) const
  143. {
  144. if (!index.is_valid())
  145. return 1;
  146. auto& node = *(Node*)index.internal_data();
  147. node.reify_if_needed(*this);
  148. if (node.type == Node::Type::Directory)
  149. return node.children.size();
  150. return 0;
  151. }
  152. GModelIndex GFileSystemModel::index(int row, int column, const GModelIndex& parent) const
  153. {
  154. if (!parent.is_valid())
  155. return create_index(row, column, m_root);
  156. auto& node = *(Node*)parent.internal_data();
  157. return create_index(row, column, node.children[row]);
  158. }
  159. GModelIndex GFileSystemModel::parent_index(const GModelIndex& index) const
  160. {
  161. if (!index.is_valid())
  162. return {};
  163. auto& node = *(const Node*)index.internal_data();
  164. if (!node.parent) {
  165. ASSERT(&node == m_root);
  166. return {};
  167. }
  168. return node.parent->index(*this);
  169. }
  170. GVariant GFileSystemModel::data(const GModelIndex& index, Role role) const
  171. {
  172. if (!index.is_valid())
  173. return {};
  174. auto& node = *(const Node*)index.internal_data();
  175. if (role == GModel::Role::Display)
  176. return node.name;
  177. if (role == GModel::Role::Icon) {
  178. if (node.type == Node::Directory) {
  179. if (selected_index() == index)
  180. return m_open_folder_icon;
  181. return m_closed_folder_icon;
  182. }
  183. return m_file_icon;
  184. }
  185. return {};
  186. }
  187. int GFileSystemModel::column_count(const GModelIndex&) const
  188. {
  189. return 1;
  190. }