twitch_message/
ping_tracker.rs

1use std::time::{Duration, Instant};
2
3use crate::{
4    encode::{pong, Pong},
5    lock::Lock,
6    messages::{Message, Ping},
7};
8
9/// A simple type to track PINGs and if you should PONG
10///
11/// This requires the `ping` feature to be enabled
12///
13/// If either `sync` or `parking_lot` features are also enabled, then this type is safe to send to other threads
14///
15/// ```rust
16/// # use twitch_message::messages::Message;
17/// # use twitch_message::encode::Encode as _;
18/// # fn read_message() -> Message<'static> { twitch_message::parse(":tmi.twitch.tv PING :1234567890\r\n").unwrap().message }
19/// # let mut io_sink = vec![];
20/// use twitch_message::PingTracker;
21/// // create a new tracker, the `threshold` is used to determine when a connection is dead/stale.
22/// let pt = PingTracker::new(std::time::Duration::from_secs(10 * 60));
23///
24/// // in some loop
25/// // if its been a while (such as if you have a way to keep track of time)
26/// if pt.probably_timed_out() {
27///     // we should reconnect
28///     return Err("timed out".into());
29/// }
30///
31/// // this might block for a while
32/// let msg = read_message();
33/// // update the tracker
34/// pt.update(&msg);
35///
36/// // check to see if you should reply.
37/// // this returns a message you can write to your sink
38/// if let Some(pong) = pt.should_pong() {
39///     io_sink.encode_msg(pong)?;
40/// }
41/// # Ok::<(),Box<dyn std::error::Error>>(())
42/// ```
43pub struct PingTracker {
44    threshold: Duration,
45    last: Lock<Option<Instant>>,
46}
47
48impl PingTracker {
49    /// Create a new [`PingTracker`] with a 'timeout' duration
50    pub const fn new(threshold: Duration) -> Self {
51        Self {
52            threshold,
53            last: Lock::new(None),
54        }
55    }
56
57    /// Get the 'timeout' duration
58    pub const fn threshold(&self) -> Duration {
59        self.threshold
60    }
61
62    /// Update the tracker with this message
63    pub fn update(&self, msg: &Message<'_>) {
64        if msg.as_typed_message::<Ping>().is_some() {
65            self.last.borrow_mut().replace(Instant::now());
66        }
67    }
68
69    /// Determines whether you should PONG
70    ///
71    /// This returns the message you should encode
72    pub fn should_pong(&self) -> Option<Pong<'static>> {
73        // TODO save the token
74        self.last.borrow_mut().take().map(|_| pong("tmi.twitch.tv"))
75    }
76
77    /// Determines if the timeout threshold has been reached
78    pub fn probably_timed_out(&self) -> bool {
79        if let Some(ping) = &*self.last.borrow() {
80            return ping.elapsed() >= self.threshold;
81        }
82        false
83    }
84}