use std::{net::ToSocketAddrs, sync::Arc};
use tokio::{sync::mpsc, sync::watch, task::JoinHandle};
use tracing::{Level, error, info, warn};
use tracing_subscriber::fmt;
use crate::application::client::client_common::{SharedWriteReceiver, spawn_check_heart};
use crate::application::client::client_config::{LynnClientConfig, LynnClientConfigBuilder};
use crate::application::client::client_connection::{ConnectionParams, connection_supervisor};
use crate::domain::model::handler_result::HandlerResult;
use crate::domain::model::input_buf_vo::InputBufVO;
#[cfg(feature = "client")]
pub struct LynnClient<'a> {
lynn_client_config: LynnClientConfig<'a>,
supervisor_join_handle: Option<JoinHandle<()>>,
tx_write: Option<mpsc::Sender<HandlerResult>>,
rx_read: Option<mpsc::Receiver<InputBufVO>>,
connection_state: Option<watch::Receiver<bool>>,
}
impl<'a> LynnClient<'a> {
pub async fn new_with_config(lynn_client_config: LynnClientConfig<'a>) -> Self {
Self {
lynn_client_config,
supervisor_join_handle: None,
tx_write: None,
rx_read: None,
connection_state: None,
}
}
#[deprecated(since = "1.1.7", note = "use `new_with_addr` instead")]
pub async fn new_with_ipv4(server_ipv4: &'a str) -> Self {
let config = LynnClientConfigBuilder::new()
.with_server_addr(server_ipv4)
.expect("Invalid server address")
.build();
Self {
lynn_client_config: config,
supervisor_join_handle: None,
tx_write: None,
rx_read: None,
connection_state: None,
}
}
pub async fn new_with_addr<T>(server_addr: T) -> Self
where
T: ToSocketAddrs,
{
let config = LynnClientConfigBuilder::new()
.with_server_addr(server_addr)
.expect("Invalid server address")
.build();
Self {
lynn_client_config: config,
supervisor_join_handle: None,
tx_write: None,
rx_read: None,
connection_state: None,
}
}
pub async fn start(mut self) -> Self {
match self.run().await {
Ok(_) => self,
Err(e) => {
error!("{}", e);
self
},
}
}
pub fn is_connected(&self) -> bool {
self.connection_state
.as_ref()
.map(|state| *state.borrow())
.unwrap_or(false)
}
async fn run(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let channel_size = *self.lynn_client_config.get_client_single_channel_size();
let (tx_read, rx_read) = mpsc::channel::<InputBufVO>(channel_size);
let (tx_write, rx_write) = mpsc::channel::<HandlerResult>(channel_size);
let (state_tx, state_rx) = watch::channel(false);
let (init_tx, init_rx) = tokio::sync::oneshot::channel();
let params = ConnectionParams::from_config(&self.lynn_client_config)?;
let shared_rx_write: SharedWriteReceiver = Arc::new(tokio::sync::Mutex::new(rx_write));
self.supervisor_join_handle = Some(tokio::spawn(connection_supervisor(
params,
tx_read,
shared_rx_write,
state_tx,
Some(init_tx),
)));
match init_rx.await {
Ok(Ok(())) => {
self.tx_write = Some(tx_write);
self.rx_read = Some(rx_read);
self.connection_state = Some(state_rx);
self.check_heart().await;
Ok(())
},
Ok(Err(message)) => Err(message.into()),
Err(_) => Err("connection supervisor exited before reporting".into()),
}
}
#[cfg(feature = "client")]
pub fn log_server(&self) {
let subscriber = fmt::Subscriber::builder()
.with_max_level(Level::INFO)
.finish();
match tracing::subscriber::set_global_default(subscriber) {
Ok(_) => {
info!("Client - [log server] start sucess!!!")
},
Err(e) => {
warn!("set_global_default failed - e: {:?}", e.to_string())
},
}
}
pub async fn get_receive_data(&mut self) -> Option<InputBufVO> {
match self.rx_read.as_mut() {
Some(rx) => rx.recv().await,
None => {
error!("Client is not connected. Call start() first.");
None
},
}
}
pub async fn get_sender(&mut self) -> Option<mpsc::Sender<HandlerResult>> {
self.tx_write.clone()
}
pub async fn send_data(
&mut self,
handler_result: HandlerResult,
) -> Result<(), Box<dyn std::error::Error>> {
match &self.tx_write {
Some(sender) => {
if let Err(e) = sender.send(handler_result).await {
error!("send to server failed - e: {:?}", e);
Err(e.into())
} else {
Ok(())
}
},
None => Err("tx_write is None , No linked server".into()),
}
}
pub(crate) async fn check_heart(&mut self) {
let interval_time = *self.lynn_client_config.get_server_check_heart_interval();
if let Some(sender) = self.get_sender().await {
spawn_check_heart(interval_time, sender);
} else {
warn!("Client - [check heart] start failed!!!");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn new_with_addr_resolves_the_address() {
let client = LynnClient::new_with_addr("127.0.0.1:9197").await;
assert_eq!(
client.lynn_client_config.get_server_ipv4(),
"127.0.0.1:9197"
);
assert!(!client.is_connected(), "unstarted client is not connected");
}
#[tokio::test]
#[allow(deprecated)]
async fn new_with_ipv4_still_resolves() {
let client = LynnClient::new_with_ipv4("127.0.0.1:9196").await;
assert_eq!(
client.lynn_client_config.get_server_ipv4(),
"127.0.0.1:9196"
);
}
#[test]
fn log_client_does_not_panic_on_repeated_init() {
let rt = tokio::runtime::Runtime::new().unwrap();
let client = rt.block_on(LynnClient::new_with_addr("127.0.0.1:9195"));
client.log_server(); client.log_server(); }
}