diff options
Diffstat (limited to 'core/src/Model')
-rw-r--r-- | core/src/Model/GlobalStates.cpp | 110 | ||||
-rw-r--r-- | core/src/Model/GlobalStates.hpp | 43 | ||||
-rw-r--r-- | core/src/Model/Project.cpp | 50 | ||||
-rw-r--r-- | core/src/Model/Project.hpp | 9 | ||||
-rw-r--r-- | core/src/Model/fwd.hpp | 3 |
5 files changed, 214 insertions, 1 deletions
diff --git a/core/src/Model/GlobalStates.cpp b/core/src/Model/GlobalStates.cpp new file mode 100644 index 0000000..cd076f4 --- /dev/null +++ b/core/src/Model/GlobalStates.cpp @@ -0,0 +1,110 @@ +#include "GlobalStates.hpp" + +#include "Model/Project.hpp" +#include "Utils/StandardDirectories.hpp" + +#include <json/reader.h> +#include <json/value.h> +#include <json/writer.h> +#include <cassert> +#include <filesystem> +#include <fstream> +#include <memory> + +namespace fs = std::filesystem; + +static std::unique_ptr<GlobalStates> globalStateInstance; +static fs::path globalDataPath; + +void GlobalStates::Init() { + globalStateInstance = std::make_unique<GlobalStates>(); + globalDataPath = StandardDirectories::UserData() / "cplt"; + fs::create_directories(globalDataPath); + + // Reading recent projects + [&]() { + std::ifstream ifs(globalDataPath / "recents.json"); + if (!ifs) return; + + Json::Value root; + ifs >> root; + + if (!root.isObject()) return; + if (auto& recents = root["RecentProjects"]; recents.isArray()) { + for (auto& elm : recents) { + if (!elm.isString()) continue; + + fs::path path(elm.asCString()); + if (!fs::exists(path)) continue; + + auto utf8String = path.string(); + globalStateInstance->mRecentProjects.push_back(RecentProject{ + .path = std::move(path), + .cachedUtf8String = std::move(utf8String), + }); + } + } + }(); +} + +void GlobalStates::Shutdown() { + if (!globalStateInstance) return; + if (globalStateInstance->mDirty) { + globalStateInstance->WriteToDisk(); + } +} + +GlobalStates& GlobalStates::GetInstance() { + return *globalStateInstance; +} + +const std::filesystem::path& GlobalStates::GetUserDataPath() { + return globalDataPath; +} + +const std::vector<GlobalStates::RecentProject>& GlobalStates::GetRecentProjects() const { + return mRecentProjects; +} + +void GlobalStates::ClearRecentProjects() { + mRecentProjects.clear(); + MarkDirty(); +} + +void GlobalStates::AddRecentProject(const Project& project) { + mRecentProjects.push_back(RecentProject{ + .path = project.GetPath(), + .cachedUtf8String = project.GetPath().string(), + }); + MarkDirty(); +} + +void GlobalStates::RemoveRecentProject(int idx) { + assert(idx >= 0 && idx < mRecentProjects.size()); + + mRecentProjects.erase(mRecentProjects.begin() + idx); + MarkDirty(); +} + +void GlobalStates::WriteToDisk() const { + Json::Value root; + + auto& recentProjects = root["RecentProjects"] = Json::Value(Json::arrayValue); + for (auto& [path, _] : mRecentProjects) { + recentProjects.append(Json::Value(path.string())); + } + + std::ofstream ofs(globalDataPath / "recents.json"); + ofs << root; + + mDirty = false; +} + +bool GlobalStates::IsDirty() const { + return mDirty; +} + +void GlobalStates::MarkDirty() { + mDirty = true; + OnModified(); +} diff --git a/core/src/Model/GlobalStates.hpp b/core/src/Model/GlobalStates.hpp new file mode 100644 index 0000000..e6d823b --- /dev/null +++ b/core/src/Model/GlobalStates.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "Utils/Sigslot.hpp" +#include "cplt_fwd.hpp" + +#include <filesystem> +#include <string> +#include <vector> + +class GlobalStates { +public: + static void Init(); + static void Shutdown(); + + static GlobalStates& GetInstance(); + static const std::filesystem::path& GetUserDataPath(); + + struct RecentProject { + std::filesystem::path path; + std::string cachedUtf8String; + }; + +public: + Signal<> OnModified; + +private: + std::vector<RecentProject> mRecentProjects; + mutable bool mDirty = false; + +public: + const std::vector<RecentProject>& GetRecentProjects() const; + void ClearRecentProjects(); + void AddRecentProject(const Project& project); + void RemoveRecentProject(int idx); + + // TODO async autosaving to prevent data loss on crash + void WriteToDisk() const; + + bool IsDirty() const; + +private: + void MarkDirty(); +}; diff --git a/core/src/Model/Project.cpp b/core/src/Model/Project.cpp index f301bb8..c54a02c 100644 --- a/core/src/Model/Project.cpp +++ b/core/src/Model/Project.cpp @@ -1,8 +1,56 @@ #include "Project.hpp" +#include <json/reader.h> +#include <json/value.h> +#include <json/writer.h> +#include <filesystem> +#include <fstream> +#include <stdexcept> #include <utility> -const std::filesystem::path& Project::GetPath() const { +namespace fs = std::filesystem; + +Project Project::Load(const fs::path& path) { + // TODO better diagnostic + const char* kInvalidFormatErr = "Failed to load project: invalid format."; + + std::ifstream ifs(path); + if (!ifs) { + std::string message; + message += "Failed to load project file at '"; + message += path.string(); + message += "'."; + throw std::runtime_error(message); + } + + Project proj; + proj.mRootPath = path.parent_path(); + + Json::Value root; + ifs >> root; + + const auto& croot = root; // Use const reference so that accessors default to returning a null if not found, instead of siliently creating new elements + if (!croot.isObject()) { + throw std::runtime_error(kInvalidFormatErr); + } + + if (auto& name = croot["Name"]; name.isString()) { + proj.mName = name.asString(); + } else { + throw std::runtime_error(kInvalidFormatErr); + } + + return proj; +} + +Project Project::Create(std::string name, const fs::path& path) { + Project proj; + proj.mRootPath = path; + proj.mName = std::move(name); + return proj; +} + +const fs::path& Project::GetPath() const { return mRootPath; } diff --git a/core/src/Model/Project.hpp b/core/src/Model/Project.hpp index 34ae1d7..7b5c7e3 100644 --- a/core/src/Model/Project.hpp +++ b/core/src/Model/Project.hpp @@ -9,9 +9,18 @@ public: std::string mName; public: + /// Load the project from a cplt_project.json file. + static Project Load(const std::filesystem::path& path); + /// Create a project with the given name in the given path. Note that the path should be a directory that will contain the project files once created. + /// This function assumes the given directory will exist and is empty. + static Project Create(std::string name, const std::filesystem::path& path); + // Path to a *directory* that contains the project file. const std::filesystem::path& GetPath() const; const std::string& GetName() const; void SetName(std::string name); + +private: + Project() = default; }; diff --git a/core/src/Model/fwd.hpp b/core/src/Model/fwd.hpp index f5a5818..6bbc0b7 100644 --- a/core/src/Model/fwd.hpp +++ b/core/src/Model/fwd.hpp @@ -1,4 +1,7 @@ #pragma once +// GlobalStates.hpp +class GlobalStates; + // Project.hpp class Project; |