use super::{Client, ConnectionState, Event, Message, ffi};
impl Drop for Client {
fn drop(&mut self) {
self.backend.close(self.ptr.0);
}
}
impl Client {
pub fn raw_ptr(&self) -> *mut ffi::TTInstance {
self.ptr.0
}
pub fn version() -> String {
let _ = crate::init();
unsafe {
let ptr = ffi::api().TT_GetVersion();
if ptr.is_null() {
"Unknown".to_string()
} else {
crate::utils::strings::from_tt(ptr)
}
}
}
pub fn poll(&self, timeout_ms: i32) -> Option<(Event, Message)> {
let mut msg = unsafe { std::mem::zeroed::<ffi::TTMessage>() };
let t = timeout_ms;
if self.backend().get_message(self.ptr.0, &mut msg, &t) {
let event = Event::from(msg.nClientEvent);
let message = Message::from_raw(event, msg);
self.update_state_for_event(event, &message);
#[cfg(feature = "state")]
self.update_cache_for_event(event, &message);
self.invoke_hooks(event, &message);
self.dispatch_bus(event, &message);
#[cfg(feature = "scripts")]
self.dispatch_scripts(event, &message);
#[cfg(feature = "logging")]
tracing::trace!(
event = ?event,
source = message.source(),
state = ?self.connection_state(),
"teamtalk poll event"
);
self.handle_auto_reconnect();
Some((event, message))
} else {
self.handle_auto_reconnect();
None
}
}
pub fn poll_until<F>(&self, timeout_ms: i32, mut predicate: F) -> Option<(Event, Message)>
where
F: FnMut(Event, &Message) -> bool,
{
use std::time::{Duration, Instant};
if timeout_ms < 0 {
loop {
if let Some((event, msg)) = self.poll(timeout_ms)
&& predicate(event, &msg)
{
return Some((event, msg));
}
}
}
let deadline = Instant::now() + Duration::from_millis(timeout_ms as u64);
loop {
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
return None;
}
let wait_ms = remaining.as_millis().min(i32::MAX as u128) as i32;
if let Some((event, msg)) = self.poll(wait_ms)
&& predicate(event, &msg)
{
return Some((event, msg));
}
}
}
pub fn wait_for(&self, event: Event, timeout_ms: i32) -> Option<Message> {
self.poll_until(timeout_ms, |incoming, _| incoming == event)
.map(|(_, msg)| msg)
}
pub fn poll_until_event(&self, event: Event, timeout_ms: i32) -> Option<Message> {
self.wait_for(event, timeout_ms)
}
pub(super) fn update_state_for_event(&self, event: Event, msg: &Message) {
#[cfg(feature = "logging")]
let prev_state = self.connection_state();
match event {
Event::ConnectSuccess => {
self.set_connection_state(ConnectionState::Connected);
let mut auto = self
.auto_reconnect
.lock()
.unwrap_or_else(|e| e.into_inner());
auto.clear_connect_phase();
if let Some(handler) = auto.handler.as_mut() {
handler.mark_connected();
}
auto.login_gave_up = false;
auto.join_gave_up = false;
if auto.enabled {
let msg =
Message::from_raw(event, unsafe { std::mem::zeroed::<ffi::TTMessage>() });
let attempts = auto.handler.as_ref().map(|h| h.attempts()).unwrap_or(0);
drop(auto);
if attempts > 0 {
self.invoke_hooks(Event::AfterReconnect { attempt: attempts }, &msg);
}
} else {
drop(auto);
}
}
Event::ConnectFailed | Event::ConnectionLost | Event::ConnectCryptError => {
self.set_connection_state(ConnectionState::Disconnected);
let mut auto = self
.auto_reconnect
.lock()
.unwrap_or_else(|e| e.into_inner());
auto.clear_phase_tracking();
if let Some(handler) = auto.handler.as_mut() {
handler.mark_disconnected();
}
}
Event::MySelfLoggedIn => {
self.set_connection_state(ConnectionState::LoggedIn);
let mut auto = self
.auto_reconnect
.lock()
.unwrap_or_else(|e| e.into_inner());
auto.clear_login_phase();
if let Some(handler) = auto.login_handler.as_mut() {
handler.mark_connected();
}
}
Event::MySelfLoggedOut => {
self.set_connection_state(ConnectionState::Connected);
let mut auto = self
.auto_reconnect
.lock()
.unwrap_or_else(|e| e.into_inner());
auto.clear_join_phase();
if let Some(handler) = auto.join_handler.as_mut() {
handler.mark_disconnected();
}
}
Event::UserJoined => {
if let Some(user) = msg.user()
&& user.id == self.my_id()
{
self.set_connection_state(ConnectionState::Joined(user.channel_id));
let mut auto = self
.auto_reconnect
.lock()
.unwrap_or_else(|e| e.into_inner());
auto.clear_join_phase();
if let Some(handler) = auto.join_handler.as_mut() {
handler.mark_connected();
}
self.invoke_joined_hook(user.channel_id);
}
}
Event::UserLeft => {
if let Some(user) = msg.user()
&& user.id == self.my_id()
{
self.set_connection_state(ConnectionState::LoggedIn);
let mut auto = self
.auto_reconnect
.lock()
.unwrap_or_else(|e| e.into_inner());
auto.clear_join_phase();
if let Some(handler) = auto.join_handler.as_mut() {
handler.mark_disconnected();
}
}
}
Event::MySelfKicked => {
let next_state = kicked_next_state(msg.source());
self.set_connection_state(next_state);
let mut auto = self
.auto_reconnect
.lock()
.unwrap_or_else(|e| e.into_inner());
auto.clear_join_phase();
if matches!(next_state, ConnectionState::Connected) {
auto.clear_login_phase();
}
if let Some(handler) = auto.join_handler.as_mut() {
handler.mark_disconnected();
}
if matches!(next_state, ConnectionState::Connected)
&& let Some(handler) = auto.login_handler.as_mut()
{
handler.mark_disconnected();
}
}
Event::CmdError => {
let source = msg.source();
let mut next_state = None;
let mut auto = self
.auto_reconnect
.lock()
.unwrap_or_else(|e| e.into_inner());
if auto.pending_login_cmd == Some(source) {
auto.clear_login_phase();
if let Some(handler) = auto.login_handler.as_mut() {
handler.mark_disconnected();
}
next_state = Some(ConnectionState::Connected);
} else if auto.pending_join_cmd == Some(source) {
auto.clear_join_phase();
if let Some(handler) = auto.join_handler.as_mut() {
handler.mark_disconnected();
}
next_state = Some(ConnectionState::LoggedIn);
}
drop(auto);
if let Some(state) = next_state {
self.set_connection_state(state);
}
}
Event::CmdSuccess => {
let source = msg.source();
let mut auto = self
.auto_reconnect
.lock()
.unwrap_or_else(|e| e.into_inner());
if auto.pending_login_cmd == Some(source) {
auto.clear_login_phase();
}
if auto.pending_join_cmd == Some(source) {
auto.clear_join_phase();
}
}
_ => {}
}
let mut auto = self
.auto_reconnect
.lock()
.unwrap_or_else(|e| e.into_inner());
if auto.enabled && event.is_reconnect_needed_with(&auto.extra_events) {
auto.force_disconnect = true;
drop(auto);
self.set_connection_state(ConnectionState::Disconnected);
}
#[cfg(feature = "logging")]
{
let next_state = self.connection_state();
if next_state != prev_state {
tracing::debug!(
event = ?event,
previous = ?prev_state,
current = ?next_state,
"connection state transition"
);
}
}
}
pub fn get_flags(&self) -> crate::types::ClientFlags {
crate::types::ClientFlags::from_raw(self.backend().get_flags(self.ptr.0))
}
pub fn get_error_message(&self, code: i32) -> String {
use crate::types::TT_STRLEN;
use crate::utils::strings::tt_buf;
let mut buf = tt_buf::<TT_STRLEN>();
unsafe {
ffi::api().TT_GetErrorMessage(code, buf.as_mut_ptr());
crate::utils::strings::to_string(&buf)
}
}
pub fn client_error(&self, code: i32) -> crate::events::Error {
crate::events::Error::ClientError {
code,
message: self.get_error_message(code),
}
}
}
fn kicked_next_state(source: i32) -> ConnectionState {
if source > 0 {
ConnectionState::LoggedIn
} else {
ConnectionState::Connected
}
}