hexchat_api/
threadsafe_list_iterator.rs

1#![cfg(feature = "threadsafe")]
2
3//! This module provides a thread-safe wrapper class for the Hexchat
4//! `ListIterator`. The methods it provides can be invoked from threads other
5//! than the Hexchat main thread safely.
6
7use std::sync::Arc;
8use std::fmt;
9use std::sync::RwLock;
10
11use libc::time_t;
12use send_wrapper::SendWrapper;
13
14use crate::HexchatError;
15use crate::list_item::*;
16use crate::list_iterator::*;
17use crate::thread_facilities::*;
18use crate::threadsafe_context::*;
19
20use HexchatError::*;
21
22const DROPPED_ERR: &str = "ListIterator dropped from threadsafe context.";
23
24/// A thread-safe wrapper class for the Hexchat `ListIterator`. The methods
25/// provided, internally execute on the Hexchat main thread without any
26/// additional code necessary to make that happen in the client code.
27///
28/// Objects of this struct can iterate over Hexchat's lists from other threads.
29/// Because each operation is delegated to the main thread from the current
30/// thread, they are not going to be as fast as the methods of `ListIterator`
31/// used exclusively in the main thread without switching to other threads.
32/// The plus to objects of this struct iterating and printing long lists is they
33/// won't halt or lag the Hexchat UI. The list can print item by item, while
34/// while Hexchat is able to handle its traffic, printing chat messages, and
35/// other tasks.
36///
37#[derive(Clone)]
38pub struct ThreadSafeListIterator {
39    list_iter: Arc<RwLock<Option<SendWrapper<ListIterator>>>>,
40}
41
42unsafe impl Send for ThreadSafeListIterator {}
43unsafe impl Sync for ThreadSafeListIterator {}
44
45impl ThreadSafeListIterator {
46    /// Creates a new wraper object for a `ListIterator`.
47    /// # Arguments
48    /// * `list_iter` - The list iterator to wrap.
49    ///
50    pub (crate)
51    fn create(list_iter: ListIterator) -> Self {
52        Self {
53            list_iter: Arc::new(RwLock::new(Some(SendWrapper::new(list_iter))))
54        }
55    }
56
57    /// Produces the list associated with `name`.
58    /// # Arguments
59    /// * `name` - The name of the list to get.
60    /// # Returns
61    /// * A thread-safe object representing one of Hexchat's internal lists.
62    ///
63    pub fn new(name: &str) -> Result<Self, HexchatError> {
64        let cname = name.to_string();
65        main_thread(move |_| {
66            ListIterator::new(&cname).map(|list|
67                ThreadSafeListIterator {
68                    list_iter:
69                        Arc::new(RwLock::new(Some(SendWrapper::new(list))))
70                })}
71        ).get().and_then(|res| res.ok_or_else(|| ListNotFound(name.into())))
72    }
73
74    /// Returns a vector of the names of the fields supported by the list
75    /// the list iterator represents.
76    ///
77    pub fn get_field_names(&self) -> Result<Vec<String>, HexchatError> {
78        let me = self.clone();
79        main_thread(move |_| {
80            Ok(me.list_iter.read().unwrap().as_ref()
81                 .ok_or_else(|| ListIteratorDropped(DROPPED_ERR.into()))?
82                 .get_field_names().to_vec())
83        }).get().and_then(|r| r)
84    }
85
86    /// Constructs a vector of list items on the main thread all at once. The
87    /// iterator will be spent after the operation.
88    ///
89    pub fn to_vec(&self) -> Result<Vec<ListItem>, HexchatError> {
90        let me = self.clone();
91        main_thread(move |_| {
92            Ok(me.list_iter.read().unwrap().as_ref()
93                 .ok_or_else(|| ListIteratorDropped(DROPPED_ERR.into()))?
94                 .to_vec())
95        }).get().and_then(|r| r)
96    }
97
98    /// Creates a `ListItem` from the field data at the current position in the
99    /// list.
100    ///
101    pub fn get_item(&self) -> Result<ListItem, HexchatError> {
102        let me = self.clone();
103        main_thread(move |_| {
104            Ok(me.list_iter.read().unwrap().as_ref()
105                 .ok_or_else(|| ListIteratorDropped(DROPPED_ERR.into()))?
106                 .get_item())
107        }).get().and_then(|r| r)
108    }
109
110    /// Returns the value for the field of the requested name.
111    ///
112    /// # Arguments
113    /// * `name` - The name of the field to retrieve the value for.
114    ///
115    /// # Returns
116    /// * A `Result` where `Ok` holds the field data, and `Err` indicates the
117    ///   field doesn't exist or some other problem. See `ListError` for the
118    ///   error types. The values are returned as `FieldValue` tuples that hold
119    ///   the requested data.
120    ///
121    pub fn get_field(&self, name: &str) 
122        -> Result<ThreadSafeFieldValue, HexchatError>
123    {
124        use FieldValue as FV;
125        use ThreadSafeFieldValue as TSFV;
126
127        let name = name.to_string();
128        let me = self.clone();
129        main_thread(move |_| {
130            if let Some(iter) = me.list_iter.read().unwrap().as_ref() {
131                match iter.get_field(&name) {
132                    Ok(field_val) => {
133                        match field_val {
134                            FV::StringVal(s) => {
135                                Ok(TSFV::StringVal(s))
136                            },
137                            FV::IntVal(i) => {
138                                Ok(TSFV::IntVal(i))
139                            },
140                            FV::PointerVal(pv) => {
141                                Ok(TSFV::PointerVal(pv))
142                            },
143                            FV::ContextVal(ctx) => {
144                                Ok(TSFV::ContextVal(
145                                    ThreadSafeContext::new(ctx)
146                                ))
147                            },
148                            FV::TimeVal(time) => {
149                                Ok(TSFV::TimeVal(time))
150                            }
151                        }
152                    },
153                    Err(err) => {
154                        Err(err)
155                    },
156                }
157            } else {
158                Err(ListIteratorDropped(DROPPED_ERR.into()))
159            }
160        }).get().and_then(|r| r)
161    }
162}
163
164impl Iterator for ThreadSafeListIterator {
165    type Item = Self;
166    fn next(&mut self) -> Option<Self::Item> {
167        let me = self.clone();
168        main_thread(move |_| {
169            if let Some(iter) = me.list_iter.write().unwrap().as_mut() {
170                iter.next().map(|it| ThreadSafeListIterator::create(it.clone()))
171            } else {
172                None
173            }
174        }).get().unwrap_or(None)
175    }
176}
177
178impl Iterator for &ThreadSafeListIterator {
179    type Item = Self;
180    fn next(&mut self) -> Option<Self::Item> {
181        let me = self.clone();
182        let has_more = main_thread(move |_| {
183            me.list_iter.write().unwrap().as_mut()
184                        .is_some_and(|it| it.next().is_some())
185        }).get().unwrap_or(false);
186        if has_more {
187            Some(self)
188        } else {
189            None
190        }
191    }
192}
193
194impl Drop for ThreadSafeListIterator {
195    fn drop(&mut self) {
196        if Arc::strong_count(&self.list_iter) <= 1
197            && self.list_iter.read().unwrap().is_some() {
198            let me = self.clone();
199            main_thread(move |_| {
200                me.list_iter.write().unwrap().take();
201            });
202        }
203    }
204}
205
206/// Thread-safe versions of the `FieldValue` variants provided by
207/// `ListIterator`.
208/// # Variants
209/// * StringVal    - A string has been returned. The enum item holds its value.
210/// * IntVal       - Integer value.
211/// * PointerVal   - A `u64` value representing the value of a pointer.
212/// * ContextVal   - Holds a `ThreadSafeContext` that can be used from other
213///                  threads.
214/// * TimeVal      - Holds a `i64` value which can be cast to a `time_t` numeric
215///                  value.
216///
217#[derive(Debug, Clone)]
218pub enum ThreadSafeFieldValue {
219    StringVal   (String),
220    IntVal      (i32),
221    PointerVal  (u64),
222    ContextVal  (ThreadSafeContext),
223    TimeVal     (time_t),
224}
225
226unsafe impl Send for ThreadSafeFieldValue {}
227unsafe impl Sync for ThreadSafeFieldValue {}
228
229impl fmt::Display for ThreadSafeFieldValue {
230    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
231        use ThreadSafeFieldValue::*;
232        match self {
233            StringVal(s)   => { write!(f, "{}",   s) },
234            IntVal(i)      => { write!(f, "{:?}", i) },
235            PointerVal(p)  => { write!(f, "{:?}", p) },
236            TimeVal(t)     => { write!(f, "{:?}", t) },
237            ContextVal(c)  => { write!(f, "ContextVal({})", c) },
238        }
239    }
240}
241
242use ThreadSafeFieldValue::*;
243
244impl ThreadSafeFieldValue {
245    /// Convert a StringVal variant to a String. FieldValue also implements
246    /// `From<String>` so you can also use `let s: String = fv.into();` 
247    /// to convert.
248    /// 
249    pub fn str(self) -> String {
250        match self {
251            StringVal(s) => s,
252            _ => panic!("Can't convert {:?} to String.", self),
253        }
254    }
255    /// Convert an IntVal variant to an i32. FieldValue also implements
256    /// `From<i32>` so you can also use `let i: i32 = fv.into();`
257    /// to convert.
258    /// 
259    pub fn int(self) -> i32 {
260        match self {
261            IntVal(i) => i,
262            _ => panic!("Can't convert {:?} to i32.", self),
263        }
264    }
265    /// Convert a PointerVal variant to a u64. FieldValue also implements
266    /// `From<u64>` so you can also use `let p: u64 = fv.into();`
267    /// to convert.
268    /// 
269    pub fn ptr(self) -> u64 {
270        match self {
271            PointerVal(p) => p,
272            _ => panic!("Can't convert {:?} to u64.", self),
273        }
274    }
275    /// Convert a TimeVal variant to a time_t (i64). FieldValue also implements
276    /// `From<time_t>` so you can also use `let t: time_t = fv.into();`
277    /// to convert.
278    /// 
279    pub fn time(self) -> time_t {
280        match self {
281            TimeVal(t) => t,
282            _ => panic!("Can't convert {:?} to time_t.", self),
283        }
284    }
285    /// Convert a ContextVal variant to a Context. FieldValue also implements
286    /// `From<Context>` so you can also use `let c: Context = fv.into();`
287    /// to convert.
288    /// 
289    pub fn ctx(self) -> ThreadSafeContext {
290        match self {
291            ContextVal(c) => c,
292            _ => panic!("Can't convert {:?} to Context.", self),
293        }
294    }
295}
296
297impl From<ThreadSafeFieldValue> for String {
298    fn from(v: ThreadSafeFieldValue) -> Self {
299        v.str()
300    }
301}
302
303impl From<ThreadSafeFieldValue> for i32 {
304    fn from(v: ThreadSafeFieldValue) -> Self {
305        v.int()
306    }
307}
308
309impl From<ThreadSafeFieldValue> for u64 {
310    fn from(v: ThreadSafeFieldValue) -> Self {
311        v.ptr()
312    }
313}
314
315impl From<ThreadSafeFieldValue> for i64 {
316    fn from(v: ThreadSafeFieldValue) -> Self {
317        v.time().into()
318    }
319}
320
321impl From<ThreadSafeFieldValue> for ThreadSafeContext {
322    fn from(v: ThreadSafeFieldValue) -> Self {
323        v.ctx()
324    }
325}
326