DirectoryModel.cpp 9.6 KB

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