#include #include #include #include #include #include using namespace std::literals; namespace fs = std::filesystem; namespace FxOS { //=== Project ===// Project::Project() { m_name = "unnamed_project"; } void Project::setName(std::string const &new_name) { m_name = new_name; setDirty(); } void Project::setPath(std::string const &new_path) { m_path = new_path; } bool Project::canSave() const { return m_path != ""; } bool Project::isDirty() const { return m_dirty; } void Project::setDirty() { m_dirty = true; } std::string Project::generateBinaryName(std::string const &base) const { if(base != "" && !m_binaries.count(base)) return base; std::string const &prefix = (base == "") ? "unnamed" : base; int counter = 0; while(true) { std::string name = fmt::format("{}_{}", prefix, counter); if(!m_binaries.count(name)) return name; counter++; } } std::string Project::createBinary(std::string const &name) { std::string trueName = generateBinaryName(name); /* Create a new default-constructed entry in the map. */ m_binaries[trueName]; setDirty(); return trueName; } Binary *Project::getBinary(std::string const &name) { auto it = m_binaries.find(name); return (it == m_binaries.end()) ? nullptr : &it->second; } Binary const *Project::getBinary(std::string const &name) const { auto it = m_binaries.find(name); return (it == m_binaries.end()) ? nullptr : &it->second; } void Project::removeBinary(std::string const &name) { if(m_binaries.erase(name)) setDirty(); } BSON Project::serializeMetadata() const { BSON binaries = BSON::mkArray(m_binaries.size()); int i = 0; for(auto const &[name, _]: m_binaries) { binaries[i] = BSON::mkString(name); i++; } return BSON::mkDocument({ {"*", BSON::mkString("Project")}, {"name", BSON::mkString(m_name)}, {"binaries", std::move(binaries)}, }); } bool Project::save() { fs::path path(m_path); if(path == "") { FxOS_log(ERR, "Project ā€œ%sā€ has no path", m_name.c_str()); return false; } std::error_code ec; fs::create_directories(path, ec); if(ec) { FxOS_log(ERR, "Could not create path to '%s': %s", path.c_str(), ec.message()); return false; } fs::path metadata_path = path / "project"; FILE *metadata = fopen(metadata_path.c_str(), "w"); if(!metadata) { FxOS_log(ERR, "cannot write '%s': %m", metadata_path.c_str()); return false; } serializeMetadata().serialize(metadata); fclose(metadata); /* Remove old binaries' folders. */ for(auto const &entry: fs::directory_iterator(path)) { fs::path const &path = entry.path(); if(path.extension() == ".bin" && m_binaries.count(path.stem()) == 0) fs::remove(path); } for(auto const &[name, binary]: m_binaries) { fs::path bin_path = path / (name + ".bin"); FILE *bin = fopen(bin_path.c_str(), "w"); if(!bin) { FxOS_log(ERR, "cannot write '%s': %m", bin_path.c_str()); continue; } binary.serialize().serialize(bin); fclose(bin); } m_dirty = false; return true; } bool Project::load(std::string const &path0) { fs::path path = path0; BSON metadata = BSON::loadDocumentFromFile(path / "project", true, true, "Project"); if(metadata.isNull()) return false; m_path = path; m_name = metadata["name"].getString(); m_dirty = false; if(!metadata.hasField("binaries")) return true; for(size_t i = 0; i < metadata["binaries"].size(); i++) { std::string name = metadata["binaries"][i].getString(); BSON bin = BSON::loadDocumentFromFile( path / (name + ".bin"), true, true, "Binary"); if(bin.isNull()) continue; assert(m_binaries.count(name) == 0); m_binaries[name]; m_binaries[name].deserialize(bin); } return true; } //=== RecentProjects ===// RecentProjects::RecentProjectEntry::RecentProjectEntry(BSON const &b) { assert(b["*"].getStringReadOnly() == "RecentProjectEntry"s); name = b["name"].getStringReadOnly(); path = b["path"].getStringReadOnly(); utime = b["utime"].getI64(); } BSON RecentProjects::RecentProjectEntry::serialize() const { return BSON::mkDocument({ {"*", BSON::mkString("RecentProjectEntry")}, {"name", BSON::mkString(name)}, {"path", BSON::mkString(path)}, {"utime", BSON::mkI64(utime)}, }); } BSON RecentProjects::serialize() const { BSON b = BSON::mkArray(m_entries.size()); for(uint i = 0; i < m_entries.size(); i++) b[i] = m_entries[i].serialize(); return b; } void RecentProjects::deserialize(BSON const &b) { assert(b.isArray()); for(uint i = 0; i < b.size(); i++) m_entries.push_back(RecentProjectEntry(b[i])); } std::string RecentProjects::pathFromName(std::string const &name) const { for(auto &entry: m_entries) { if(entry.name == name) return entry.path; } return ""; } std::string RecentProjects::mostRecentPath() const { if(!m_entries.size()) return ""; return std::max_element(m_entries.begin(), m_entries.end(), [](RecentProjectEntry const &e1, RecentProjectEntry const &e2) { return e1.utime < e2.utime; }) ->path; } void RecentProjects::remove(std::string const &path) { auto it = m_entries.begin(); while(it != m_entries.end()) { if(it->path == path) it = m_entries.erase(it); else ++it; } } void RecentProjects::touch(Project const &p) { remove(p.path()); struct RecentProjectEntry entry; entry.name = p.name(); entry.path = p.path(); entry.utime = time(NULL); m_entries.insert(m_entries.begin(), entry); } void RecentProjects::refresh() { auto it = std::remove_if( m_entries.begin(), m_entries.end(), [](RecentProjectEntry const &e) { std::error_code rc; fs::file_status st = fs::status(e.path, rc); return rc || st.type() != fs::file_type::directory; }); m_entries.erase(it, m_entries.end()); } } /* namespace FxOS */