use libc::c_void;
use libc::time_t;
use std::cell::RefCell;
#[cfg(feature = "threadsafe")]
use std::thread;
use std::{fmt, error};
use std::rc::Rc;
#[cfg(feature = "threadsafe")]
use crate::MAIN_THREAD_ID;
use crate::context::*;
use crate::hexchat::Hexchat;
use crate::hexchat_entry_points::PHEXCHAT;
use crate::list_item::ListItem;
use crate::utils::*;
use FieldValue::*;
use ListError::*;
#[derive(Clone)]
pub struct ListIterator {
field_names : Rc<Vec<String>>,
data : Rc<RefCell<ListIteratorData>>,
}
impl ListIterator {
pub fn new(list_name: &str) -> Option<Self> {
#[cfg(feature = "threadsafe")]
assert!(thread::current().id() == unsafe { MAIN_THREAD_ID.unwrap() },
"ListIterator::new() must be called from the main thread.");
let name = str2cstring(list_name);
let hc = unsafe { &*PHEXCHAT };
let list_ptr = unsafe { (hc.c_list_get)(hc, name.as_ptr()) };
if !list_ptr.is_null() {
let mut field_types = vec![];
let mut field_names = vec![];
unsafe {
let c_fields = (hc.c_list_fields)(hc, name.as_ptr());
let mut c_field = *c_fields;
let mut i = 0;
while !c_field.is_null() && *c_field != 0 {
let c_typ = *c_field;
let field = pchar2string(c_field.add(1));
field_types.push((field.clone(), c_typ));
field_names.push(field);
i += 1;
c_field = *c_fields.add(i);
}
field_names.sort();
}
Some( ListIterator {
field_names: Rc::new(field_names),
data: Rc::new(
RefCell::new(
ListIteratorData {
list_name : list_name.to_string(),
hc,
field_types,
list_ptr,
started: false,
}))})
} else {
None
}
}
pub fn to_vec(&self) -> Vec<ListItem> {
self.map(ListItem::from).collect()
}
pub fn get_item(&self) -> ListItem {
ListItem::from(self)
}
pub fn get_field_names(&self) -> &[String] {
&self.field_names
}
pub fn get_field(&self, name: &str) -> Result<FieldValue, ListError> {
let cell = &*self.data;
let data = &*cell.borrow();
if data.started {
let field_type_opt = data.get_type(name);
if let Some(field_type) = field_type_opt {
self.get_field_pvt(data, name, field_type)
} else {
Err(UnknownField(name.to_owned()))
}
} else {
Err(NotStarted("The iterator must have `.next()` invoked \
before fields can be accessed.".to_string()))
}
}
pub fn traverse<F>(&self, mut visitor: F)
where
F: FnMut(&String, &FieldValue, bool) -> bool
{
let cell = &*self.data;
'main: for _item in self {
let data = &*cell.borrow();
let mut start = true;
for (field_name, field_type) in &data.field_types {
let value = self.get_field_pvt(data, field_name, *field_type)
.unwrap();
if !visitor(field_name, &value, start) {
break 'main;
}
start = false;
}
}
}
fn get_field_pvt(&self, data: &ListIteratorData, name: &str, field_type: i8)
-> Result<FieldValue, ListError>
{
let c_name = str2cstring(name);
unsafe {
match field_type {
115 => {
let val = (data.hc.c_list_str)(data.hc,
data.list_ptr,
c_name.as_ptr());
Ok(StringVal(pchar2string(val)))
},
105 => {
let val = (data.hc.c_list_int)(data.hc,
data.list_ptr,
c_name.as_ptr());
Ok(IntVal(val))
},
112 => {
let networkcstr = str2cstring("network");
let channelcstr = str2cstring("channel");
if name.to_lowercase() == "context" {
let network = (data.hc.c_list_str)(data.hc,
data.list_ptr,
networkcstr
.as_ptr());
let channel = (data.hc.c_list_str)(data.hc,
data.list_ptr,
channelcstr
.as_ptr());
if let Some(c) = Context::find(&pchar2string(network),
&pchar2string(channel))
{
Ok(ContextVal(c))
} else {
Err(NotAvailable("Context unavailable."
.to_string()))
}
} else {
let ptr = (data.hc.c_list_str)(data.hc,
data.list_ptr,
c_name.as_ptr());
Ok(PointerVal(ptr as u64))
}
},
116 => {
let val = (data.hc.c_list_time)(data.hc,
data.list_ptr,
c_name.as_ptr());
Ok(TimeVal(val))
},
_ => {
Err(UnknownType(field_type))
},
}
}
}
}
impl Iterator for ListIterator {
type Item = Self;
fn next(&mut self) -> Option<Self::Item> {
let data = &mut *self.data.borrow_mut();
data.started = true;
if unsafe { (data.hc.c_list_next)(data.hc, data.list_ptr) != 0 } {
Some(self.clone())
} else {
None
}
}
}
impl Iterator for &ListIterator {
type Item = Self;
fn next(&mut self) -> Option<Self::Item> {
let data = &mut *self.data.borrow_mut();
data.started = true;
if unsafe { (data.hc.c_list_next)(data.hc, data.list_ptr) != 0 } {
Some(self)
} else {
None
}
}
}
#[allow(dead_code)]
struct ListIteratorData {
list_name : String,
field_types : Vec<(String, i8)>,
hc : &'static Hexchat,
list_ptr : *const c_void,
started : bool,
}
impl ListIteratorData {
#[inline]
fn get_type(&self, field: &str) -> Option<i8> {
let fields = &self.field_types;
Some(fields.iter().find(|f| f.0 == field)?.1)
}
}
impl Drop for ListIteratorData {
fn drop(&mut self) {
unsafe {
(self.hc.c_list_free)(self.hc, self.list_ptr);
}
}
}
#[derive(Debug, Clone)]
pub enum FieldValue {
StringVal (String),
IntVal (i32),
PointerVal (u64),
ContextVal (Context),
TimeVal (time_t),
}
impl fmt::Display for FieldValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
StringVal(s) => { write!(f, "{}", s) },
IntVal(i) => { write!(f, "{:?}", i) },
PointerVal(p) => { write!(f, "{:?}", p) },
TimeVal(t) => { write!(f, "{:?}", t) },
ContextVal(c) => { write!(f, "ContextVal({})", c) },
}
}
}
#[derive(Debug, Clone)]
pub enum ListError {
UnknownList(String),
UnknownField(String),
UnknownType(i8),
NotStarted(String),
NotAvailable(String),
ListIteratorDropped(String),
ThreadSafeOperationFailed(String),
}
impl error::Error for ListError {}
impl fmt::Display for ListError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut s = format!("{:?}", self);
s.retain(|c| c != '"');
write!(f, "{}", s)
}
}