Serenity Operating System
at portability 309 lines 9.6 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "Project.h" 28#include <AK/FileSystemPath.h> 29#include <AK/QuickSort.h> 30#include <AK/StringBuilder.h> 31#include <LibCore/File.h> 32#include <stdio.h> 33#include <string.h> 34#include <sys/stat.h> 35 36struct Project::ProjectTreeNode : public RefCounted<ProjectTreeNode> { 37 enum class Type { 38 Invalid, 39 Project, 40 Directory, 41 File, 42 }; 43 44 ProjectTreeNode& find_or_create_subdirectory(const String& name) 45 { 46 for (auto& child : children) { 47 if (child->type == Type::Directory && child->name == name) 48 return *child; 49 } 50 auto new_child = adopt(*new ProjectTreeNode); 51 new_child->type = Type::Directory; 52 new_child->name = name; 53 new_child->parent = this; 54 auto* ptr = new_child.ptr(); 55 children.append(move(new_child)); 56 return *ptr; 57 } 58 59 void sort() 60 { 61 if (type == Type::File) 62 return; 63 quick_sort(children.begin(), children.end(), [](auto& a, auto& b) { 64 return a->name < b->name; 65 }); 66 for (auto& child : children) 67 child->sort(); 68 } 69 70 Type type { Type::Invalid }; 71 String name; 72 String path; 73 Vector<NonnullRefPtr<ProjectTreeNode>> children; 74 ProjectTreeNode* parent { nullptr }; 75}; 76 77class ProjectModel final : public GUI::Model { 78public: 79 explicit ProjectModel(Project& project) 80 : m_project(project) 81 { 82 } 83 84 virtual int row_count(const GUI::ModelIndex& index) const override 85 { 86 if (!index.is_valid()) 87 return 1; 88 auto* node = static_cast<Project::ProjectTreeNode*>(index.internal_data()); 89 return node->children.size(); 90 } 91 92 virtual int column_count(const GUI::ModelIndex&) const override 93 { 94 return 1; 95 } 96 97 virtual GUI::Variant data(const GUI::ModelIndex& index, Role role = Role::Display) const override 98 { 99 auto* node = static_cast<Project::ProjectTreeNode*>(index.internal_data()); 100 if (role == Role::Display) { 101 return node->name; 102 } 103 if (role == Role::Custom) { 104 return node->path; 105 } 106 if (role == Role::Icon) { 107 if (node->type == Project::ProjectTreeNode::Type::Project) 108 return m_project.m_project_icon; 109 if (node->type == Project::ProjectTreeNode::Type::Directory) 110 return m_project.m_directory_icon; 111 if (node->name.ends_with(".cpp")) 112 return m_project.m_cplusplus_icon; 113 if (node->name.ends_with(".h")) 114 return m_project.m_header_icon; 115 return m_project.m_file_icon; 116 } 117 if (role == Role::Font) { 118 extern String g_currently_open_file; 119 if (node->name == g_currently_open_file) 120 return Gfx::Font::default_bold_font(); 121 return {}; 122 } 123 return {}; 124 } 125 126 virtual GUI::ModelIndex index(int row, int column = 0, const GUI::ModelIndex& parent = GUI::ModelIndex()) const override 127 { 128 if (!parent.is_valid()) { 129 return create_index(row, column, &m_project.root_node()); 130 } 131 auto& node = *static_cast<Project::ProjectTreeNode*>(parent.internal_data()); 132 return create_index(row, column, node.children.at(row).ptr()); 133 } 134 135 GUI::ModelIndex parent_index(const GUI::ModelIndex& index) const override 136 { 137 if (!index.is_valid()) 138 return {}; 139 auto& node = *static_cast<Project::ProjectTreeNode*>(index.internal_data()); 140 if (!node.parent) 141 return {}; 142 143 if (!node.parent->parent) { 144 return create_index(0, 0, &m_project.root_node()); 145 ASSERT_NOT_REACHED(); 146 return {}; 147 } 148 149 for (size_t row = 0; row < node.parent->parent->children.size(); ++row) { 150 if (node.parent->parent->children[row].ptr() == node.parent) 151 return create_index(row, 0, node.parent); 152 } 153 154 ASSERT_NOT_REACHED(); 155 return {}; 156 } 157 158 virtual void update() override 159 { 160 did_update(); 161 } 162 163private: 164 Project& m_project; 165}; 166 167Project::Project(const String& path, Vector<String>&& filenames) 168 : m_path(path) 169{ 170 m_name = FileSystemPath(m_path).basename(); 171 172 m_file_icon = GIcon(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-unknown.png")); 173 m_cplusplus_icon = GIcon(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-cplusplus.png")); 174 m_header_icon = GIcon(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-header.png")); 175 m_directory_icon = GIcon(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-folder.png")); 176 m_project_icon = GIcon(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-hack-studio.png")); 177 178 for (auto& filename : filenames) { 179 m_files.append(ProjectFile::construct_with_name(filename)); 180 } 181 182 m_model = adopt(*new ProjectModel(*this)); 183 184 rebuild_tree(); 185} 186 187Project::~Project() 188{ 189} 190 191OwnPtr<Project> Project::load_from_file(const String& path) 192{ 193 auto file = Core::File::construct(path); 194 if (!file->open(Core::File::ReadOnly)) 195 return nullptr; 196 197 Vector<String> files; 198 for (;;) { 199 auto line = file->read_line(1024); 200 if (line.is_null()) 201 break; 202 files.append(String::copy(line, Chomp)); 203 } 204 205 return OwnPtr(new Project(path, move(files))); 206} 207 208bool Project::add_file(const String& filename) 209{ 210 m_files.append(ProjectFile::construct_with_name(filename)); 211 rebuild_tree(); 212 m_model->update(); 213 return save(); 214} 215 216bool Project::remove_file(const String& filename) 217{ 218 if (!get_file(filename)) 219 return false; 220 m_files.remove_first_matching([filename](auto& file) { return file->name() == filename; }); 221 rebuild_tree(); 222 m_model->update(); 223 return save(); 224} 225 226bool Project::save() 227{ 228 auto project_file = Core::File::construct(m_path); 229 if (!project_file->open(Core::File::WriteOnly)) 230 return false; 231 232 for (auto& file : m_files) { 233 // FIXME: Check for error here. IODevice::printf() needs some work on error reporting. 234 project_file->printf("%s\n", file.name().characters()); 235 } 236 237 if (!project_file->close()) 238 return false; 239 240 return true; 241} 242 243ProjectFile* Project::get_file(const String& filename) 244{ 245 for (auto& file : m_files) { 246 if (FileSystemPath(file.name()).string() == FileSystemPath(filename).string()) 247 return &file; 248 } 249 return nullptr; 250} 251 252void Project::rebuild_tree() 253{ 254 auto root = adopt(*new ProjectTreeNode); 255 root->name = m_name; 256 root->type = ProjectTreeNode::Type::Project; 257 258 for (auto& file : m_files) { 259 FileSystemPath path(file.name()); 260 ProjectTreeNode* current = root.ptr(); 261 StringBuilder partial_path; 262 263 for (size_t i = 0; i < path.parts().size(); ++i) { 264 auto& part = path.parts().at(i); 265 if (part == ".") 266 continue; 267 if (i != path.parts().size() - 1) { 268 current = &current->find_or_create_subdirectory(part); 269 continue; 270 } 271 struct stat st; 272 if (lstat(path.string().characters(), &st) < 0) 273 continue; 274 275 if (S_ISDIR(st.st_mode)) { 276 current = &current->find_or_create_subdirectory(part); 277 continue; 278 } 279 auto file_node = adopt(*new ProjectTreeNode); 280 file_node->name = part; 281 file_node->path = path.string(); 282 file_node->type = Project::ProjectTreeNode::Type::File; 283 file_node->parent = current; 284 current->children.append(move(file_node)); 285 break; 286 } 287 } 288 289 root->sort(); 290 291#if 0 292 Function<void(ProjectTreeNode&, int indent)> dump_tree = [&](ProjectTreeNode& node, int indent) { 293 for (int i = 0; i < indent; ++i) 294 printf(" "); 295 if (node.name.is_null()) 296 printf("(null)\n"); 297 else 298 printf("%s\n", node.name.characters()); 299 for (auto& child : node.children) { 300 dump_tree(*child, indent + 2); 301 } 302 }; 303 304 dump_tree(*root, 0); 305#endif 306 307 m_root_node = move(root); 308 m_model->update(); 309}