summary refs log tree commit diff stats
path: root/src/lib.rs
diff options
context:
space:
mode:
authorSoniEx2 <endermoneymod@gmail.com>2022-04-30 22:23:31 -0300
committerSoniEx2 <endermoneymod@gmail.com>2022-04-30 23:38:50 -0300
commit23ba75117815bc384bd4d678b5c5c2091edffcbb (patch)
treebb11fa24f86f5ffa3264e5ff66997360e86fe8a5 /src/lib.rs
parentf56ae877c32418630485d71a58b76edb64e96200 (diff)
Add pluginpref impl
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs339
1 files changed, 331 insertions, 8 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 8f72571..5f80c1c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -27,6 +27,12 @@
 //!
 //! This caveat does not apply to static assets (`static FOO: &'static str`, for example), but it
 //! does apply to thread-local storage.
+//!
+//! # Panics
+//!
+//! Unless otherwise stated, all functions in this crate taking strings (be
+//! that `&str`, `String`, etc) panic when the string contains embedded `NUL`
+//! characters (`\0`).
 //! 
 //! # Examples
 //!
@@ -167,12 +173,12 @@
  *     -[x] hexchat_find_context
  *     -[x] hexchat_get_context
  *     -[x] hexchat_set_context
- *     -[ ] hexchat_pluginpref_set_str
- *     -[ ] hexchat_pluginpref_get_str
- *     -[ ] hexchat_pluginpref_set_int
- *     -[ ] hexchat_pluginpref_get_int
- *     -[ ] hexchat_pluginpref_delete
- *     -[ ] hexchat_pluginpref_list
+ *     -[x] hexchat_pluginpref_set_str
+ *     -[x] hexchat_pluginpref_get_str
+ *     -[x] ~~hexchat_pluginpref_set_int~~ not available - use `format!`
+ *     -[x] ~~hexchat_pluginpref_get_int~~ not available - use `str::parse`
+ *     -[x] hexchat_pluginpref_delete
+ *     -[x] hexchat_pluginpref_list
  *     -[x] hexchat_plugingui_add
  *     -[x] ~~hexchat_plugingui_remove~~ not available - use Drop impls.
  * -[x] Wrap contexts within something we keep track of. Mark them invalid when contexts are
@@ -233,6 +239,7 @@ use std::fmt;
 use std::marker::PhantomData;
 use std::mem;
 use std::mem::ManuallyDrop;
+use std::mem::MaybeUninit;
 use std::panic::{AssertUnwindSafe, RefUnwindSafe, UnwindSafe, catch_unwind};
 use std::pin::Pin;
 use std::ptr;
@@ -435,12 +442,81 @@ pub struct Context<'ph> {
 // #[derive(Debug)] // doesn't work
 pub struct InvalidContextError<F>(F);
 
+/// An iterable list of plugin pref names.
+pub struct PluginPrefList {
+    inner: Vec<u8>,
+}
+
+/// An iterator over `PluginPrefList`.
+pub struct PluginPrefListIter<'a> {
+    list: Option<&'a [u8]>,
+}
+
 // Enums
 
+/// Errors returned by `pluginpref_*` functions.
+#[derive(Debug)]
+pub enum PluginPrefError {
+    /// The var contains a forbidden character: `[ ,=\n]` (space, comma,
+    /// equals, newline), is too long (max 126 bytes), is empty, or contains
+    /// trailing ASCII whitespace.
+    ///
+    /// Returned by anything that interacts with vars. `pluginpref_list` only
+    /// returns these during iteration.
+    InvalidVar,
+    /// The value starts with space.
+    ///
+    /// Returned by `pluginpref_set`.
+    InvalidValue,
+    /// The input (var + value) was too long after encoding.
+    ///
+    /// Returned by `pluginpref_set`.
+    TooLong,
+    /// The returned value was not valid UTF-8.
+    ///
+    /// Returned by `pluginpref_get`.
+    Utf8Error(::std::ffi::IntoStringError),
+    /// The returned var was not valid UTF-8.
+    ///
+    /// Returned while iterating a `pluginpref_list`.
+    VarUtf8Error(::std::str::Utf8Error),
+    /// The operation failed.
+    ///
+    /// Returned by anything that interacts with pluginprefs. Iterating a
+    /// `pluginpref_list` never returns this.
+    Failed,
+}
+
 // ***** //
 // impls //
 // ***** //
 
+impl<'a> Iterator for PluginPrefListIter<'a> {
+    type Item = Result<&'a str, PluginPrefError>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let mut splitter = self.list?.splitn(2, |x| *x == 0);
+        let ret = splitter.next().unwrap();
+        let ret = test_pluginpref_var(ret).and_then(|_| {
+            std::str::from_utf8(ret).map_err(|e| PluginPrefError::VarUtf8Error(e))
+        });
+        self.list = splitter.next();
+        Some(ret)
+    }
+}
+
+impl<'a> IntoIterator for &'a PluginPrefList {
+    type Item = Result<&'a str, PluginPrefError>;
+
+    type IntoIter = PluginPrefListIter<'a>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        PluginPrefListIter {
+            list: self.inner.split_last().map(|(_, x)| x),
+        }
+    }
+}
+
 impl<F> InvalidContextError<F> {
     /// Returns the closure wrapped within this error.
     pub fn get_closure(self) -> F {
@@ -1278,8 +1354,203 @@ impl<'ph> PluginHandle<'ph> {
         String::from_utf8(out).unwrap()
     }
 
-    // TODO pluginprefs but see: https://developer.gimp.org/api/2.0/glib/glib-String-Utility-Functions.html#g-strescape
-    // var.len() + g_strescape(value).len() + 3 < 512
+    /// Sets a pluginpref.
+    ///
+    /// Note: If two pluginprefs exist with the same name, but different ASCII
+    /// case, only one will be available through `pluginpref_get`.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the plugin is not registered.
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// use hexchat_unsafe_plugin::PluginHandle;
+    ///
+    /// fn set_str(ph: &PluginHandle<'_>, val: &str) {
+    ///     ph.pluginpref_set("string", val);
+    /// }
+    ///
+    /// fn set_int(ph: &PluginHandle<'_>, val: i32) {
+    ///     ph.pluginpref_set("int", &format!("{}", val));
+    /// }
+    /// ```
+    pub fn pluginpref_set(
+        &self,
+        var: &str,
+        value: &str,
+    ) -> Result<(), PluginPrefError> {
+        assert!(!self.info.name.is_null(), "must register plugin first");
+        if value.starts_with(' ') {
+            return Err(PluginPrefError::InvalidValue)
+        }
+        let var_len = var.len();
+        let var = check_pluginpref_var(var)?;
+        let mut val_len = value.len();
+        // octal is \000 - \377, adds 3 bytes
+        val_len += 3 * value.bytes().filter(|&x| x < 32 || x > 127).count();
+        // special chars are \t \n etc, or octal - 2 bytes
+        val_len -= 2 * value.bytes().filter(|&x| {
+            matches!(
+                x,
+                | b'\n'
+                | b'\r'
+                | b'\t'
+                | b'\x0C' // \f
+                | b'\x08' // \b
+            )
+        }).count();
+        // additionally \ and " also get an extra byte
+        val_len += value.bytes().filter(|&x| x == b'"' || x == b'\\').count();
+        // 3 bytes from " = ", < 512 because 511 + NUL
+        if var_len + val_len + 3 < 512 {
+            let value = CString::new(value).unwrap();
+            let success = unsafe {
+                ph_call!(
+                    hexchat_pluginpref_set_str(
+                        self,
+                        var.as_ptr(),
+                        value.as_ptr()
+                    )
+                )
+            } != 0;
+            if !success {
+                Err(PluginPrefError::Failed)
+            } else {
+                Ok(())
+            }
+        } else {
+            Err(PluginPrefError::TooLong)
+        }
+    }
+
+    /// Retrieves a pluginpref.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the plugin is not registered.
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// use hexchat_unsafe_plugin::PluginHandle;
+    ///
+    /// fn get_str(ph: &PluginHandle<'_>) -> String {
+    ///     ph.pluginpref_get("string").unwrap_or(String::new())
+    /// }
+    ///
+    /// fn get_int(ph: &PluginHandle<'_>) -> i32 {
+    ///     ph.pluginpref_get("int").unwrap_or(String::new()).parse().unwrap_or(-1)
+    /// }
+    /// ```
+    pub fn pluginpref_get(
+        &self,
+        var: &str,
+    ) -> Result<String, PluginPrefError> {
+        assert!(!self.info.name.is_null(), "must register plugin first");
+        let var = check_pluginpref_var(var)?;
+        let mut buffer: [MaybeUninit<c_char>; 512] = unsafe {
+            MaybeUninit::uninit().assume_init()
+        };
+        let success = unsafe {
+            ph_call!(
+                hexchat_pluginpref_get_str(
+                    self,
+                    var.as_ptr(),
+                    buffer.as_mut_ptr() as *mut _
+                )
+            )
+        } != 0;
+        if !success {
+            Err(PluginPrefError::Failed)
+        } else {
+            match unsafe {
+                CStr::from_ptr(buffer.as_ptr() as *const _)
+            }.to_owned().into_string() {
+                Ok(s) => Ok(s),
+                Err(e) => Err(PluginPrefError::Utf8Error(e)),
+            }
+        }
+    }
+
+    /// Removes a pluginpref.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the plugin is not registered.
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// use hexchat_unsafe_plugin::PluginHandle;
+    ///
+    /// fn del_str(ph: &PluginHandle<'_>) {
+    ///     let _ = ph.pluginpref_delete("string");
+    /// }
+    ///
+    /// fn del_int(ph: &PluginHandle<'_>) {
+    ///     let _ = ph.pluginpref_delete("int");
+    /// }
+    /// ```
+    pub fn pluginpref_delete(&self, var: &str) -> Result<(), PluginPrefError> {
+        assert!(!self.info.name.is_null(), "must register plugin first");
+        let var = check_pluginpref_var(var)?;
+        let success = unsafe {
+            ph_call!(hexchat_pluginpref_delete(self, var.as_ptr()))
+        } != 0;
+        if !success {
+            Err(PluginPrefError::Failed)
+        } else {
+            Ok(())
+        }
+    }
+
+    /// Lists pluginprefs.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the plugin is not registered.
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// use hexchat_unsafe_plugin::PluginHandle;
+    ///
+    /// fn list_prefs(ph: &PluginHandle<'_>) {
+    ///     match ph.pluginpref_list() {
+    ///         Ok(it) => for pref in &it {
+    ///             match pref {
+    ///                 Ok(pref) => write!(ph, "{}", pref),
+    ///                 _ => (),
+    ///             }
+    ///         },
+    ///         _ => (),
+    ///     }
+    /// }
+    /// ```
+    pub fn pluginpref_list(&self) -> Result<PluginPrefList, PluginPrefError> {
+        assert!(!self.info.name.is_null(), "must register plugin first");
+        let mut buffer: [MaybeUninit<c_char>; 4096] = unsafe {
+            MaybeUninit::uninit().assume_init()
+        };
+        let success = unsafe {
+            ph_call!(
+                hexchat_pluginpref_list(self, buffer.as_mut_ptr() as *mut _)
+            )
+        } != 0;
+        if !success {
+            Err(PluginPrefError::Failed)
+        } else {
+            let mut list = PluginPrefList {
+                inner: unsafe {
+                    CStr::from_ptr(buffer.as_ptr() as *const _)
+                }.to_owned().into_bytes(),
+            };
+            list.inner.iter_mut().for_each(|x| if *x == b',' { *x = 0; });
+            Ok(list)
+        }
+    }
 
     // ******* //
     // PRIVATE //
@@ -1689,6 +1960,37 @@ impl<'a, 'ph: 'a> ValidContext<'a, 'ph> {
     pub fn strip(&self, s: &str, strip: Strip) -> String {
         self.ph.strip(s, strip)
     }
+    /// Sets a pluginpref.
+    ///
+    /// See [`PluginHandle::pluginpref_set`].
+    pub fn pluginpref_set(
+        &self,
+        var: &str,
+        value: &str,
+    ) -> Result<(), PluginPrefError> {
+        self.ph.pluginpref_set(var, value)
+    }
+    /// Retrieves a pluginpref.
+    ///
+    /// See [`PluginHandle::pluginpref_get`].
+    pub fn pluginpref_get(
+        &self,
+        var: &str,
+    ) -> Result<String, PluginPrefError> {
+        self.ph.pluginpref_get(var)
+    }
+    /// Removes a pluginpref.
+    ///
+    /// See [`PluginHandle::pluginpref_delete`].
+    pub fn pluginpref_delete(&self, var: &str) -> Result<(), PluginPrefError> {
+        self.ph.pluginpref_delete(var)
+    }
+    /// Lists pluginprefs.
+    ///
+    /// See [`PluginHandle::pluginpref_list`].
+    pub fn pluginpref_list(&self) -> Result<PluginPrefList, PluginPrefError> {
+        self.ph.pluginpref_list()
+    }
 }
 
 // ******* //
@@ -1850,6 +2152,27 @@ unsafe fn pop_userdata<'ph>(ph: LtPhPtr<'ph>) -> Box<PhUserdata<'ph>> {
     Box::from_raw(mem::replace(&mut (*ph.ph).userdata, ptr::null_mut()) as *mut PhUserdata<'ph>)
 }
 
+fn test_pluginpref_var(var: &[u8]) -> Result<(), PluginPrefError> {
+    if var.len() >= 127
+    || var.len() < 1
+    // rust uses the same definition of ascii whitespace
+    || var.last().unwrap().is_ascii_whitespace()
+    || var.contains(&b' ')
+    || var.contains(&b'=')
+    || var.contains(&b'\n')
+    || var.contains(&b',') {
+        Err(PluginPrefError::InvalidVar)
+    } else {
+        Ok(())
+    }
+}
+
+fn check_pluginpref_var(var: impl Into<Vec<u8>>) -> Result<CString, PluginPrefError> {
+    let var = var.into();
+    test_pluginpref_var(&var)?;
+    Ok(CString::new(var).unwrap())
+}
+
 // *********************** //
 // PUBLIC OUT OF NECESSITY //
 // *********************** //