diff options
Diffstat (limited to 'core/src/Model')
-rw-r--r-- | core/src/Model/GlobalStates.cpp | 17 | ||||
-rw-r--r-- | core/src/Model/GlobalStates.hpp | 5 | ||||
-rw-r--r-- | core/src/Model/Items.cpp | 88 | ||||
-rw-r--r-- | core/src/Model/Items.hpp | 209 | ||||
-rw-r--r-- | core/src/Model/Project.cpp | 66 | ||||
-rw-r--r-- | core/src/Model/Project.hpp | 6 | ||||
-rw-r--r-- | core/src/Model/fwd.hpp | 1 |
7 files changed, 258 insertions, 134 deletions
diff --git a/core/src/Model/GlobalStates.cpp b/core/src/Model/GlobalStates.cpp index cd076f4..0c4e58e 100644 --- a/core/src/Model/GlobalStates.cpp +++ b/core/src/Model/GlobalStates.cpp @@ -17,8 +17,12 @@ static std::unique_ptr<GlobalStates> globalStateInstance; static fs::path globalDataPath; void GlobalStates::Init() { + Init(StandardDirectories::UserData() / "cplt"); +} + +void GlobalStates::Init(std::filesystem::path userDataDir) { globalStateInstance = std::make_unique<GlobalStates>(); - globalDataPath = StandardDirectories::UserData() / "cplt"; + globalDataPath = userDataDir; fs::create_directories(globalDataPath); // Reading recent projects @@ -79,6 +83,17 @@ void GlobalStates::AddRecentProject(const Project& project) { MarkDirty(); } +void GlobalStates::MoveProjectToTop(const Project& project) { + for (auto it = mRecentProjects.begin(); it != mRecentProjects.end(); ++it) { + if (it->path == project.GetPath()) { + std::rotate(it, it + 1, mRecentProjects.end()); + MarkDirty(); + return; + } + } + AddRecentProject(project); +} + void GlobalStates::RemoveRecentProject(int idx) { assert(idx >= 0 && idx < mRecentProjects.size()); diff --git a/core/src/Model/GlobalStates.hpp b/core/src/Model/GlobalStates.hpp index e6d823b..8375569 100644 --- a/core/src/Model/GlobalStates.hpp +++ b/core/src/Model/GlobalStates.hpp @@ -10,6 +10,7 @@ class GlobalStates { public: static void Init(); + static void Init(std::filesystem::path userDataDir); static void Shutdown(); static GlobalStates& GetInstance(); @@ -31,6 +32,10 @@ public: const std::vector<RecentProject>& GetRecentProjects() const; void ClearRecentProjects(); void AddRecentProject(const Project& project); + /// Move or add the project to end of the recent projects list. + /// If the project is not in the list of recently used projects, it will be appended, otherwise + /// it will be moved to the end. + void MoveProjectToTop(const Project& project); void RemoveRecentProject(int idx); // TODO async autosaving to prevent data loss on crash diff --git a/core/src/Model/Items.cpp b/core/src/Model/Items.cpp index db2d39f..7679eb9 100644 --- a/core/src/Model/Items.cpp +++ b/core/src/Model/Items.cpp @@ -1,37 +1,5 @@ #include "Items.hpp" -#include <limits> -#include <utility> - -ItemBase::ItemBase() - : mId{ std::numeric_limits<size_t>::max() } { -} - -ItemBase::ItemBase(size_t id) - : mId{ id } { -} - -bool ItemBase::IsInvalid() const { - return mId == std::numeric_limits<size_t>::max(); -} - -size_t ItemBase::GetId() const { - return mId; -} - -ProductItem::ProductItem(size_t id, std::string name) - : ItemBase(id) - , mName{ std::move(name) } { -} - -const std::string& ProductItem::GetName() const { - return mName; -} - -void ProductItem::SetName(std::string name) { - mName = std::move(name); -} - const std::string& ProductItem::GetDescription() const { return mDescription; } @@ -40,17 +8,14 @@ void ProductItem::SetDescription(std::string description) { mDescription = std::move(description); } -FactoryItem::FactoryItem(size_t id, std::string name) - : ItemBase(id) - , mName{ std::move(name) } { -} - -const std::string& FactoryItem::GetName() const { - return mName; +Json::Value ProductItem::Serialize() const { + Json::Value elm; + elm["Description"] = mDescription; + return elm; } -void FactoryItem::SetName(std::string name) { - mName = std::move(name); +void ProductItem::Deserialize(const Json::Value& elm) { + mDescription = elm["Description"].asString(); } const std::string& FactoryItem::GetDescription() const { @@ -61,17 +26,24 @@ void FactoryItem::SetDescription(std::string description) { mDescription = std::move(description); } -CustomerItem::CustomerItem(size_t id, std::string name) - : ItemBase(id) - , mName{ std::move(name) } { +const std::string& FactoryItem::GetEmail() const { + return mEmail; } -const std::string& CustomerItem::GetName() const { - return mName; +void FactoryItem::SetEmail(std::string email) { + mEmail = std::move(email); } -void CustomerItem::SetName(std::string name) { - mName = std::move(name); +Json::Value FactoryItem::Serialize() const { + Json::Value elm; + elm["Description"] = mDescription; + elm["Email"] = mEmail; + return elm; +} + +void FactoryItem::Deserialize(const Json::Value& elm) { + mDescription = elm["Description"].asString(); + mEmail = elm["Email"].asString(); } const std::string& CustomerItem::GetDescription() const { @@ -81,3 +53,23 @@ const std::string& CustomerItem::GetDescription() const { void CustomerItem::SetDescription(std::string description) { mDescription = std::move(description); } + +const std::string& CustomerItem::GetEmail() const { + return mEmail; +} + +void CustomerItem::SetEmail(std::string email) { + mEmail = std::move(email); +} + +Json::Value CustomerItem::Serialize() const { + Json::Value elm; + elm["Description"] = mDescription; + elm["Email"] = mEmail; + return elm; +} + +void CustomerItem::Deserialize(const Json::Value& elm) { + mDescription = elm["Description"].asString(); + mEmail = elm["Email"].asString(); +} diff --git a/core/src/Model/Items.hpp b/core/src/Model/Items.hpp index 0c7be41..e20a290 100644 --- a/core/src/Model/Items.hpp +++ b/core/src/Model/Items.hpp @@ -1,54 +1,21 @@ #pragma once +#include "cplt_fwd.hpp" + +#include <json/reader.h> +#include <json/value.h> +#include <json/writer.h> #include <tsl/array_map.h> #include <cstddef> +#include <limits> #include <stdexcept> #include <string> #include <string_view> +#include <utility> #include <vector> -/// Pointers and references returned by accessors are valid as long as no non-const functions have been called. template <class T> class ItemList { -public: - class Iterator { - private: - typename std::vector<T>::const_iterator mBackingIter; - - public: - Iterator(typename std::vector<T>::const_iterator it) - : mBackingIter{ it } { - } - - Iterator& operator++() { - ++mBackingIter; - return *this; - } - - Iterator& operator++(int) { - auto tmp = *this; - ++mBackingIter; - return tmp; - } - - Iterator& operator--() { - --mBackingIter; - return *this; - } - - Iterator& operator--(int) { - auto tmp = *this; - --mBackingIter; - return tmp; - } - - const T& operator*() const { - return *mBackingIter; - } - - friend bool operator==(const Iterator&, const Iterator&) = default; - }; - private: std::vector<T> mStorage; tsl::array_map<char, size_t> mNameLookup; @@ -61,9 +28,28 @@ public: throw std::runtime_error("Duplicate key."); } + for (size_t i = 0; i < mStorage.size(); ++i) { + if (mStorage[i].IsInvalid()) { + mStorage[i] = T(*this, i, std::move(name), std::forward<Args>(args)...); + mNameLookup.insert(name, i); + return mStorage[i]; + } + } + size_t id = mStorage.size(); mNameLookup.insert(name, id); - return mStorage.emplace_back(id, std::move(name), std::forward<Args>(args)...); + mStorage.emplace_back(*this, id, std::move(name), std::forward<Args>(args)...); + return mStorage[id]; + } + + void Remove(size_t index) { + auto& item = mStorage[index]; + mNameLookup.erase(item.GetName()); + mStorage[index] = T(*this); + } + + T* Find(size_t id) { + return &mStorage[id]; } const T* Find(size_t id) const { @@ -79,68 +65,157 @@ public: } } - Iterator begin() const { - return Iterator(mStorage.begin()); + Json::Value Serialize() const { + Json::Value items(Json::arrayValue); + for (auto& item : mStorage) { + if (!item.IsInvalid()) { + auto elm = item.Serialize(); + elm["Id"] = item.GetId(); + elm["Name"] = item.GetName(); + items.append(elm); + } + } + + Json::Value root; + root["MaxItemId"] = mStorage.size(); + root["Items"] = std::move(items); + + return root; } - Iterator end() const { - return Iterator(mStorage.end()); + ItemList() = default; + + ItemList(const Json::Value& root) { + constexpr const char* kMessage = "Failed to load item list from JSON."; + + auto& itemCount = root["MaxItemId"]; + if (!itemCount.isIntegral()) throw std::runtime_error(kMessage); + + mStorage.resize(itemCount.asInt64(), T(*this)); + + auto& items = root["Items"]; + if (!items.isArray()) throw std::runtime_error(kMessage); + + for (auto& elm : items) { + if (!elm.isObject()) throw std::runtime_error(kMessage); + + auto& id = elm["Id"]; + if (!id.isIntegral()) throw std::runtime_error(kMessage); + auto& name = elm["Name"]; + if (!name.isString()) throw std::runtime_error(kMessage); + + size_t iid = id.asInt64(); + mStorage[iid] = T(*this, iid, name.asString()); + mStorage[iid].Deserialize(elm); + } + } + + typename decltype(mStorage)::iterator begin() { + return mStorage.begin(); + } + + typename decltype(mStorage)::iterator end() { + return mStorage.end(); + } + + typename decltype(mStorage)::const_iterator begin() const { + return mStorage.begin(); + } + + typename decltype(mStorage)::const_iterator end() const { + return mStorage.end(); + } + +private: + template <class TSelf> + friend class ItemBase; + + void UpdateItemName(const T& item, const std::string& newName) { + mNameLookup.erase(item.GetName()); + mNameLookup.insert(newName, item.GetId()); } }; +template <class TSelf> class ItemBase { private: + ItemList<TSelf>* mList; size_t mId; + std::string mName; public: - ItemBase(); - ItemBase(size_t id); + ItemBase(ItemList<TSelf>& list, size_t id = std::numeric_limits<size_t>::max(), std::string name = "") + : mList{ &list } + , mId{ id } + , mName{ std::move(name) } { + } - bool IsInvalid() const; - size_t GetId() const; + bool IsInvalid() const { + return mId == std::numeric_limits<size_t>::max(); + } + + ItemList<TSelf>& GetList() const { + return *mList; + } + + size_t GetId() const { + return mId; + } + + const std::string& GetName() const { + return mName; + } + + void SetName(std::string name) { + mList->UpdateItemName(static_cast<TSelf&>(*this), name); + mName = std::move(name); + } }; -class ProductItem : public ItemBase { +class ProductItem : public ItemBase<ProductItem> { private: - std::string mName; std::string mDescription; public: - ProductItem() {} - ProductItem(size_t id, std::string name); + using ItemBase::ItemBase; - const std::string& GetName() const; - void SetName(std::string mName); const std::string& GetDescription() const; void SetDescription(std::string description); + + Json::Value Serialize() const; + void Deserialize(const Json::Value& elm); }; -class FactoryItem : public ItemBase { +class FactoryItem : public ItemBase<FactoryItem> { private: - std::string mName; std::string mDescription; + std::string mEmail; public: - FactoryItem() {} - FactoryItem(size_t id, std::string name); + using ItemBase::ItemBase; - const std::string& GetName() const; - void SetName(std::string name); const std::string& GetDescription() const; void SetDescription(std::string description); + const std::string& GetEmail() const; + void SetEmail(std::string email); + + Json::Value Serialize() const; + void Deserialize(const Json::Value& elm); }; -class CustomerItem : public ItemBase { +class CustomerItem : public ItemBase<CustomerItem> { private: - std::string mName; std::string mDescription; + std::string mEmail; public: - CustomerItem() {} - CustomerItem(size_t id, std::string name); + using ItemBase::ItemBase; - const std::string& GetName() const; - void SetName(std::string name); const std::string& GetDescription() const; void SetDescription(std::string description); + const std::string& GetEmail() const; + void SetEmail(std::string email); + + Json::Value Serialize() const; + void Deserialize(const Json::Value& elm); }; diff --git a/core/src/Model/Project.cpp b/core/src/Model/Project.cpp index f070940..cdb88c6 100644 --- a/core/src/Model/Project.cpp +++ b/core/src/Model/Project.cpp @@ -10,40 +10,62 @@ namespace fs = std::filesystem; -Project Project::Load(const fs::path& path) { +template <class T> +void ReadItemList(ItemList<T>& list, const fs::path& filePath) { + std::ifstream ifs(filePath); + if (ifs) { + Json::Value root; + ifs >> root; + + list = ItemList<T>(root); + } +} + +Project Project::Load(const fs::path& projectFilePath) { // TODO better diagnostic const char* kInvalidFormatErr = "Failed to load project: invalid format."; - std::ifstream ifs(path); + std::ifstream ifs(projectFilePath); if (!ifs) { std::string message; message += "Failed to load project file at '"; - message += path.string(); + message += projectFilePath.string(); message += "'."; throw std::runtime_error(message); } Project proj; - proj.mRootPath = path.parent_path(); + proj.mRootPath = projectFilePath.parent_path(); proj.mRootPathString = proj.mRootPath.string(); - Json::Value root; - ifs >> root; + { + 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 silently creating new elements - if (!croot.isObject()) { - throw std::runtime_error(kInvalidFormatErr); - } + const auto& croot = root; // Use const reference so that accessors default to returning a null if not found, instead of silently 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); + if (auto& name = croot["Name"]; name.isString()) { + proj.mName = name.asString(); + } else { + throw std::runtime_error(kInvalidFormatErr); + } } + auto itemsDir = proj.mRootPath / "items"; + ReadItemList(proj.Products, itemsDir / "products.json"); + ReadItemList(proj.Factories, itemsDir / "factories.json"); + ReadItemList(proj.Customers, itemsDir / "customers.json"); + return proj; } +Project Project::LoadDir(const std::filesystem::path& projectPath) { + return Load(projectPath / "cplt_project.json"); +} + Project Project::Create(std::string name, const fs::path& path) { Project proj; proj.mRootPath = path; @@ -76,8 +98,20 @@ Json::Value Project::Serialize() { return root; } +template <class T> +static void WriteItemList(ItemList<T>& list, const fs::path& filePath) { + std::ofstream ofs(filePath); + ofs << list.Serialize(); +} + void Project::WriteToDisk() { - auto root = Serialize(); std::ofstream ofs(mRootPath / "cplt_project.json"); - ofs << root; + ofs << this->Serialize(); + + auto itemsDir = mRootPath / "items"; + fs::create_directories(itemsDir); + + WriteItemList(Products, itemsDir / "products.json"); + WriteItemList(Factories, itemsDir / "factories.json"); + WriteItemList(Customers, itemsDir / "customers.json"); } diff --git a/core/src/Model/Project.hpp b/core/src/Model/Project.hpp index 23eafc1..280eaf3 100644 --- a/core/src/Model/Project.hpp +++ b/core/src/Model/Project.hpp @@ -19,12 +19,14 @@ private: public: /// Load the project from a cplt_project.json file. - static Project Load(const std::filesystem::path& path); + static Project Load(const std::filesystem::path& projectFilePath); + /// Load the project from the directory containing the cplt_project.json file. + static Project LoadDir(const std::filesystem::path& projectPath); /// 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. + /// Path to a *directory* that contains the project file. const std::filesystem::path& GetPath() const; const std::string& GetPathString() const; diff --git a/core/src/Model/fwd.hpp b/core/src/Model/fwd.hpp index bf9a8cf..146f74a 100644 --- a/core/src/Model/fwd.hpp +++ b/core/src/Model/fwd.hpp @@ -6,6 +6,7 @@ class GlobalStates; // Items.hpp template <class T> class ItemList; +template <class TSelf> class ItemBase; class ProductItem; class FactoryItem; |