1 #include "../include/optionsbase.h"
2
3 #include <libfilezilla/event_handler.hpp>
4
option_def(std::string_view name,std::wstring_view def,option_flags flags,size_t max_len)5 option_def::option_def(std::string_view name, std::wstring_view def, option_flags flags, size_t max_len)
6 : name_(name)
7 , default_(def)
8 , type_(option_type::string)
9 , flags_(flags)
10 , max_(static_cast<int>(max_len))
11 {}
12
option_def(std::string_view name,std::wstring_view def,option_flags flags,option_type t,size_t max_len,bool (* validator)(std::wstring & v))13 option_def::option_def(std::string_view name, std::wstring_view def, option_flags flags, option_type t, size_t max_len, bool (*validator)(std::wstring& v))
14 : name_(name)
15 , default_(def)
16 , type_(t)
17 , flags_(flags)
18 , max_(static_cast<int>(max_len))
19 , validator_((t == option_type::string) ? reinterpret_cast<void*>(validator) : nullptr)
20 {
21 }
22
option_def(std::string_view name,std::wstring_view def,option_flags flags,bool (* validator)(pugi::xml_node &))23 option_def::option_def(std::string_view name, std::wstring_view def, option_flags flags, bool (*validator)(pugi::xml_node&))
24 : name_(name)
25 , default_(def)
26 , type_(option_type::xml)
27 , flags_(flags)
28 , max_(10000000)
29 , validator_(reinterpret_cast<void*>(validator))
30 {}
31
option_def(std::string_view name,int def,option_flags flags,int min,int max,bool (* validator)(int & v))32 option_def::option_def(std::string_view name, int def, option_flags flags, int min, int max, bool (*validator)(int& v))
33 : name_(name)
34 , default_(fz::to_wstring(def))
35 , type_(option_type::number)
36 , flags_(flags)
37 , min_(min)
38 , max_(max)
39 , validator_(reinterpret_cast<void*>(validator))
40 {}
41
42 template<>
option_def(std::string_view name,bool def,option_flags flags)43 FZC_PUBLIC_SYMBOL option_def::option_def(std::string_view name, bool def, option_flags flags)
44 : name_(name)
45 , default_(fz::to_wstring(def))
46 , type_(option_type::boolean)
47 , flags_(flags)
48 , max_(1)
49 {}
50
51
52
53 namespace {
event_handler_option_watcher_notifier(void * handler,watched_options && options)54 void event_handler_option_watcher_notifier(void* handler, watched_options&& options)
55 {
56 static_cast<fz::event_handler*>(handler)->send_event<options_changed_event>(std::move(options));
57 }
58 }
59
get_option_watcher_notifier(fz::event_handler * handler)60 std::tuple<void*, watcher_notifier> get_option_watcher_notifier(fz::event_handler * handler)
61 {
62 return std::make_tuple(handler, &event_handler_option_watcher_notifier);
63 }
64
65 namespace {
66 class option_registry final {
67 public:
68 fz::mutex mtx_;
69 std::vector<option_def> options_;
70 std::map<std::string, size_t, std::less<>> name_to_option_;
71 };
72
get_option_registry()73 std::pair<option_registry&, fz::scoped_lock> get_option_registry()
74 {
75 static option_registry reg;
76 return std::make_pair(std::ref(reg), fz::scoped_lock(reg.mtx_));
77 }
78 }
79
register_options(std::initializer_list<option_def> options)80 unsigned int register_options(std::initializer_list<option_def> options)
81 {
82 auto registry = get_option_registry();
83 size_t const prev = registry.first.options_.size();
84 registry.first.options_.insert(registry.first.options_.end(), options);
85 for (size_t i = prev; i < registry.first.options_.size(); ++i) {
86 registry.first.name_to_option_[registry.first.options_[i].name()] = i;
87 }
88
89 return static_cast<unsigned int>(prev);
90 }
91
92 namespace {
set_default_value(size_t i,std::vector<option_def> & options,std::vector<COptionsBase::option_value> & values)93 void set_default_value(size_t i, std::vector<option_def>& options, std::vector<COptionsBase::option_value>& values)
94 {
95 auto& val = values[i];
96 auto const& def = options[i];
97
98 if (def.type() == option_type::xml) {
99 val.xml_ = std::make_unique<pugi::xml_document>();
100 val.xml_->load_string(fz::to_utf8(def.def()).c_str());
101 }
102 else {
103 val.str_ = def.def();
104 val.v_ = fz::to_integral<int>(def.def());
105 }
106 }
107
108 template<typename Lock>
do_add_missing(optionsIndex opt,Lock & l,fz::rwmutex & mtx,std::vector<option_def> & options,std::map<std::string,size_t,std::less<>> & name_to_option,std::vector<COptionsBase::option_value> & values)109 bool do_add_missing(optionsIndex opt, Lock & l, fz::rwmutex & mtx, std::vector<option_def> & options, std::map<std::string, size_t, std::less<>> & name_to_option, std::vector<COptionsBase::option_value> & values)
110 {
111 l.unlock();
112
113 {
114 auto registry = get_option_registry();
115
116 if (static_cast<size_t>(opt) >= registry.first.options_.size()) {
117 return false;
118 }
119
120 mtx.lock_write();
121
122 options = registry.first.options_;
123 name_to_option = registry.first.name_to_option_;
124 }
125
126 size_t i = values.size();
127 values.resize(options.size());
128
129 for (; i < options.size(); ++i) {
130 set_default_value(i, options, values);
131 }
132 mtx.unlock_write();
133 l.lock();
134 return true;
135 }
136 }
137
add_missing(fz::scoped_write_lock & l)138 void COptionsBase::add_missing(fz::scoped_write_lock & l)
139 {
140 do_add_missing(static_cast<optionsIndex>(0), l, mtx_, options_, name_to_option_, values_);
141 }
142
get_int(optionsIndex opt)143 int COptionsBase::get_int(optionsIndex opt)
144 {
145 if (opt == optionsIndex::invalid) {
146 return 0;
147 }
148
149 fz::scoped_read_lock l(mtx_);
150 if (static_cast<size_t>(opt) >= values_.size() && !do_add_missing(opt, l, mtx_, options_, name_to_option_, values_)) {
151 return 0;
152 }
153
154 auto& val = values_[static_cast<size_t>(opt)];
155 return val.v_;
156 }
157
get_string(optionsIndex opt)158 std::wstring COptionsBase::get_string(optionsIndex opt)
159 {
160 if (opt == optionsIndex::invalid) {
161 return std::wstring();
162 }
163
164 fz::scoped_read_lock l(mtx_);
165 if (static_cast<size_t>(opt) >= values_.size() && !do_add_missing(opt, l, mtx_, options_, name_to_option_, values_)) {
166 return std::wstring();
167 }
168
169 auto& val = values_[static_cast<size_t>(opt)];
170 return val.str_;
171 }
172
get_xml(optionsIndex opt)173 pugi::xml_document COptionsBase::get_xml(optionsIndex opt)
174 {
175 pugi::xml_document ret;
176 if (opt == optionsIndex::invalid) {
177 return ret;
178 }
179
180 fz::scoped_write_lock l(mtx_); // Aquire write lock as we don't know what pugixml does internally
181 if (static_cast<size_t>(opt) >= values_.size() && !do_add_missing(opt, l, mtx_, options_, name_to_option_, values_)) {
182 return ret;
183 }
184
185 auto& val = values_[static_cast<size_t>(opt)];
186 if (val.xml_) {
187 for (auto c = val.xml_->first_child(); c; c = c.next_sibling()) {
188 ret.append_copy(c);
189 }
190 }
191 return ret;
192 }
193
predefined(optionsIndex opt)194 bool COptionsBase::predefined(optionsIndex opt)
195 {
196 fz::scoped_read_lock l(mtx_);
197 if (opt == optionsIndex::invalid || static_cast<size_t>(opt) >= values_.size()) {
198 // No need for add_missing, predefined_ can only be set from set()
199 return false;
200 }
201
202 auto& val = values_[static_cast<size_t>(opt)];
203 return val.predefined_;
204 }
205
set(optionsIndex opt,int value)206 void COptionsBase::set(optionsIndex opt, int value)
207 {
208 if (opt == optionsIndex::invalid) {
209 return;
210 }
211
212 fz::scoped_write_lock l(mtx_);
213 if (static_cast<size_t>(opt) >= values_.size() && !do_add_missing(opt, l, mtx_, options_, name_to_option_, values_)) {
214 return;
215 }
216
217 auto const& def = options_[static_cast<size_t>(opt)];
218 auto& val = values_[static_cast<size_t>(opt)];
219
220 // Type conversion
221 if (def.type() == option_type::number) {
222 set(opt, def, val, value);
223 }
224 else if (def.type() == option_type::boolean) {
225 set(opt, def, val, value ? 1 : 0);
226 }
227 else if (def.type() == option_type::string) {
228 set(opt, def, val, fz::to_wstring(value));
229 }
230 }
231
set(optionsIndex opt,std::wstring_view const & value,bool predefined)232 void COptionsBase::set(optionsIndex opt, std::wstring_view const& value, bool predefined)
233 {
234 if (opt == optionsIndex::invalid) {
235 return;
236 }
237
238 fz::scoped_write_lock l(mtx_);
239 if (static_cast<size_t>(opt) >= values_.size() && !do_add_missing(opt, l, mtx_, options_, name_to_option_, values_)) {
240 return;
241 }
242
243 auto const& def = options_[static_cast<size_t>(opt)];
244 auto& val = values_[static_cast<size_t>(opt)];
245
246 // Type conversion
247 if (def.type() == option_type::number) {
248 set(opt, def, val, fz::to_integral<int>(value), predefined);
249 }
250 else if (def.type() == option_type::boolean) {
251 set(opt, def, val, fz::to_integral<int>(value), predefined);
252 }
253 else if (def.type() == option_type::string) {
254 set(opt, def, val, value, predefined);
255 }
256 }
257
set(optionsIndex opt,pugi::xml_node const & value)258 void COptionsBase::set(optionsIndex opt, pugi::xml_node const& value)
259 {
260 if (opt == optionsIndex::invalid) {
261 return;
262 }
263
264 pugi::xml_document doc;
265 if (value) {
266 if (value.type() == pugi::node_document) {
267 for (auto c = value.first_child(); c; c = c.next_sibling()) {
268 if (c.type() == pugi::node_element) {
269 doc.append_copy(c);
270 }
271 }
272 }
273 else {
274 doc.append_copy(value);
275 }
276 }
277
278 fz::scoped_write_lock l(mtx_);
279 if (static_cast<size_t>(opt) >= values_.size() && !do_add_missing(opt, l, mtx_, options_, name_to_option_, values_)) {
280 return;
281 }
282
283 auto const& def = options_[static_cast<size_t>(opt)];
284 auto& val = values_[static_cast<size_t>(opt)];
285
286 // Type check
287 if (def.type() != option_type::xml) {
288 return;
289 }
290
291 set(opt, def, val, std::move(doc));
292 }
293
set(optionsIndex opt,option_def const & def,option_value & val,int value,bool predefined)294 void COptionsBase::set(optionsIndex opt, option_def const& def, option_value& val, int value, bool predefined)
295 {
296 if ((def.flags() & option_flags::predefined_only) && !predefined) {
297 return;
298 }
299 if ((def.flags() & option_flags::predefined_priority) && !predefined && val.predefined_) {
300 return;
301 }
302
303 if (value < def.min()) {
304 if (!(def.flags() & option_flags::numeric_clamp)) {
305 return;
306 }
307 value = def.min();
308 }
309 else if (value > def.max()) {
310 if (!(def.flags() & option_flags::numeric_clamp)) {
311 return;
312 }
313 value = def.max();
314 }
315 if (def.validator()) {
316 if (!reinterpret_cast<bool(*)(int&)>(def.validator())(value)) {
317 return;
318 }
319 }
320
321 val.predefined_ = predefined;
322 if (value == val.v_) {
323 return;
324 }
325
326 val.v_ = value;
327 val.str_ = fz::to_wstring(value);
328 ++val.change_counter_;
329
330 set_changed(opt);
331 }
332
set(optionsIndex opt,option_def const & def,option_value & val,std::wstring_view const & value,bool predefined)333 void COptionsBase::set(optionsIndex opt, option_def const& def, option_value& val, std::wstring_view const& value, bool predefined)
334 {
335 if ((def.flags() & option_flags::predefined_only) && !predefined) {
336 return;
337 }
338 if ((def.flags() & option_flags::predefined_priority) && !predefined && val.predefined_) {
339 return;
340 }
341
342 if (value.size() > static_cast<size_t>(def.max())) {
343 return;
344 }
345
346 if (def.validator()) {
347 std::wstring v(value);
348 if (!reinterpret_cast<bool(*)(std::wstring&)>(def.validator())(v)) {
349 return;
350 }
351
352 val.predefined_ = predefined;
353 if (v == val.str_) {
354 return;
355 }
356 val.v_ = fz::to_integral<int>(v);
357 val.str_ = std::move(v);
358 }
359 else {
360
361 val.predefined_ = predefined;
362 if (value == val.str_) {
363 return;
364 }
365 val.v_ = fz::to_integral<int>(value);
366 val.str_ = value;
367 }
368 ++val.change_counter_;
369
370 set_changed(opt);
371 }
372
set(optionsIndex opt,option_def const & def,option_value & val,pugi::xml_document && value,bool predefined)373 void COptionsBase::set(optionsIndex opt, option_def const& def, option_value& val, pugi::xml_document&& value, bool predefined)
374 {
375 if ((def.flags() & option_flags::predefined_only) && !predefined) {
376 return;
377 }
378 if ((def.flags() & option_flags::predefined_priority) && !predefined && val.predefined_) {
379 return;
380 }
381
382 if (def.validator()) {
383 if (!reinterpret_cast<bool(*)(pugi::xml_node&)>(def.validator())(value)) {
384 return;
385 }
386 }
387 *val.xml_ = std::move(value);
388 ++val.change_counter_;
389
390 set_changed(opt);
391 }
392
set_changed(optionsIndex opt)393 void COptionsBase::set_changed(optionsIndex opt)
394 {
395 bool notify = can_notify_ && !changed_.any();
396 changed_.set(opt);
397 if (notify) {
398 notify_changed();
399 }
400 }
401
any() const402 bool watched_options::any() const
403 {
404 for (auto const& v : options_) {
405 if (v) {
406 return true;
407 }
408 }
409
410 return false;
411 }
412
set(optionsIndex opt)413 void watched_options::set(optionsIndex opt)
414 {
415 auto idx = static_cast<size_t>(opt) / 64;
416 if (idx >= options_.size()) {
417 options_.resize(idx + 1);
418 }
419
420 options_[idx] |= (1ull << static_cast<size_t>(opt) % 64);
421 }
422
unset(optionsIndex opt)423 void watched_options::unset(optionsIndex opt)
424 {
425 auto idx = static_cast<size_t>(opt) / 64;
426 if (idx < options_.size()) {
427 options_[idx] &= ~(1ull << static_cast<size_t>(opt) % 64);
428 }
429
430 }
431
test(optionsIndex opt) const432 bool watched_options::test(optionsIndex opt) const
433 {
434 auto idx = static_cast<size_t>(opt) / 64;
435 if (idx >= options_.size()) {
436 return false;
437 }
438
439 return options_[idx] & (1ull << static_cast<size_t>(opt) % 64);
440 }
441
operator &=(std::vector<uint64_t> const & op)442 watched_options& watched_options::operator&=(std::vector<uint64_t> const& op)
443 {
444 size_t s = std::min(options_.size(), op.size());
445 options_.resize(s);
446 for (size_t i = 0; i < s; ++i) {
447 options_[i] &= op[i];
448 }
449 return *this;
450 }
451
continue_notify_changed()452 void COptionsBase::continue_notify_changed()
453 {
454 watched_options changed;
455 {
456 fz::scoped_write_lock l(mtx_);
457 if (!changed_) {
458 return;
459 }
460 changed = changed_;
461 changed_.clear();
462 process_changed(changed);
463 }
464
465 fz::scoped_lock l(notification_mtx_);
466
467 for (auto const& w : watchers_) {
468 watched_options n = changed;
469 if (!w.all_) {
470 n &= w.options_;
471 }
472 if (n) {
473 w.notifier_(w.handler_, std::move(n));
474 }
475 }
476 }
477
watch(optionsIndex opt,std::tuple<void *,watcher_notifier> handler)478 void COptionsBase::watch(optionsIndex opt, std::tuple<void*, watcher_notifier> handler)
479 {
480 if (!std::get<0>(handler) || !std::get<1>(handler)|| opt == optionsIndex::invalid) {
481 return;
482 }
483
484 fz::scoped_lock l(notification_mtx_);
485 for (size_t i = 0; i < watchers_.size(); ++i) {
486 if (watchers_[i].handler_ == std::get<0>(handler)) {
487 watchers_[i].options_.set(opt);
488 return;
489 }
490 }
491 watcher w;
492 w.handler_ = std::get<0>(handler);
493 w.notifier_ = std::get<1>(handler);
494 w.options_.set(opt);
495 watchers_.push_back(w);
496 }
497
watch_all(std::tuple<void *,watcher_notifier> handler)498 void COptionsBase::watch_all(std::tuple<void*, watcher_notifier> handler)
499 {
500 if (!std::get<0>(handler)) {
501 return;
502 }
503
504 fz::scoped_lock l(notification_mtx_);
505 for (size_t i = 0; i < watchers_.size(); ++i) {
506 if (watchers_[i].handler_ == std::get<0>(handler)) {
507 watchers_[i].all_ = true;
508 return;
509 }
510 }
511 watcher w;
512 w.handler_ = std::get<0>(handler);
513 w.notifier_ = std::get<1>(handler);
514 w.all_ = true;
515 watchers_.push_back(w);
516 }
517
unwatch(optionsIndex opt,std::tuple<void *,watcher_notifier> handler)518 void COptionsBase::unwatch(optionsIndex opt, std::tuple<void*, watcher_notifier> handler)
519 {
520 if (!std::get<0>(handler) || opt == optionsIndex::invalid) {
521 return;
522 }
523
524 fz::scoped_lock l(notification_mtx_);
525 for (size_t i = 0; i < watchers_.size(); ++i) {
526 if (watchers_[i].handler_ == std::get<0>(handler)) {
527 watchers_[i].options_.unset(opt);
528 if (watchers_[i].options_ || watchers_[i].all_) {
529 return;
530 }
531 watchers_[i] = watchers_.back();
532 watchers_.pop_back();
533 return;
534 }
535 }
536 }
537
unwatch_all(std::tuple<void *,watcher_notifier> handler)538 void COptionsBase::unwatch_all(std::tuple<void*, watcher_notifier> handler)
539 {
540 if (!std::get<0>(handler) || !std::get<1>(handler)) {
541 return;
542 }
543
544 fz::scoped_lock l(notification_mtx_);
545 for (size_t i = 0; i < watchers_.size(); ++i) {
546 if (watchers_[i].handler_ == std::get<0>(handler)) {
547 watchers_[i] = watchers_.back();
548 watchers_.pop_back();
549 return;
550 }
551 }
552 }
553
set_default_value(optionsIndex opt)554 void COptionsBase::set_default_value(optionsIndex opt)
555 {
556 ::set_default_value(static_cast<size_t>(opt), options_, values_);
557 }
558
change_count(optionsIndex opt)559 uint64_t COptionsBase::change_count(optionsIndex opt)
560 {
561 fz::scoped_read_lock l(mtx_);
562 if (opt == optionsIndex::invalid || static_cast<size_t>(opt) >= values_.size()) {
563 return 0;
564 }
565
566 auto& val = values_[static_cast<size_t>(opt)];
567 return val.change_counter_;
568 }
569