hexchat_api/
list_iterator.rs

1
2//! This module provides a Rust wrapper for Hexchat's list related API. A list
3//! can be accessed by creating a `ListIterator` by passing the name of one of
4//! the list types to the constructor. The iterator itself can be used to
5//! access list item fields by name using `get_field()`. Fields can't be
6//! accessed until `next()` has been invoked to advance the internal pointer.
7//! This iterator can be used in loops like any other iterator, or `collect()`
8//! can be called to generate a vector or other collections.
9
10use libc::c_void;
11use libc::time_t;
12use core::panic;
13use std::cell::RefCell;
14use std::fmt;
15#[cfg(feature = "threadsafe")]
16use std::thread;
17use std::rc::Rc;
18
19#[cfg(feature = "threadsafe")]
20use crate::MAIN_THREAD_ID;
21use crate::context::*;
22use crate::errors::HexchatError;
23use crate::hexchat::Hexchat;
24use crate::hexchat_entry_points::PHEXCHAT;
25use crate::list_item::ListItem;
26use crate::utils::*;
27
28// Local types.
29use FieldValue::*;
30use HexchatError::*;
31
32/// The `ListIterator` wraps the list pointer and related functions of Hexchat.
33/// It provides are more Rust OO interface. The iterator returns clones of
34/// itself that can be used to access the current list item's fields through
35/// `get_field()`. The list iterator object is internally a smart pointer,
36/// among other things. You can clone it if you need multiple references to
37/// a list.
38#[derive(Clone)]
39pub struct ListIterator {
40    field_names : Rc<Vec<String>>,
41    data        : Rc<RefCell<ListIteratorData>>,
42}
43
44impl ListIterator {
45    /// Creates a new list iterator instance.`
46    /// # Arguments
47    /// * `list_name` - The name of one of the Hexchat lists ('channels', 'dcc',
48    ///                'ignore', 'notify', 'users').
49    /// # Returns
50    /// * An iterator to the list of the requested name, or `None` if the list
51    ///   doesn't exist.
52    ///
53    pub fn new(list_name: &str) -> Option<Self> {
54        #[cfg(feature = "threadsafe")]
55        assert!(thread::current().id() == unsafe { MAIN_THREAD_ID.unwrap() },
56                "ListIterator::new() must be called from the main thread.");
57        let name     = str2cstring(list_name);
58        let hc       = unsafe { &*PHEXCHAT };
59        let list_ptr = unsafe { (hc.c_list_get)(hc, name.as_ptr()) };
60        if !list_ptr.is_null() {
61            let mut field_types = vec![];
62            let mut field_names = vec![];
63            unsafe {
64                // Get the list pointer to field names.
65                let     c_fields = (hc.c_list_fields)(hc, name.as_ptr());
66                let mut c_field  = *c_fields;
67                let mut i        = 0;
68
69                // Build a mapping between field names and field types.
70                while !c_field.is_null() && *c_field != 0 {
71                    // The first char of the name is the type.
72                    let c_typ = *c_field;
73                    // Advance the char pointer once to get the name w/o type
74                    // char.
75                    let field = pchar2string(c_field.add(1));
76
77                    field_types.push((field.clone(), c_typ));
78                    field_names.push(field);
79                    i += 1;
80                    c_field = *c_fields.add(i);
81                }
82                field_names.sort();
83            }
84            Some( ListIterator {
85                    field_names: Rc::new(field_names),
86                    data: Rc::new(
87                        RefCell::new(
88                            ListIteratorData {
89                                list_name : list_name.to_string(),
90                                hc,
91                                field_types,
92                                list_ptr,
93                                started: false,
94                            }))})
95        } else {
96            None
97        }
98    }
99
100    /// Eagerly constructs a vector of `ListItem`s. The iterator will be spent
101    /// afterward.
102    ///
103    pub fn to_vec(&self) -> Vec<ListItem> {
104        self.map(ListItem::from).collect()
105    }
106
107    /// Creates a `ListItem` from the field data at the current position in
108    /// the list.
109    ///
110    pub fn get_item(&self) -> ListItem {
111        ListItem::from(self)
112    }
113
114    /// Returns a slice containing the field names of the list items.
115    ///
116    pub fn get_field_names(&self) -> &[String] {
117        &self.field_names
118    }
119
120    /// Returns the value for the field of the requested name.
121    ///
122    /// # Arguments
123    /// * `name` - The name of the field to retrieve the value for.
124    ///
125    /// # Returns
126    /// * A `Result` where `Ok` holds the field data, and `Err` indicates the
127    ///   field doesn't exist or some other problem. See [HexchatError] for the
128    ///   error types. The values are returned as `FieldValue` tuples that hold
129    ///   the requested data.
130    ///
131    pub fn get_field(&self, name: &str) -> Result<FieldValue, HexchatError> {
132        let cell = &*self.data;
133        let data = &*cell.borrow();
134        if data.started {
135            let field_type_opt = data.get_type(name);
136            if let Some(field_type) = field_type_opt {
137                self.get_field_pvt(data, name, field_type)
138            } else {
139                Err(ListFieldNotFound(name.to_owned()))
140            }
141        } else {
142            Err(ListIteratorNotStarted("The iterator must have `.next()` \
143                                       invoked before fields can be accessed."
144                                       .to_string()))
145        }
146    }
147
148    /// Traverses a list while invoking the supplied callback to give the
149    /// record data. This is an alternative push model approach to accessing
150    /// the list data sequentially. The visitor callback has the form:
151    ///
152    /// ```FnMut(&String, &FieldValue, bool) -> bool```
153    ///
154    /// The first parameter is the field name, followed by its value,
155    /// then a boolean that when `true` indicates the start of a new record.
156    /// The callback returns `true` to keep going. If it returns `false`,
157    /// the traversal stops.
158    ///
159    pub fn traverse<F>(&self, mut visitor: F)
160    where
161        F: FnMut(&String, &FieldValue, bool) -> bool
162    {
163        let cell = &*self.data;
164
165        'main: for _item in self {
166            let data = &*cell.borrow();
167            let mut start = true;
168            for (field_name, field_type) in &data.field_types {
169                let value = self.get_field_pvt(data, field_name, *field_type)
170                                .unwrap();
171                if !visitor(field_name, &value, start) {
172                    break 'main;
173                }
174                start = false;
175            }
176        }
177    }
178
179    /// Internal method that gets the value of a field given the field name
180    /// and type. This should remain private in scope to this file as using
181    /// the wrong type when accessing fields can cause instability. This method
182    /// is invoked by `traverse()` and `get_field()`.
183    ///
184    fn get_field_pvt(&self, data: &ListIteratorData, name: &str, field_type: i8)
185        -> Result<FieldValue, HexchatError>
186    {
187        let c_name = str2cstring(name);
188        unsafe {
189            match field_type {
190                // Match against the ascii values for one of 's', 'i',
191                //'p', or 't'.
192                115 /* 's' (string) */ => {
193                    let val = (data.hc.c_list_str)(data.hc,
194                                                   data.list_ptr,
195                                                   c_name.as_ptr());
196                    Ok(StringVal(pchar2string(val)))
197                },
198                105 /* 'i' (integer) */ => {
199                    let val = (data.hc.c_list_int)(data.hc,
200                                                   data.list_ptr,
201                                                   c_name.as_ptr());
202                    Ok(IntVal(val))
203                },
204                112 /* 'p' (pointer) */ => {
205                    let networkcstr = str2cstring("network");
206                    let channelcstr = str2cstring("channel");
207                    if name.to_lowercase() == "context" {
208                        let network = (data.hc.c_list_str)(data.hc,
209                                                           data.list_ptr,
210                                                           networkcstr
211                                                           .as_ptr());
212                        let channel = (data.hc.c_list_str)(data.hc,
213                                                           data.list_ptr,
214                                                           channelcstr
215                                                           .as_ptr());
216                        if let Some(c) = Context::find(&pchar2string(network),
217                                                       &pchar2string(channel))
218                        {
219                            Ok(ContextVal(c))
220                        } else {
221                            Err(ContextAcquisitionFailed("Context unavailable."
222                                                         .to_string()))
223                        }
224                    } else {
225                        let ptr = (data.hc.c_list_str)(data.hc,
226                                                       data.list_ptr,
227                                                       c_name.as_ptr());
228                        Ok(PointerVal(ptr as u64))
229                    }
230                },
231                116 /* 't' (time) */ => {
232                    let val = (data.hc.c_list_time)(data.hc,
233                                                    data.list_ptr,
234                                                    c_name.as_ptr());
235                    Ok(TimeVal(val))
236                },
237                _ => {
238                    // This should never happen.
239                    Err(UnknownType(field_type.to_string()))
240                },
241            }
242        }
243    }
244}
245
246impl Iterator for ListIterator {
247    type Item = Self;
248
249    /// The standard method for iterators. The items returned are clones of the
250    /// iterator itself. Calling `next` on the iterator advances an internal
251    /// pointer used to access Hexchat data.
252    ///
253    fn next(&mut self) -> Option<Self::Item> {
254        let data = &mut *self.data.borrow_mut();
255        data.started = true;
256        if unsafe { (data.hc.c_list_next)(data.hc, data.list_ptr) != 0 } {
257            Some(self.clone())
258        } else {
259            None
260        }
261    }
262}
263
264impl Iterator for &ListIterator {
265    type Item = Self;
266
267    fn next(&mut self) -> Option<Self::Item> {
268        let data = &mut *self.data.borrow_mut();
269        data.started = true;
270        if unsafe { (data.hc.c_list_next)(data.hc, data.list_ptr) != 0 } {
271            Some(self)
272        } else {
273            None
274        }
275    }
276}
277
278/// Holds the iterator state and maps the field names to their data type.
279/// # Fields
280/// * `field_types` - A mapping of field names to data type.
281/// * `field_names` - The list of field names for the particular list.
282/// * `list_ptr`    - A raw pointer to a list internal to Hexchat.
283/// * `hc`          - The Hexchat pointer.
284/// * `started`     - true if `next()` has aready been called on the Rust iter.
285///
286#[allow(dead_code)]
287struct ListIteratorData {
288    list_name   : String,
289    field_types : Vec<(String, i8)>,
290    hc          : &'static Hexchat,
291    list_ptr    : *const c_void,
292    started     : bool,
293}
294
295impl ListIteratorData {
296    /// Returns the type of the given field. The field lists are short, so
297    /// a simple comparisons search for the right item may be quicker than
298    /// a HashMap's hashings and lookups.
299    #[inline]
300    fn get_type(&self, field: &str) -> Option<i8> {
301        let fields = &self.field_types;
302        Some(fields.iter().find(|f| f.0 == field)?.1)
303    }
304}
305
306impl Drop for ListIteratorData {
307    /// Frees the Hexchat internal list pointer.
308    fn drop(&mut self) {
309        unsafe {
310            (self.hc.c_list_free)(self.hc, self.list_ptr);
311        }
312    }
313}
314
315/// # Field Data Types
316/// * String    - A string has been returned. The enum item holds its value.
317/// * Int       - Integer value.
318/// * Pointer   - This will be updated to be Context soon.
319/// * Time      - Holds a `time_t` numeric value.
320///
321#[derive(Debug, Clone)]
322pub enum FieldValue {
323    StringVal    (String),
324    IntVal       (i32),
325    PointerVal   (u64),
326    ContextVal   (Context),
327    TimeVal      (time_t),
328}
329
330impl FieldValue {
331    /// Convert a StringVal variant to a String. FieldValue also implements
332    /// `From<String>`` so you can also use `let s: String = fv.into();` 
333    /// to convert.
334    /// 
335    pub fn str(self) -> String {
336        match self {
337            StringVal(s) => s,
338            _ => panic!("Can't convert {:?} to String.", self),
339        }
340    }
341    /// Convert an IntVal variant to an i32. FieldValue also implements
342    /// `From<i32>` so you can also use `let i: i32 = fv.into();`
343    /// to convert.
344    /// 
345    pub fn int(self) -> i32 {
346        match self {
347            IntVal(i) => i,
348            _ => panic!("Can't convert {:?} to i32.", self),
349        }
350    }
351    /// Convert a PointerVal variant to a u64. FieldValue also implements
352    /// `From<u64>` so you can also use `let p: u64 = fv.into();`
353    /// to convert.
354    /// 
355    pub fn ptr(self) -> u64 {
356        match self {
357            PointerVal(p) => p,
358            _ => panic!("Can't convert {:?} to u64.", self),
359        }
360    }
361    /// Convert a TimeVal variant to a time_t (i64). FieldValue also implements
362    /// `From<time_t>` so you can also use `let t: time_t = fv.into();`
363    /// to convert.
364    /// 
365    pub fn time(self) -> time_t {
366        match self {
367            TimeVal(t) => t,
368            _ => panic!("Can't convert {:?} to time_t.", self),
369        }
370    }
371    /// Convert a ContextVal variant to a Context. FieldValue also implements
372    /// `From<Context>` so you can also use `let c: Context = fv.into();`
373    /// to convert.
374    /// 
375    pub fn ctx(self) -> Context {
376        match self {
377            ContextVal(c) => c,
378            _ => panic!("Can't convert {:?} to Context.", self),
379        }
380    }
381}
382
383impl From<FieldValue> for String {
384    fn from(v: FieldValue) -> Self {
385        v.str()
386    }
387}
388
389impl From<FieldValue> for i32 {
390    fn from(v: FieldValue) -> Self {
391        v.int()
392    }
393}
394
395impl From<FieldValue> for u64 {
396    fn from(v: FieldValue) -> Self {
397        v.ptr()
398    }
399}
400
401impl From<FieldValue> for i64 {
402    fn from(v: FieldValue) -> Self {
403        v.time().into()
404    }
405}
406
407impl From<FieldValue> for Context {
408    fn from(v: FieldValue) -> Self {
409        v.ctx()
410    }
411}
412
413
414impl fmt::Display for FieldValue {
415    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
416        match self {
417            StringVal(s)   => { write!(f, "{}",   s) },
418            IntVal(i)      => { write!(f, "{:?}", i) },
419            PointerVal(p)  => { write!(f, "{:?}", p) },
420            TimeVal(t)     => { write!(f, "{:?}", t) },
421            ContextVal(c)  => { write!(f, "ContextVal({})", c) },
422        }
423    }
424}