weechat/config/
config.rs

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
29/// Weechat configuration file
30pub struct Config {
31    inner: Conf,
32    _config_data: *mut ConfigPointers,
33    sections: HashMap<String, Rc<RefCell<ConfigSection>>>,
34}
35
36/// The borrowed equivalent of the `Config`. Will be present in callbacks.
37pub struct Conf {
38    ptr: *mut t_config_file,
39    weechat_ptr: *mut t_weechat_plugin,
40}
41
42/// Status for updating options
43#[derive(Debug)]
44pub enum OptionChanged {
45    /// The option was successfully changed.
46    Changed = weechat_sys::WEECHAT_CONFIG_OPTION_SET_OK_CHANGED as isize,
47    /// The options value has not changed.
48    Unchanged = weechat_sys::WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE as isize,
49    /// The option was not found.
50    NotFound = weechat_sys::WEECHAT_CONFIG_OPTION_SET_OPTION_NOT_FOUND as isize,
51    /// An error occurred changing the value.
52    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
79/// Trait for the config reload callback.
80///
81/// This trait can be implemented or a normal function or coroutine can be
82/// passed as the callback.
83pub trait ConfigReloadCallback: 'static {
84    /// Function called when configuration file is reloaded with /reload
85    ///
86    /// # Arguments
87    ///
88    /// * `weeechat` - A reference to the weechat context.
89    ///
90    /// * `config` - A reference to the non-owned config.
91    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    /// Search an option with a full name.
120    /// # Arguments
121    ///
122    /// * `option_name` - The full name of the option that should be searched for
123    /// (format: "file.section.option").
124    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    /// Get value of a plugin option
145    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    /// Set the value of a plugin option
161    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        // Drop the sections first.
181        self.sections.clear();
182
183        unsafe {
184            // Now drop the config.
185            Box::from_raw(self._config_data);
186            config_free(self.inner.ptr)
187        };
188    }
189}
190
191impl Config {
192    /// Create a new Weechat configuration file, returns a `Config` object.
193    /// The configuration file is freed when the `Config` object is dropped.
194    ///
195    /// # Arguments
196    /// * `name` - Name of the new configuration file
197    ///
198    /// # Panics
199    ///
200    /// Panics if the method is not called from the main Weechat thread.
201    pub fn new(name: &str) -> Result<Config, ()> {
202        Config::config_new_helper(name, None)
203    }
204
205    /// Create a new Weechat configuration file, returns a `Config` object.
206    /// The configuration file is freed when the `Config` object is dropped.
207    ///
208    /// # Arguments
209    ///
210    /// * `name` - Name of the new configuration file
211    ///
212    /// * `reload_callback` - Callback that will be called when the
213    /// configuration file is reloaded.
214    ///
215    /// # Examples
216    ///
217    /// ```no_run
218    /// use weechat::Weechat;
219    /// use weechat::config::{Conf, Config};
220    ///
221    /// let config = Config::new_with_callback("server_buffer",
222    ///     |weechat: &Weechat, conf: &Conf| {
223    ///         Weechat::print("Config was reloaded");
224    ///     }
225    /// );
226    /// ```
227    ///
228    /// # Panics
229    ///
230    /// Panics if the method is not called from the main Weechat thread.
231    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    /// Read the configuration file from the disk.
352    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    /// Write the configuration file to the disk.
362    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    /// Create a new section in the configuration file.
372    ///
373    /// # Arguments
374    ///
375    /// * `section_settings` - Settings that decide how the section will be
376    /// created.
377    ///
378    /// # Panics
379    ///
380    /// Panics if the method is not called from the main Weechat thread.
381    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(&section_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(&section));
556
557        self.sections.insert(section_settings.name.clone(), section);
558        let section = &self.sections[&section_settings.name];
559
560        Ok(SectionHandleMut {
561            inner: section.borrow_mut(),
562        })
563    }
564
565    /// Search the configuration object for a section and borrow it.
566    ///
567    /// Returns a handle to a section if one is found, None otherwise.
568    ///
569    /// # Arguments
570    ///
571    /// * `section_name` - The name of the section that should be retrieved.
572    ///
573    /// # Panics
574    ///
575    /// This will panic if it is being called in a section read/write callback
576    /// of the same section that is being retrieved or if the section is already
577    /// mutably borrowed.
578    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    /// Search the configuration object for a section and borrow it mutably.
589    ///
590    /// Returns a handle to a section if one is found, None otherwise.
591    ///
592    /// # Arguments
593    ///
594    /// * `section_name` - The name of the section that should be retrieved.
595    ///
596    /// # Panics
597    ///
598    /// This will panic if it is being called in a section read/write callback
599    /// of the same section that is being retrieved or if the section is already
600    /// borrowed.
601    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    /// Write the section header to the configuration file.
614    pub fn write_section(&self, section_name: &str) {
615        self.write(section_name, None)
616    }
617
618    /// Write a line to the configuration file.
619    ///
620    /// # Arguments
621    ///
622    /// * `key` - The key of the option that will be written. Can be a
623    ///     section name.
624    ///
625    /// * `value` - The value of the option that will be written.
626    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    /// Write a line in a configuration file with option and its value.
647    ///
648    /// # Arguments
649    ///
650    /// * `option` - The option that will be written to the configuration file.
651    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}