/* * Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved. */ #pragma once #include #include #include #include #include #include #include #include #include "nostalgiastudio_export.h" namespace nostalgia::studio { enum class ProjectEvent { None, FileAdded, // FileRecognized is triggered for all matching files upon a new // subscription to a section of the project and upon the addition of a file. FileRecognized, FileDeleted, FileUpdated, }; [[nodiscard]] constexpr ox::Result fileExt(const ox::String &path) noexcept { const auto extStart = ox::find(path.crbegin(), path.crend(), '.').offset(); if (!extStart) { return OxError(1, "Cannot open a file without valid extension."); } return path.substr(extStart + 1); } class NOSTALGIASTUDIO_EXPORT Project { private: ox::String m_path; mutable core::TypeStore m_typeStore; mutable ox::FileSystem *m_fs = nullptr; ox::HashMap> m_fileExtFileMap; public: explicit Project(ox::FileSystem *fs, ox::String path) noexcept; ox::Error create() noexcept; [[nodiscard]] ox::FileSystem *romFs() noexcept; ox::Error mkdir(const ox::String &path) const noexcept; /** * Writes a MetalClaw object to the project at the given path. */ template ox::Error writeObj(const ox::String &path, const T *obj, ox::ClawFormat fmt = ox::ClawFormat::Metal) noexcept; template ox::Result loadObj(const ox::String &path) const noexcept; ox::Result stat(const ox::String &path) const noexcept; [[nodiscard]] bool exists(const ox::String& path) const noexcept; template ox::Error subscribe(ProjectEvent e, ox::SignalHandler *tgt, Functor &&slot) const noexcept; [[nodiscard]] const ox::Vector &fileList(const char *ng) noexcept; private: void buildFileIndex() noexcept; void indexFile(const ox::String &path) noexcept; ox::Error writeBuff(const ox::String &path, const ox::Buffer &buff) noexcept; ox::Result loadBuff(const ox::String &path) const noexcept; ox::Error lsProcDir(ox::Vector *paths, const ox::String &path) const noexcept; ox::Result> listFiles(const ox::String &path = "") const noexcept; // signals public: ox::Signal fileEvent; ox::Signal fileAdded; // FileRecognized is triggered for all matching files upon a new // subscription to a section of the project and upon the addition of a // file. ox::Signal fileRecognized; ox::Signal fileDeleted; ox::Signal fileUpdated; }; template ox::Error Project::writeObj(const ox::String &path, const T *obj, ox::ClawFormat fmt) noexcept { // write MetalClaw oxRequireM(buff, ox::writeClaw(obj, fmt)); // write to FS oxReturnError(writeBuff(path, buff)); // write type descriptor if (m_typeStore.get().error) { oxReturnError(ox::buildTypeDef(&m_typeStore, obj)); } // write out type store static constexpr auto descPath = "/.nostalgia/type_descriptors"; oxReturnError(mkdir(descPath)); for (const auto &t : m_typeStore.typeList()) { oxRequireM(typeOut, ox::writeClaw(t, ox::ClawFormat::Organic)); // replace garbage last character with new line typeOut.back().value = '\n'; // write to FS const auto typePath = ox::sfmt("{}/{}", descPath, buildTypeId(*t)); oxReturnError(writeBuff(typePath, typeOut)); } fileUpdated.emit(path); return OxError(0); } template ox::Result Project::loadObj(const ox::String &path) const noexcept { oxRequire(buff, loadBuff(path)); if constexpr (ox::is_same_v) { return ox::readClaw(&m_typeStore, buff); } else { return ox::readClaw(buff); } } template ox::Error Project::subscribe(ProjectEvent e, ox::SignalHandler *tgt, Functor &&slot) const noexcept { switch (e) { case ProjectEvent::None: break; case ProjectEvent::FileAdded: connect(this, &Project::fileAdded, tgt, slot); break; case ProjectEvent::FileRecognized: { oxRequire(files, listFiles()); for (const auto &f : files) { slot(f); } connect(this, &Project::fileRecognized, tgt, slot); break; } case ProjectEvent::FileDeleted: connect(this, &Project::fileDeleted, tgt, slot); break; case ProjectEvent::FileUpdated: connect(this, &Project::fileUpdated, tgt, slot); break; } return OxError(0); } }