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