GFileSystemModel.cpp 6.1 KB

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