1 #pragma once
2 
3 #include <rapidjson/pointer.h>
4 
5 #include <algorithm>
6 #include <cinttypes>
7 #include <map>
8 #include <memory>
9 #include <mutex>
10 #include <pajlada/settings/common.hpp>
11 #include <pajlada/settings/detail/fs.hpp>
12 #include <pajlada/settings/signalargs.hpp>
13 #include <vector>
14 
15 namespace pajlada {
16 namespace Settings {
17 
18 class SettingData;
19 
20 class SettingManager
21 {
22 public:
23     SettingManager();
24     ~SettingManager();
25 
26     enum class LoadError {
27         NoError,
28         CannotOpenFile,
29         FileHandleError,
30         FileReadError,
31         FileSeekError,
32         JSONParseError,
33     };
34 
35     // Print given document json data prettily
36     void pp(const std::string &prefix = std::string());
37     static void gPP(const std::string &prefix = std::string());
38     static std::string stringify(const rapidjson::Value &v);
39 
40     rapidjson::Value *get(const char *path);
41     bool set(const char *path, const rapidjson::Value &value,
42              SignalArgs args = SignalArgs());
43 
44 private:
45     // Called from set
46     void notifyUpdate(const std::string &path, const rapidjson::Value &value,
47                       SignalArgs args = SignalArgs());
48 
49     // Called from load
50     void notifyLoadedValues();
51 
52 public:
53     // Useful array helper methods
54     static rapidjson::SizeType arraySize(const std::string &path);
55     static bool isNull(const std::string &path);
56     bool _isNull(const std::string &path);
57     static void setNull(const std::string &path);
58 
59     // Basically the same as setNull, except we fully remove a value if it's the
60     // last index of the array
61     static bool removeArrayValue(const std::string &arrayPath,
62                                  rapidjson::SizeType index);
63 
64     static rapidjson::SizeType cleanArray(const std::string &arrayPath);
65 
66     // Useful object helper methods
67     static std::vector<std::string> getObjectKeys(
68         const std::string &objectPath);
69 
70     static void clear();
71 
72     static std::weak_ptr<SettingData> getSetting(
73         const std::string &path, std::shared_ptr<SettingManager> instance);
74 
75     static bool removeSetting(const std::string &path);
76 
77 private:
78     template <typename Type>
79     friend class Setting;
80 
81     bool _removeSetting(const std::string &path);
82 
83     void clearSettings(const std::string &root);
84 
85 public:
86     void setPath(const fs::path &newPath);
87 
88     static LoadError gLoad(const fs::path &path = fs::path());
89     static LoadError gLoadFrom(const fs::path &path);
90 
91     // Load from given path and set given path as the "default path" (or load
92     // from default path if nullptr is sent)
93     LoadError load(const fs::path &path = fs::path());
94     // Load from given path
95     LoadError loadFrom(const fs::path &path);
96 
97     static bool gSave(const fs::path &path = fs::path());
98     static bool gSaveAs(const fs::path &path);
99 
100     // Force a settings save
101     // It is recommended to run this every now and then unless your application
102     // is crash free
103     // Save to given path and set path as the default path (or save from default
104     // path if filePath is a nullptr)
105     bool save(const fs::path &path = fs::path());
106     // Save to given path
107     bool saveAs(const fs::path &path);
108 
109 private:
110     bool writeTo(const fs::path &path);
111 
112 public:
113     // Functions prefixed with g are static functions that work
114     // on the statically initialized SettingManager instance
115 
116     enum class SaveMethod : uint64_t {
117         SaveOnExit = (1ull << 1ull),
118         SaveOnSettingChange = (1ull << 2ull),
119 
120         // Force user to manually call SettingsManager::save() to save
121         SaveManually = 0,
122         SaveAllTheTime = SaveOnExit | SaveOnSettingChange,
123     } saveMethod = SaveMethod::SaveOnExit;
124 
125 private:
126     // Returns true if the given save method is activated
127     inline bool
hasSaveMethodFlag(SettingManager::SaveMethod testSaveMethod) const128     hasSaveMethodFlag(SettingManager::SaveMethod testSaveMethod) const
129     {
130         return (static_cast<uint64_t>(this->saveMethod) &
131                 static_cast<uint64_t>(testSaveMethod)) != 0;
132     }
133 
134     struct {
135         bool enabled{};
136         uint8_t numSlots = 3;
137     } backup;
138 
139 public:
140     void setBackupEnabled(bool enabled = true);
141     void setBackupSlots(uint8_t numSlots);
142 
143     static const std::shared_ptr<SettingManager> &
getInstance()144     getInstance()
145     {
146         static auto m = std::make_shared<SettingManager>();
147 
148         return m;
149     }
150 
151 private:
152     std::shared_ptr<SettingData> getSetting(const std::string &path);
153 
154 public:
155     rapidjson::Document document;
156 
157 private:
158     fs::path filePath = "settings.json";
159 
160     std::mutex settingsMutex;
161 
162     //       path         setting
163     std::map<std::string, std::shared_ptr<SettingData>> settings;
164 };
165 
166 }  // namespace Settings
167 }  // namespace pajlada
168