DirectoryModel.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. #include "DirectoryModel.h"
  2. #include <dirent.h>
  3. #include <stdio.h>
  4. #include <unistd.h>
  5. #include <grp.h>
  6. #include <pwd.h>
  7. #include <AK/FileSystemPath.h>
  8. #include <AK/StringBuilder.h>
  9. #include <SharedGraphics/GraphicsBitmap.h>
  10. #include <SharedGraphics/Painter.h>
  11. static HashMap<String, RetainPtr<GraphicsBitmap>>& thumbnail_cache()
  12. {
  13. static HashMap<String, RetainPtr<GraphicsBitmap>>* s_map;
  14. if (!s_map)
  15. s_map = new HashMap<String, RetainPtr<GraphicsBitmap>>();
  16. return *s_map;
  17. }
  18. int thumbnail_thread(void* model)
  19. {
  20. for (;;) {
  21. sleep(1);
  22. for (auto& it : thumbnail_cache()) {
  23. if (it.value)
  24. continue;
  25. if (auto png_bitmap = GraphicsBitmap::load_from_file(it.key)) {
  26. auto thumbnail = GraphicsBitmap::create(png_bitmap->format(), { 32, 32 });
  27. Painter painter(*thumbnail);
  28. painter.draw_scaled_bitmap(thumbnail->rect(), *png_bitmap, png_bitmap->rect());
  29. it.value = move(thumbnail);
  30. ((DirectoryModel*)model)->did_update();
  31. }
  32. }
  33. }
  34. }
  35. DirectoryModel::DirectoryModel()
  36. {
  37. create_thread(thumbnail_thread, this);
  38. m_directory_icon = GIcon(GraphicsBitmap::load_from_file("/res/icons/folder16.png"), GraphicsBitmap::load_from_file("/res/icons/32x32/folder.png"));
  39. m_file_icon = GIcon(GraphicsBitmap::load_from_file("/res/icons/file16.png"), GraphicsBitmap::load_from_file("/res/icons/32x32/file.png"));
  40. m_symlink_icon = GIcon(GraphicsBitmap::load_from_file("/res/icons/link16.png"));
  41. m_socket_icon = GIcon(GraphicsBitmap::load_from_file("/res/icons/socket16.png"));
  42. m_executable_icon = GIcon(GraphicsBitmap::load_from_file("/res/icons/executable16.png"), GraphicsBitmap::load_from_file("/res/icons/32x32/filetype-executable.png"));
  43. m_filetype_image_icon = GIcon(GraphicsBitmap::load_from_file("/res/icons/16x16/filetype-image.png"), GraphicsBitmap::load_from_file("/res/icons/32x32/filetype-image.png"));
  44. setpwent();
  45. while (auto* passwd = getpwent())
  46. m_user_names.set(passwd->pw_uid, passwd->pw_name);
  47. endpwent();
  48. setgrent();
  49. while (auto* group = getgrent())
  50. m_group_names.set(group->gr_gid, group->gr_name);
  51. endgrent();
  52. }
  53. DirectoryModel::~DirectoryModel()
  54. {
  55. }
  56. int DirectoryModel::row_count() const
  57. {
  58. return m_directories.size() + m_files.size();
  59. }
  60. int DirectoryModel::column_count() const
  61. {
  62. return Column::__Count;
  63. }
  64. String DirectoryModel::column_name(int column) const
  65. {
  66. switch (column) {
  67. case Column::Icon: return "";
  68. case Column::Name: return "Name";
  69. case Column::Size: return "Size";
  70. case Column::Owner: return "Owner";
  71. case Column::Group: return "Group";
  72. case Column::Permissions: return "Mode";
  73. case Column::Inode: return "Inode";
  74. }
  75. ASSERT_NOT_REACHED();
  76. }
  77. GModel::ColumnMetadata DirectoryModel::column_metadata(int column) const
  78. {
  79. switch (column) {
  80. case Column::Icon: return { 16, TextAlignment::Center };
  81. case Column::Name: return { 120, TextAlignment::CenterLeft };
  82. case Column::Size: return { 80, TextAlignment::CenterRight };
  83. case Column::Owner: return { 50, TextAlignment::CenterLeft };
  84. case Column::Group: return { 50, TextAlignment::CenterLeft };
  85. case Column::Permissions: return { 80, TextAlignment::CenterLeft };
  86. case Column::Inode: return { 80, TextAlignment::CenterRight };
  87. }
  88. ASSERT_NOT_REACHED();
  89. }
  90. GIcon DirectoryModel::icon_for(const Entry& entry) const
  91. {
  92. if (S_ISDIR(entry.mode))
  93. return m_directory_icon;
  94. if (S_ISLNK(entry.mode))
  95. return m_symlink_icon;
  96. if (S_ISSOCK(entry.mode))
  97. return m_socket_icon;
  98. if (entry.mode & S_IXUSR)
  99. return m_executable_icon;
  100. if (entry.name.ends_with(".png")) {
  101. if (!entry.thumbnail) {
  102. auto path = entry.full_path(*this);
  103. auto it = thumbnail_cache().find(path);
  104. if (it != thumbnail_cache().end()) {
  105. entry.thumbnail = (*it).value.copy_ref();
  106. } else {
  107. thumbnail_cache().set(path, nullptr);
  108. }
  109. }
  110. if (!entry.thumbnail)
  111. return m_filetype_image_icon;
  112. return GIcon(m_filetype_image_icon.bitmap_for_size(16), *entry.thumbnail);
  113. }
  114. return m_file_icon;
  115. }
  116. static String permission_string(mode_t mode)
  117. {
  118. StringBuilder builder;
  119. if (S_ISDIR(mode))
  120. builder.append("d");
  121. else if (S_ISLNK(mode))
  122. builder.append("l");
  123. else if (S_ISBLK(mode))
  124. builder.append("b");
  125. else if (S_ISCHR(mode))
  126. builder.append("c");
  127. else if (S_ISFIFO(mode))
  128. builder.append("f");
  129. else if (S_ISSOCK(mode))
  130. builder.append("s");
  131. else if (S_ISREG(mode))
  132. builder.append("-");
  133. else
  134. builder.append("?");
  135. builder.appendf("%c%c%c%c%c%c%c%c",
  136. mode & S_IRUSR ? 'r' : '-',
  137. mode & S_IWUSR ? 'w' : '-',
  138. mode & S_ISUID ? 's' : (mode & S_IXUSR ? 'x' : '-'),
  139. mode & S_IRGRP ? 'r' : '-',
  140. mode & S_IWGRP ? 'w' : '-',
  141. mode & S_ISGID ? 's' : (mode & S_IXGRP ? 'x' : '-'),
  142. mode & S_IROTH ? 'r' : '-',
  143. mode & S_IWOTH ? 'w' : '-'
  144. );
  145. if (mode & S_ISVTX)
  146. builder.append("t");
  147. else
  148. builder.appendf("%c", mode & S_IXOTH ? 'x' : '-');
  149. return builder.to_string();
  150. }
  151. String DirectoryModel::name_for_uid(uid_t uid) const
  152. {
  153. auto it = m_user_names.find(uid);
  154. if (it == m_user_names.end())
  155. return String::format("%u", uid);
  156. return (*it).value;
  157. }
  158. String DirectoryModel::name_for_gid(uid_t gid) const
  159. {
  160. auto it = m_user_names.find(gid);
  161. if (it == m_user_names.end())
  162. return String::format("%u", gid);
  163. return (*it).value;
  164. }
  165. GVariant DirectoryModel::data(const GModelIndex& index, Role role) const
  166. {
  167. ASSERT(is_valid(index));
  168. auto& entry = this->entry(index.row());
  169. if (role == Role::Sort) {
  170. switch (index.column()) {
  171. case Column::Icon: return entry.is_directory() ? 0 : 1;
  172. case Column::Name: return entry.name;
  173. case Column::Size: return (int)entry.size;
  174. case Column::Owner: return name_for_uid(entry.uid);
  175. case Column::Group: return name_for_gid(entry.gid);
  176. case Column::Permissions: return permission_string(entry.mode);
  177. case Column::Inode: return (int)entry.inode;
  178. }
  179. ASSERT_NOT_REACHED();
  180. }
  181. if (role == Role::Display) {
  182. switch (index.column()) {
  183. case Column::Icon: return icon_for(entry);
  184. case Column::Name: return entry.name;
  185. case Column::Size: return (int)entry.size;
  186. case Column::Owner: return name_for_uid(entry.uid);
  187. case Column::Group: return name_for_gid(entry.gid);
  188. case Column::Permissions: return permission_string(entry.mode);
  189. case Column::Inode: return (int)entry.inode;
  190. }
  191. }
  192. if (role == Role::Icon) {
  193. return icon_for(entry);
  194. }
  195. return { };
  196. }
  197. void DirectoryModel::update()
  198. {
  199. DIR* dirp = opendir(m_path.characters());
  200. if (!dirp) {
  201. perror("opendir");
  202. exit(1);
  203. }
  204. m_directories.clear();
  205. m_files.clear();
  206. m_bytes_in_files = 0;
  207. while (auto* de = readdir(dirp)) {
  208. Entry entry;
  209. entry.name = de->d_name;
  210. if (entry.name == "." || entry.name == "..")
  211. continue;
  212. struct stat st;
  213. int rc = lstat(String::format("%s/%s", m_path.characters(), de->d_name).characters(), &st);
  214. if (rc < 0) {
  215. perror("lstat");
  216. continue;
  217. }
  218. entry.size = st.st_size;
  219. entry.mode = st.st_mode;
  220. entry.uid = st.st_uid;
  221. entry.gid = st.st_gid;
  222. entry.inode = st.st_ino;
  223. auto& entries = S_ISDIR(st.st_mode) ? m_directories : m_files;
  224. entries.append(move(entry));
  225. if (S_ISREG(entry.mode))
  226. m_bytes_in_files += st.st_size;
  227. }
  228. closedir(dirp);
  229. did_update();
  230. }
  231. void DirectoryModel::open(const String& a_path)
  232. {
  233. FileSystemPath canonical_path(a_path);
  234. auto path = canonical_path.string();
  235. if (m_path == path)
  236. return;
  237. DIR* dirp = opendir(path.characters());
  238. if (!dirp)
  239. return;
  240. closedir(dirp);
  241. m_path = path;
  242. update();
  243. set_selected_index({ 0, 0 });
  244. }
  245. void DirectoryModel::activate(const GModelIndex& index)
  246. {
  247. if (!index.is_valid())
  248. return;
  249. auto& entry = this->entry(index.row());
  250. FileSystemPath path(String::format("%s/%s", m_path.characters(), entry.name.characters()));
  251. if (entry.is_directory()) {
  252. open(path.string());
  253. return;
  254. }
  255. if (entry.is_executable()) {
  256. if (fork() == 0) {
  257. int rc = execl(path.string().characters(), path.string().characters(), nullptr);
  258. if (rc < 0)
  259. perror("exec");
  260. ASSERT_NOT_REACHED();
  261. }
  262. return;
  263. }
  264. if (path.string().ends_with(".png")) {
  265. if (fork() == 0) {
  266. int rc = execl("/bin/qs", "/bin/qs", path.string().characters(), nullptr);
  267. if (rc < 0)
  268. perror("exec");
  269. ASSERT_NOT_REACHED();
  270. }
  271. return;
  272. }
  273. if (fork() == 0) {
  274. int rc = execl("/bin/TextEditor", "/bin/TextEditor", path.string().characters(), nullptr);
  275. if (rc < 0)
  276. perror("exec");
  277. ASSERT_NOT_REACHED();
  278. }
  279. return;
  280. }