hexchat_api/
threadsafe_context.rs

1#![cfg(feature = "threadsafe")]
2
3//! A thread-safe version of `Context`. The methods of these objects will
4//! execute on the main thread of Hexchat. Invoking them is virutally the same
5//! as with `Context` objects.
6
7use std::sync::Arc;
8use std::fmt;
9use std::sync::RwLock;
10
11use send_wrapper::SendWrapper;
12
13use crate::HexchatError;
14use crate::context::*;
15use crate::thread_facilities::*;
16use crate::threadsafe_list_iterator::*;
17
18use HexchatError::*;
19
20const DROPPED_ERR: &str = "Context dropped from threadsafe context.";
21
22/// A thread-safe version of `Context`. Its methods automatically execute on
23/// the Hexchat main thread. The full set of methods of `Context` aren't
24/// fully implemented for this struct because some can't be trusted to produce
25/// predictable results from other threads. For instance `.set()` from a thread
26/// would only cause Hexchat to momentarily set its context, but Hexchat's
27/// context could change again at any moment while the other thread is
28/// executing.
29///
30///
31#[derive(Clone)]
32pub struct ThreadSafeContext {
33    ctx : Arc<RwLock<Option<SendWrapper<Context>>>>,
34}
35
36unsafe impl Send for ThreadSafeContext {}
37unsafe impl Sync for ThreadSafeContext {}
38
39impl ThreadSafeContext {
40    /// Creates a new `ThreadSafeContext` object, which wraps a `Context` object
41    /// internally. Only to be called from the main thread internally.
42    /// 
43    pub (crate)
44    fn new(ctx: Context) -> Self {
45        Self { ctx: Arc::new(RwLock::new(Some(SendWrapper::new(ctx)))) }
46    }
47
48    /// Gets the user's current `Context` wrapped in a `ThreadSafeContext` 
49    /// object.
50    ///
51    pub fn get() -> Result<Self, HexchatError> {
52        main_thread(|_| Context::get().map(Self::new)).get()
53        .and_then(|r| r.ok_or_else(|| ContextAcquisitionFailed("?, ?".into())))
54    }
55
56    /// Gets a `ThreadSafeContext` object associated with the given channel.
57    /// # Arguments
58    /// * `network` - The network of the channel to get the context for.
59    /// * `channel` - The channel to get the context of.
60    /// # Returns
61    /// * `Ok(ThreadSafeContext)` on success, and `HexchatError` on failure.
62    ///
63    pub fn find(network: &str, channel: &str) -> Result<Self, HexchatError> {
64        let data = (network.to_string(), channel.to_string());
65        main_thread(move |_| Context::find(&data.0, &data.1).map(Self::new))
66        .get()
67        .and_then(|r| r.ok_or_else(|| {
68                    let msg = format!("{}, {}", network, channel);
69                    ContextAcquisitionFailed(msg) 
70                }))
71    }
72
73    /// Prints the message to the `ThreadSafeContext` object's Hexchat context.
74    /// This is how messages can be printed to Hexchat windows apart from the
75    /// currently active one.
76    ///
77    pub fn print(&self, message: &str) -> Result<(), HexchatError> {
78        let message = message.to_string();
79        let me = self.clone();
80        main_thread(move |_| {
81            me.ctx.read().unwrap().as_ref()
82                  .ok_or_else(|| ContextDropped(DROPPED_ERR.into()))?
83                  .print(&message)
84        }).get().and_then(|r| r)
85    }
86
87    /// Prints without waiting for asynchronous completion. This will print
88    /// faster than `.print()` because it just stacks up print requests in the
89    /// timer queue and moves on without blocking. The downside is errors
90    /// will not be checked. Error messages will, however, still be printed
91    /// if any occur.
92    ///
93    pub fn aprint(&self, message: &str) {
94        let message = message.to_string();
95        let me = self.clone();
96        main_thread(move |hc| {
97            if let Err(err)
98                = me.ctx.read().unwrap().as_ref().unwrap().print(&message) {
99                hc.print(&format!("\x0313Context.aprint() failed to acquire \
100                                  context: {}", err));
101                hc.print(&format!("\x0313{}", message));
102            }
103        });
104    }
105
106    /// Issues a command in the context held by the `ThreadSafeContext` object.
107    ///
108    pub fn command(&self, command: &str) -> Result<(), HexchatError> {
109        let command = command.to_string();
110        let me = self.clone();
111        main_thread(move |_| {
112            me.ctx.read().unwrap().as_ref()
113                  .ok_or_else(|| ContextDropped(DROPPED_ERR.into()))?
114                  .command(&command)
115        }).get().and_then(|r| r)
116    }
117
118    /// Gets information from the channel/window that the `ThreadSafeContext`
119    /// object holds an internal pointer to.
120    ///
121    pub fn get_info(&self, info: &str) -> Result<String, HexchatError> {
122        let info = info.to_string();
123        let me = self.clone();
124        main_thread(move |_| {
125            me.ctx.read().unwrap().as_ref()
126                  .ok_or_else(|| ContextDropped(DROPPED_ERR.into()))?
127                  .get_info(&info)
128            }).get().and_then(|r| r)
129    }
130
131    /// Issues a print event to the context held by the `ThreadSafeContext`
132    /// object.
133    ///
134    pub fn emit_print(&self, event_name: &str, var_args: &[&str])
135        -> Result<(), HexchatError>
136    {
137        let var_args: Vec<String> = var_args.iter()
138                                            .map(|s| s.to_string())
139                                            .collect();
140        let data = (event_name.to_string(), var_args);
141        let me = self.clone();
142        main_thread(move |_| {
143            let var_args: Vec<&str> = data.1.iter()
144                                            .map(|s| s.as_str())
145                                            .collect();
146            me.ctx.read().unwrap().as_ref()
147                  .ok_or_else(|| ContextDropped(DROPPED_ERR.into()))?
148                  .emit_print(&data.0, var_args.as_slice())
149        }).get().and_then(|r| r)
150    }
151
152    /// Gets a `ThreadSafeListIterator` from the context.  If the list doesn't 
153    /// exist, or a problem occurs, an error will be returned.
154    ///
155    pub fn list_get(&self,
156                    name: &str)
157        -> Result<ThreadSafeListIterator, HexchatError>
158    {
159        let name = name.to_string();
160        let me = self.clone();
161        main_thread(move |_| {
162            if let Some(ctx) = me.ctx.read().unwrap().as_ref() {
163                match ctx.list_get(&name) {
164                    Ok(list) => {
165                        Ok(ThreadSafeListIterator::create(list))
166                    },
167                    Err(err) => Err(err),
168                }
169            } else {
170                Err(ContextDropped(DROPPED_ERR.into()))
171            }
172        }).get().and_then(|r| r)
173    }
174    /// Returns the network name associated with the context.
175    /// 
176    pub fn network(&self) -> Result<String, HexchatError> {
177        let me = self.clone();
178        main_thread(move |_| {
179            if let Some(ctx) = me.ctx.read().unwrap().as_ref() {
180                Ok(ctx.network())
181            } else {
182                Err(ContextDropped(DROPPED_ERR.into()))
183            }
184        }).get().and_then(|r| r)
185    }
186
187    /// Returns the channel name associated with the context.
188    /// 
189    pub fn channel(&self) -> Result<String, HexchatError> {
190        let me = self.clone();
191        main_thread(move |_| {
192            if let Some(ctx) = me.ctx.read().unwrap().as_ref() {
193                Ok(ctx.channel())
194            } else {
195                Err(ContextDropped(DROPPED_ERR.into()))
196            }
197        }).get().and_then(|r| r)
198    }
199}
200
201impl fmt::Display for ThreadSafeContext {
202    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
203        write!(f, "{:?}", self.ctx)
204    }
205}
206
207impl Drop for ThreadSafeContext {
208    fn drop(&mut self) {
209        if Arc::strong_count(&self.ctx) <= 1
210            && self.ctx.read().unwrap().is_some() {
211            let me = self.clone();
212            main_thread(move |_| {
213                me.ctx.write().unwrap().take();
214            });
215        }
216    }
217}
218
219impl fmt::Debug for ThreadSafeContext {
220    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
221        // A bit overkill, but fixes problems with users trying to debug print
222        // the object from other threads.
223        let me = self.clone();
224        let s = main_thread(move |_| {
225            if let Ok(guard) = me.ctx.read() {
226                if let Some(ctx) =  guard.as_ref() {
227                    format!("Context({:?}, {:?})", ctx.network(), ctx.channel())
228                } else {
229                    "Context(Error getting info)".to_string()
230                }
231            } else {
232                "Context(Error getting info)".to_string()
233            }
234        }).get().unwrap();
235        write!(f, "{}", s)
236    }
237}