tnnl 0.1.17

tnnl gives you full control over whether and when your IoT devices should be reachable from the internet
use std::sync::Arc;

use serde_json::{Map, Value};

use crate::{
    apperror::{self, AppError},
    channel::{Channel, ChannelReadHalf, ChannelWriteHalf},
    model::{command::CommandWithoutId, command_response::CommandResponse, site_info::SiteInfo},
    rpc_helper::{Response, RpcHelper},
    session::LoginInfo,
};

pub(crate) const COMMAND_CHANNEL_ID: i32 = 0;

pub struct CommandChannelWriteHalf {
    channel: ChannelWriteHalf,
    rpc_helper: Arc<RpcHelper>,
}

pub struct CommandChannelReadHalf {
    channel: ChannelReadHalf,
    rpc_helper: Arc<RpcHelper>,
}

pub struct CommandChannel {
    rpc_helper: RpcHelper,
    channel: Channel,
}

impl CommandChannelWriteHalf {
    pub async fn send_command(
        &self,
        command: CommandWithoutId,
    ) -> Result<serde_json::Value, AppError> {
        let (receive, command_id) = self.rpc_helper.add().await;
        let command = command.to_command(command_id);

        self.channel.send(&command.to_json_buffer()?).await?;

        let result = receive
            .await
            .map_err(|_| AppError::new("failed to receive rpc result"))?;

        match result {
            Response::Success(result) => Ok(result),
            Response::Error(error) => Err(apperror::AppError::new(format_args!(
                "request failed: {error}"
            ))),
        }
    }

    pub async fn send_info(&self, login_info: LoginInfo) -> Result<Value, AppError> {
        let client = "tnnl-rust-client".to_string();
        let protocol_version = 1;
        let command = CommandWithoutId::create_send_info(login_info, client, protocol_version);

        self.send_command(command).await
    }

    pub async fn get_download(&self) -> Result<Value, AppError> {
        let command = CommandWithoutId::create_get_download();

        self.send_command(command).await
    }

    pub async fn post_upload(&self, data: Map<String, Value>) -> Result<Value, AppError> {
        let command = CommandWithoutId::create_post_upload(data);
        self.send_command(command).await
    }

    pub async fn get_site_info(&self) -> Result<SiteInfo, AppError> {
        let command = CommandWithoutId::create_get_site_info();
        SiteInfo::from_value(self.send_command(command).await?)
    }
}

impl CommandChannelReadHalf {
    pub fn spawn(mut self) {
        tokio::spawn(async move {
            loop {
                match self.channel.recv().await {
                    Some(data) => {
                        let Ok(response) = CommandResponse::from_json_buffer(&data) else {
                            log::warn!("failed to parse command response");
                            return;
                        };

                        let command_id = response.command_id;
                        let response: Response = match (response.error, response.response) {
                            (None, None) => {
                                log::warn!(
                                    "we got no response but also no error -> we assume that it is a null value"
                                );
                                Response::Success(Value::Null)
                            }
                            (None, Some(response)) => Response::Success(response),
                            (Some(error), None) => Response::Error(error),
                            (Some(error), Some(response)) => {
                                log::warn!(
                                    "response has an error {:} and a response {:}",
                                    error,
                                    response
                                );
                                Response::Error(error)
                            }
                        };

                        self.rpc_helper.complete(command_id, response).await;
                    }
                    None => {
                        log::warn!("command channel got closed");
                        break;
                    }
                }
            }
        });
    }
}

impl CommandChannel {
    pub fn new(channel: Channel) -> CommandChannel {
        CommandChannel {
            channel,
            rpc_helper: RpcHelper::new(),
        }
    }

    pub fn split(self) -> (CommandChannelReadHalf, CommandChannelWriteHalf) {
        let rpc_helper = Arc::new(self.rpc_helper);

        let (channel_read, channel_write) = self.channel.split();

        (
            CommandChannelReadHalf {
                rpc_helper: rpc_helper.clone(),
                channel: channel_read,
            },
            CommandChannelWriteHalf {
                rpc_helper,
                channel: channel_write,
            },
        )
    }
}