HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
preferences.hpp
1// Copyright Take Vos 2020-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "../telemetry/telemetry.hpp"
8#include "../observer/observer.hpp"
9#include "../codec/codec.hpp"
10#include "../dispatch/dispatch.hpp"
11#include "../file/file.hpp"
12#include "../macros.hpp"
13#include <typeinfo>
14#include <filesystem>
15
16hi_export_module(hikogui.settings.preferences);
17
18hi_export namespace hi::inline v1 {
19class preferences;
20
21namespace detail {
22
24public:
25 preference_item_base(preferences& parent, std::string_view path) noexcept : _parent(parent), _path(path) {}
26
29 preference_item_base& operator=(preference_item_base const&) = delete;
30 preference_item_base& operator=(preference_item_base&&) = delete;
31 virtual ~preference_item_base() = default;
32
35 virtual void reset() noexcept = 0;
36
39 void load() noexcept;
40
41protected:
42 preferences& _parent;
43 jsonpath _path;
44
49 [[nodiscard]] virtual datum encode() const noexcept = 0;
50
51 virtual void decode(datum const& data) = 0;
52};
53
54template<typename T>
56public:
57 preference_item(preferences& parent, std::string_view path, observer<T> const& value, T init) noexcept :
58 preference_item_base(parent, path), _value(value), _init(std::move(init))
59 {
60 _value_cbt = _value.subscribe(
61 [this](auto...) {
62 if (auto tmp = this->encode(); not holds_alternative<std::monostate>(tmp)) {
63 this->_parent.write(_path, this->encode());
64 } else {
65 this->_parent.remove(_path);
66 }
67 },
68 callback_flags::local);
69 }
70
71 void reset() noexcept override
72 {
73 _value = _init;
74 }
75
76protected:
77 [[nodiscard]] datum encode() const noexcept override
78 {
79 if (*_value != _init) {
80 return hi::pickle<T>{}.encode(*_value);
81 } else {
82 return datum{std::monostate{}};
83 }
84 }
85
86 void decode(datum const& data) override
87 {
88 _value = hi::pickle<T>{}.decode(data);
89 }
90
91private:
92 T _init;
93 observer<T> _value;
94 callback<void(T)> _value_cbt;
95};
96
97} // namespace detail
98
117public:
124
131 preferences() noexcept : _location(), _data(datum::make_map()), _modified(false)
132 {
133 using namespace std::chrono_literals;
134
135 _check_modified_cbt = loop::timer().repeat_function(5s, [this](auto...) {
136 this->check_modified();
137 });
138 }
139
146 preferences(std::filesystem::path location) noexcept : preferences()
147 {
148 load(location);
149 }
150
151 preferences(std::string_view location) : preferences(std::filesystem::path{location}) {}
152 preferences(std::string const& location) : preferences(std::filesystem::path{location}) {}
153 preferences(char const *location) : preferences(std::filesystem::path{location}) {}
154
155 ~preferences()
156 {
157 save();
158 }
159
160 preferences(preferences const&) = delete;
161 preferences(preferences&&) = delete;
162 preferences& operator=(preferences const&) = delete;
163 preferences& operator=(preferences&&) = delete;
164
169 void save() const noexcept
170 {
171 auto const lock = std::scoped_lock(mutex);
172 _save();
173 }
174
181 void save(std::filesystem::path location) noexcept
182 {
183 auto const lock = std::scoped_lock(mutex);
184 _location = std::move(location);
185 _save();
186 }
187
192 void load() noexcept
193 {
194 auto const lock = std::scoped_lock(mutex);
195 _load();
196 }
197
204 void load(std::filesystem::path location) noexcept
205 {
206 auto const lock = std::scoped_lock(mutex);
207 _location = std::move(location);
208 _load();
209 }
210
213 void reset() noexcept
214 {
215 _data = datum::make_map();
216 for (auto& item : _items) {
217 item->reset();
218 }
219 }
226 template<typename T>
227 void add(std::string_view path, observer<T> const& item, T init = T{}) noexcept
228 {
229 auto item_ = std::make_unique<detail::preference_item<T>>(*this, path, item, std::move(init));
230 item_->load();
231 _items.push_back(std::move(item_));
232 }
233
234private:
237 std::filesystem::path _location;
238
241 datum _data;
242
246 mutable bool _modified = false;
247
251
252 callback<void()> _check_modified_cbt;
253
254 void _load() noexcept
255 {
256 try {
257 auto file = hi::file(_location, access_mode::open_for_read);
258 auto text = file.read_string();
259 _data = parse_JSON(text);
260
261 for (auto& item : _items) {
262 item->load();
263 }
264
265 } catch (io_error const& e) {
266 hi_log_warning("Could not read preferences file. \"{}\"", e.what());
267 reset();
268
269 } catch (parse_error const& e) {
270 hi_log_error("Could not parse preferences file. \"{}\"", e.what());
271 reset();
272 }
273 }
274
275 void _save() const noexcept
276 {
277 try {
278 auto text = format_JSON(_data);
279
280 auto tmp_location = _location;
281 tmp_location += ".tmp";
282
283 auto file = hi::file(tmp_location, access_mode::truncate_or_create_for_write | access_mode::rename);
284 file.write(text);
285 file.flush();
286 file.rename(_location, true);
287
288 } catch (io_error const& e) {
289 hi_log_error("Could not save preferences to file. \"{}\"", e.what());
290 }
291
292 _modified = false;
293 }
294
297 void check_modified() noexcept
298 {
299 auto const lock = std::scoped_lock(mutex);
300
301 if (_modified) {
302 _save();
303 }
304 }
305
308 void write(jsonpath const& path, datum const value) noexcept
309 {
310 auto const lock = std::scoped_lock(mutex);
311 auto *v = _data.find_one_or_create(path);
312 if (v == nullptr) {
313 hi_log_fatal("Could not write '{}' to preference file '{}'", path, _location.string());
314 }
315
316 if (*v != value) {
317 *v = value;
318 _modified = true;
319 }
320 }
321
324 datum read(jsonpath const& path) noexcept
325 {
326 auto const lock = std::scoped_lock(mutex);
327 if (auto const *const r = _data.find_one(path)) {
328 return *r;
329 } else {
330 return datum{std::monostate{}};
331 }
332 }
333
336 void remove(jsonpath const& path) noexcept
337 {
338 auto const lock = std::scoped_lock(mutex);
339 if (_data.remove(path)) {
340 _modified = true;
341 }
342 }
343
344 friend class detail::preference_item_base;
345 template<typename T>
346 friend class detail::preference_item;
347};
348
349inline void detail::preference_item_base::load() noexcept
350{
351 auto const value = this->_parent.read(_path);
352 if (value.is_undefined()) {
353 this->reset();
354 } else {
355 try {
356 this->decode(value);
357 } catch (std::exception const&) {
358 hi_log_error("Could not decode preference {}, value {}", _path, value);
359 this->reset();
360 }
361 }
362}
363
364} // namespace hi::inline v1
Defines the file class.
STL namespace.
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
hi_export constexpr std::string format_JSON(datum const &root)
Dump an datum object into a JSON string.
Definition JSON.hpp:330
Encode and decode a type to and from a UTF-8 string.
Definition pickle.hpp:24
T decode(datum rhs) const
Decode a UTF-8 string into a value of a given type.
Definition pickle.hpp:48
datum encode(T const &rhs) const noexcept
Encode the value of a type into a UTF-8 string.
Definition pickle.hpp:30
A File object.
Definition file_intf.hpp:33
Definition preferences.hpp:23
virtual void reset() noexcept=0
Reset the value.
Definition preferences.hpp:55
void reset() noexcept override
Reset the value.
Definition preferences.hpp:71
user preferences.
Definition preferences.hpp:116
void save() const noexcept
Save the preferences.
Definition preferences.hpp:169
void reset() noexcept
Reset data members to their default value.
Definition preferences.hpp:213
preferences(std::filesystem::path location) noexcept
Construct a preferences instance.
Definition preferences.hpp:146
void save(std::filesystem::path location) noexcept
Save the preferences.
Definition preferences.hpp:181
std::mutex mutex
Mutex used to synchronize changes to the preferences.
Definition preferences.hpp:123
preferences() noexcept
Construct a preferences instance.
Definition preferences.hpp:131
void load() noexcept
Load the preferences.
Definition preferences.hpp:192
void load(std::filesystem::path location) noexcept
Load the preferences.
Definition preferences.hpp:204
void add(std::string_view path, observer< T > const &item, T init=T{}) noexcept
Register an observer to a preferences file.
Definition preferences.hpp:227
T lock(T... args)
T move(T... args)
T remove(T... args)