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}