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}