1use libc::{c_char, c_int};
2use std::{
3 borrow::Cow,
4 cell::RefCell,
5 collections::HashMap,
6 ffi::CStr,
7 io::{Error as IoError, ErrorKind},
8 marker::PhantomData,
9 os::raw::c_void,
10 ptr,
11 rc::Rc,
12};
13
14use weechat_sys::{
15 t_config_file, t_config_option, t_config_section, t_weechat_plugin, WEECHAT_RC_OK,
16};
17
18use crate::{
19 config::{
20 section::{
21 ConfigSection, ConfigSectionPointers, ConfigSectionSettings, SectionHandle,
22 SectionHandleMut, SectionReadCbT, SectionWriteCbT,
23 },
24 BaseConfigOption, BooleanOption, ColorOption, ConfigOption, IntegerOption, StringOption,
25 },
26 LossyCString, Weechat,
27};
28
29pub struct Config {
31 inner: Conf,
32 _config_data: *mut ConfigPointers,
33 sections: HashMap<String, Rc<RefCell<ConfigSection>>>,
34}
35
36pub struct Conf {
38 ptr: *mut t_config_file,
39 weechat_ptr: *mut t_weechat_plugin,
40}
41
42#[derive(Debug)]
44pub enum OptionChanged {
45 Changed = weechat_sys::WEECHAT_CONFIG_OPTION_SET_OK_CHANGED as isize,
47 Unchanged = weechat_sys::WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE as isize,
49 NotFound = weechat_sys::WEECHAT_CONFIG_OPTION_SET_OPTION_NOT_FOUND as isize,
51 Error = weechat_sys::WEECHAT_CONFIG_OPTION_SET_ERROR as isize,
53}
54
55impl OptionChanged {
56 pub(crate) fn from_int(v: i32) -> OptionChanged {
57 use OptionChanged::*;
58 match v {
59 weechat_sys::WEECHAT_CONFIG_OPTION_SET_OK_CHANGED => Changed,
60 weechat_sys::WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE => Unchanged,
61 weechat_sys::WEECHAT_CONFIG_OPTION_SET_OPTION_NOT_FOUND => NotFound,
62 weechat_sys::WEECHAT_CONFIG_OPTION_SET_ERROR => Error,
63 _ => unreachable!(),
64 }
65 }
66}
67
68struct ConfigPointers {
69 reload_cb: Option<Box<dyn ConfigReloadCallback>>,
70 weechat_ptr: *mut t_weechat_plugin,
71}
72
73type ReloadCB = unsafe extern "C" fn(
74 pointer: *const c_void,
75 _data: *mut c_void,
76 config_pointer: *mut t_config_file,
77) -> c_int;
78
79pub trait ConfigReloadCallback: 'static {
84 fn callback(&mut self, weechat: &Weechat, config: &Conf);
92}
93
94impl<T: FnMut(&Weechat, &Conf) + 'static> ConfigReloadCallback for T {
95 fn callback(&mut self, weechat: &Weechat, config: &Conf) {
96 self(weechat, config)
97 }
98}
99
100impl Weechat {
101 pub(crate) fn config_option_get_string(
102 &self,
103 pointer: *mut t_config_option,
104 property: &str,
105 ) -> Option<Cow<str>> {
106 let get_string = self.get().config_option_get_string.unwrap();
107 let property = LossyCString::new(property);
108
109 unsafe {
110 let string = get_string(pointer, property.as_ptr());
111 if string.is_null() {
112 None
113 } else {
114 Some(CStr::from_ptr(string).to_string_lossy())
115 }
116 }
117 }
118
119 pub fn config_get(&self, option_name: &str) -> Option<ConfigOption> {
125 let weechat = Weechat::from_ptr(self.ptr);
126 let config_get = weechat.get().config_get.unwrap();
127 let name = LossyCString::new(option_name);
128
129 let ptr = unsafe { config_get(name.as_ptr()) };
130
131 if ptr.is_null() {
132 return None;
133 }
134
135 let option_type = weechat.config_option_get_string(ptr, "type").unwrap();
136
137 Some(Config::option_from_type_and_ptr(
138 self.ptr,
139 ptr,
140 option_type.as_ref(),
141 ))
142 }
143
144 pub fn get_plugin_option(&self, option: &str) -> Option<Cow<str>> {
146 let config_get_plugin = self.get().config_get_plugin.unwrap();
147
148 let option_name = LossyCString::new(option);
149
150 unsafe {
151 let option = config_get_plugin(self.ptr, option_name.as_ptr());
152 if option.is_null() {
153 None
154 } else {
155 Some(CStr::from_ptr(option).to_string_lossy())
156 }
157 }
158 }
159
160 pub fn set_plugin_option(&self, option: &str, value: &str) -> OptionChanged {
162 let config_set_plugin = self.get().config_set_plugin.unwrap();
163
164 let option_name = LossyCString::new(option);
165 let value = LossyCString::new(value);
166
167 unsafe {
168 let result = config_set_plugin(self.ptr, option_name.as_ptr(), value.as_ptr());
169
170 OptionChanged::from_int(result as i32)
171 }
172 }
173}
174
175impl Drop for Config {
176 fn drop(&mut self) {
177 let weechat = Weechat::from_ptr(self.inner.weechat_ptr);
178 let config_free = weechat.get().config_free.unwrap();
179
180 self.sections.clear();
182
183 unsafe {
184 Box::from_raw(self._config_data);
186 config_free(self.inner.ptr)
187 };
188 }
189}
190
191impl Config {
192 pub fn new(name: &str) -> Result<Config, ()> {
202 Config::config_new_helper(name, None)
203 }
204
205 pub fn new_with_callback(
232 name: &str,
233 reload_callback: impl ConfigReloadCallback,
234 ) -> Result<Config, ()> {
235 let callback = Box::new(reload_callback);
236 Config::config_new_helper(name, Some(callback))
237 }
238
239 fn config_new_helper(
240 name: &str,
241 callback: Option<Box<dyn ConfigReloadCallback>>,
242 ) -> Result<Config, ()> {
243 unsafe extern "C" fn c_reload_cb(
244 pointer: *const c_void,
245 _data: *mut c_void,
246 config_pointer: *mut t_config_file,
247 ) -> c_int {
248 let pointers: &mut ConfigPointers = { &mut *(pointer as *mut ConfigPointers) };
249
250 let cb = &mut pointers
251 .reload_cb
252 .as_mut()
253 .expect("C callback was set while no rust callback");
254 let conf = Conf {
255 ptr: config_pointer,
256 weechat_ptr: pointers.weechat_ptr,
257 };
258
259 let weechat = Weechat::from_ptr(pointers.weechat_ptr);
260
261 cb.callback(&weechat, &conf);
262
263 WEECHAT_RC_OK
264 }
265
266 Weechat::check_thread();
267 let weechat = unsafe { Weechat::weechat() };
268
269 let c_name = LossyCString::new(name);
270
271 let c_reload_cb = match callback {
272 Some(_) => Some(c_reload_cb as ReloadCB),
273 None => None,
274 };
275
276 let config_pointers = Box::new(ConfigPointers {
277 reload_cb: callback,
278 weechat_ptr: weechat.ptr,
279 });
280 let config_pointers_ref = Box::leak(config_pointers);
281
282 let config_new = weechat.get().config_new.unwrap();
283
284 let config_ptr = unsafe {
285 config_new(
286 weechat.ptr,
287 c_name.as_ptr(),
288 c_reload_cb,
289 config_pointers_ref as *const _ as *const c_void,
290 ptr::null_mut(),
291 )
292 };
293
294 if config_ptr.is_null() {
295 unsafe { Box::from_raw(config_pointers_ref) };
296 return Err(());
297 };
298
299 Ok(Config {
300 inner: Conf {
301 ptr: config_ptr,
302 weechat_ptr: weechat.ptr,
303 },
304 _config_data: config_pointers_ref,
305 sections: HashMap::new(),
306 })
307 }
308
309 pub(crate) fn option_from_type_and_ptr<'a>(
310 weechat_ptr: *mut t_weechat_plugin,
311 option_ptr: *mut t_config_option,
312 option_type: &str,
313 ) -> ConfigOption<'a> {
314 match option_type {
315 "boolean" => ConfigOption::Boolean(BooleanOption {
316 ptr: option_ptr,
317 weechat_ptr,
318 _phantom: PhantomData,
319 }),
320 "integer" => ConfigOption::Integer(IntegerOption {
321 ptr: option_ptr,
322 weechat_ptr,
323 _phantom: PhantomData,
324 }),
325 "string" => ConfigOption::String(StringOption {
326 ptr: option_ptr,
327 weechat_ptr,
328 _phantom: PhantomData,
329 }),
330 "color" => ConfigOption::Color(ColorOption {
331 ptr: option_ptr,
332 weechat_ptr,
333 _phantom: PhantomData,
334 }),
335 _ => unreachable!(),
336 }
337 }
338 fn return_value_to_error(return_value: c_int) -> std::io::Result<()> {
339 match return_value {
340 weechat_sys::WEECHAT_CONFIG_READ_OK => Ok(()),
341 weechat_sys::WEECHAT_CONFIG_READ_FILE_NOT_FOUND => {
342 Err(IoError::new(ErrorKind::NotFound, "File was not found"))
343 }
344 weechat_sys::WEECHAT_CONFIG_READ_MEMORY_ERROR => {
345 Err(IoError::new(ErrorKind::Other, "Not enough memory"))
346 }
347 _ => unreachable!(),
348 }
349 }
350
351 pub fn read(&self) -> std::io::Result<()> {
353 let weechat = Weechat::from_ptr(self.inner.weechat_ptr);
354 let config_read = weechat.get().config_read.unwrap();
355
356 let ret = unsafe { config_read(self.inner.ptr) };
357
358 Config::return_value_to_error(ret)
359 }
360
361 pub fn write(&self) -> std::io::Result<()> {
363 let weechat = Weechat::from_ptr(self.inner.weechat_ptr);
364 let config_write = weechat.get().config_write.unwrap();
365
366 let ret = unsafe { config_write(self.inner.ptr) };
367
368 Config::return_value_to_error(ret)
369 }
370
371 pub fn new_section(
382 &mut self,
383 section_settings: ConfigSectionSettings,
384 ) -> Result<SectionHandleMut, ()> {
385 unsafe extern "C" fn c_read_cb(
386 pointer: *const c_void,
387 _data: *mut c_void,
388 config: *mut t_config_file,
389 _section: *mut t_config_section,
390 option_name: *const c_char,
391 value: *const c_char,
392 ) -> c_int {
393 let option_name = CStr::from_ptr(option_name).to_string_lossy();
394 let value = CStr::from_ptr(value).to_string_lossy();
395 let pointers: &mut ConfigSectionPointers =
396 { &mut *(pointer as *mut ConfigSectionPointers) };
397
398 let conf = Conf {
399 ptr: config,
400 weechat_ptr: pointers.weechat_ptr,
401 };
402 let section = pointers
403 .section
404 .as_ref()
405 .expect("Section reference wasn't set up correctly")
406 .upgrade()
407 .expect("Config has been destroyed but a read callback run");
408
409 let weechat = Weechat::from_ptr(pointers.weechat_ptr);
410
411 let cb = pointers
412 .read_cb
413 .as_mut()
414 .expect("C read callback was called but no ruts callback");
415
416 let ret = cb.callback(
417 &weechat,
418 &conf,
419 &mut section.borrow_mut(),
420 option_name.as_ref(),
421 value.as_ref(),
422 );
423
424 ret as i32
425 }
426
427 unsafe extern "C" fn c_write_cb(
428 pointer: *const c_void,
429 _data: *mut c_void,
430 config: *mut t_config_file,
431 _section_name: *const c_char,
432 ) -> c_int {
433 let pointers: &mut ConfigSectionPointers =
434 { &mut *(pointer as *mut ConfigSectionPointers) };
435
436 let section = pointers
437 .section
438 .as_ref()
439 .expect("Section reference wasn't set up correctly")
440 .upgrade()
441 .expect("Config has been destroyed but a read callback run");
442
443 let conf = Conf {
444 ptr: config,
445 weechat_ptr: pointers.weechat_ptr,
446 };
447 let weechat = Weechat::from_ptr(pointers.weechat_ptr);
448
449 if let Some(ref mut cb) = pointers.write_cb {
450 cb.callback(&weechat, &conf, &mut section.borrow_mut())
451 }
452 WEECHAT_RC_OK
453 }
454
455 unsafe extern "C" fn c_write_default_cb(
456 pointer: *const c_void,
457 _data: *mut c_void,
458 config: *mut t_config_file,
459 _section_name: *const c_char,
460 ) -> c_int {
461 let pointers: &mut ConfigSectionPointers =
462 { &mut *(pointer as *mut ConfigSectionPointers) };
463
464 let section = pointers
465 .section
466 .as_ref()
467 .expect("Section reference wasn't set up correctly")
468 .upgrade()
469 .expect("Config has been destroyed but a read callback run");
470
471 let conf = Conf {
472 ptr: config,
473 weechat_ptr: pointers.weechat_ptr,
474 };
475 let weechat = Weechat::from_ptr(pointers.weechat_ptr);
476
477 if let Some(ref mut cb) = pointers.write_default_cb {
478 cb.callback(&weechat, &conf, &mut section.borrow_mut())
479 }
480 WEECHAT_RC_OK
481 }
482
483 let weechat = Weechat::from_ptr(self.inner.weechat_ptr);
484
485 let new_section = weechat.get().config_new_section.unwrap();
486
487 let name = LossyCString::new(§ion_settings.name);
488
489 let (c_read_cb, read_cb) = match section_settings.read_callback {
490 Some(cb) => (Some(c_read_cb as SectionReadCbT), Some(cb)),
491 None => (None, None),
492 };
493
494 let (c_write_cb, write_cb) = match section_settings.write_callback {
495 Some(cb) => (Some(c_write_cb as SectionWriteCbT), Some(cb)),
496 None => (None, None),
497 };
498
499 let (c_write_default_cb, write_default_cb) = match section_settings.write_default_callback {
500 Some(cb) => (Some(c_write_default_cb as SectionWriteCbT), Some(cb)),
501 None => (None, None),
502 };
503
504 let section_data = Box::new(ConfigSectionPointers {
505 read_cb,
506 write_cb,
507 write_default_cb,
508 weechat_ptr: self.inner.weechat_ptr,
509 section: None,
510 });
511 let section_data_ptr = Box::leak(section_data);
512
513 let ptr = unsafe {
514 new_section(
515 self.inner.ptr,
516 name.as_ptr(),
517 0,
518 0,
519 c_read_cb,
520 section_data_ptr as *const _ as *const c_void,
521 ptr::null_mut(),
522 c_write_cb,
523 section_data_ptr as *const _ as *const c_void,
524 ptr::null_mut(),
525 c_write_default_cb,
526 section_data_ptr as *const _ as *const c_void,
527 ptr::null_mut(),
528 None,
529 ptr::null_mut(),
530 ptr::null_mut(),
531 None,
532 ptr::null_mut(),
533 ptr::null_mut(),
534 )
535 };
536
537 if ptr.is_null() {
538 unsafe { Box::from_raw(section_data_ptr) };
539 return Err(());
540 };
541
542 let section = ConfigSection {
543 ptr,
544 config_ptr: self.inner.ptr,
545 weechat_ptr: weechat.ptr,
546 section_data: section_data_ptr as *const _ as *const c_void,
547 name: section_settings.name.clone(),
548 option_pointers: HashMap::new(),
549 };
550
551 let section = Rc::new(RefCell::new(section));
552 let pointers: &mut ConfigSectionPointers =
553 unsafe { &mut *(section_data_ptr as *mut ConfigSectionPointers) };
554
555 pointers.section = Some(Rc::downgrade(§ion));
556
557 self.sections.insert(section_settings.name.clone(), section);
558 let section = &self.sections[§ion_settings.name];
559
560 Ok(SectionHandleMut {
561 inner: section.borrow_mut(),
562 })
563 }
564
565 pub fn search_section(&self, section_name: &str) -> Option<SectionHandle> {
579 if !self.sections.contains_key(section_name) {
580 None
581 } else {
582 Some(SectionHandle {
583 inner: self.sections[section_name].borrow(),
584 })
585 }
586 }
587
588 pub fn search_section_mut(&mut self, section_name: &str) -> Option<SectionHandleMut> {
602 if !self.sections.contains_key(section_name) {
603 None
604 } else {
605 Some(SectionHandleMut {
606 inner: self.sections[section_name].borrow_mut(),
607 })
608 }
609 }
610}
611
612impl Conf {
613 pub fn write_section(&self, section_name: &str) {
615 self.write(section_name, None)
616 }
617
618 pub fn write_line(&self, key: &str, value: &str) {
627 self.write(key, Some(value))
628 }
629
630 fn write(&self, key: &str, value: Option<&str>) {
631 let weechat = Weechat::from_ptr(self.weechat_ptr);
632 let write_line = weechat.get().config_write_line.unwrap();
633
634 let option_name = LossyCString::new(key);
635
636 let c_value = match value {
637 Some(v) => LossyCString::new(v).as_ptr(),
638 None => ptr::null(),
639 };
640
641 unsafe {
642 write_line(self.ptr, option_name.as_ptr(), c_value);
643 }
644 }
645
646 pub fn write_option<'a, O: AsRef<dyn BaseConfigOption + 'a>>(&self, option: O) {
652 let weechat = Weechat::from_ptr(self.weechat_ptr);
653 let write_option = weechat.get().config_write_option.unwrap();
654
655 unsafe {
656 write_option(self.ptr, option.as_ref().get_ptr());
657 }
658 }
659}