GFileSystemModel.cpp 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  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 canonicalized_path(builder.to_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. child->reify_if_needed(*this);
  102. node = child;
  103. found = true;
  104. if (i == canonical_path.parts().size() - 1)
  105. return node->index(*this);
  106. break;
  107. }
  108. }
  109. if (!found)
  110. return {};
  111. }
  112. return {};
  113. }
  114. String GFileSystemModel::path(const GModelIndex& index) const
  115. {
  116. if (!index.is_valid())
  117. return {};
  118. auto& node = *(Node*)index.internal_data();
  119. node.reify_if_needed(*this);
  120. return node.full_path(*this);
  121. }
  122. GFileSystemModel::GFileSystemModel(const StringView& root_path, Mode mode)
  123. : m_root_path(canonicalized_path(root_path))
  124. , m_mode(mode)
  125. {
  126. m_open_folder_icon = GIcon::default_icon("filetype-folder-open");
  127. m_closed_folder_icon = GIcon::default_icon("filetype-folder");
  128. m_file_icon = GIcon::default_icon("filetype-unknown");
  129. update();
  130. }
  131. GFileSystemModel::~GFileSystemModel()
  132. {
  133. }
  134. void GFileSystemModel::update()
  135. {
  136. // FIXME: Support refreshing the model!
  137. if (m_root)
  138. return;
  139. m_root = new Node;
  140. m_root->name = m_root_path;
  141. m_root->reify_if_needed(*this);
  142. }
  143. int GFileSystemModel::row_count(const GModelIndex& index) const
  144. {
  145. if (!index.is_valid())
  146. return 1;
  147. auto& node = *(Node*)index.internal_data();
  148. node.reify_if_needed(*this);
  149. if (node.type == Node::Type::Directory)
  150. return node.children.size();
  151. return 0;
  152. }
  153. GModelIndex GFileSystemModel::index(int row, int column, const GModelIndex& parent) const
  154. {
  155. if (!parent.is_valid())
  156. return create_index(row, column, m_root);
  157. auto& node = *(Node*)parent.internal_data();
  158. return create_index(row, column, node.children[row]);
  159. }
  160. GModelIndex GFileSystemModel::parent_index(const GModelIndex& index) const
  161. {
  162. if (!index.is_valid())
  163. return {};
  164. auto& node = *(const Node*)index.internal_data();
  165. if (!node.parent) {
  166. ASSERT(&node == m_root);
  167. return {};
  168. }
  169. return node.parent->index(*this);
  170. }
  171. GVariant GFileSystemModel::data(const GModelIndex& index, Role role) const
  172. {
  173. if (!index.is_valid())
  174. return {};
  175. auto& node = *(const Node*)index.internal_data();
  176. if (role == GModel::Role::Display)
  177. return node.name;
  178. if (role == GModel::Role::Icon) {
  179. if (node.type == Node::Directory) {
  180. if (selected_index() == index)
  181. return m_open_folder_icon;
  182. return m_closed_folder_icon;
  183. }
  184. return m_file_icon;
  185. }
  186. return {};
  187. }
  188. int GFileSystemModel::column_count(const GModelIndex&) const
  189. {
  190. return 1;
  191. }