tabiew 0.16.0

A lightweight TUI application to view and query tabular data files, such as CSV, TSV, and parquet.
use std::{
    sync::mpsc::{Receiver, Sender, TryRecvError, channel},
    time::Duration,
};

use iceoryx2::{
    config::Config,
    node::NodeBuilder,
    prelude::{AllocationStrategy, FileName, SemanticString, ZeroCopySend},
    service::ipc,
};
use serde::{Deserialize, Serialize};

use crate::AppResult;

const CYCLE_TIME: Duration = Duration::from_millis(100);

pub struct Channel<T> {
    send: Sender<T>,
    recv: Receiver<T>,
}

impl<T> Channel<T>
where
    T: Sync + Send + 'static + Serialize + for<'de> Deserialize<'de>,
{
    pub fn new() -> Self {
        let (send, trecv) = channel::<T>();
        let (tsend, recv) = channel::<T>();

        std::thread::spawn(move || -> AppResult<()> {
            let node = NodeBuilder::new()
                .config(&Self::ipc_config())
                .create::<ipc::Service>()?;
            let service = node
                .service_builder(&"tabiew/broadcast/v1".try_into()?)
                .publish_subscribe::<[u8]>()
                .user_header::<Header>()
                .max_nodes(32)
                .max_publishers(32)
                .max_subscribers(32)
                .history_size(0)
                .open_or_create()?;

            let subscriber = service.subscriber_builder().create()?;
            let publisher = service
                .publisher_builder()
                .initial_max_slice_len(65_536)
                .allocation_strategy(AllocationStrategy::PowerOfTwo)
                .create()?;

            loop {
                loop {
                    match trecv.try_recv() {
                        Ok(msg) => {
                            let buf = postcard::to_allocvec(&msg)?;
                            let mut sample = publisher.loan_slice(buf.len())?;
                            sample.user_header_mut().pid = std::process::id();
                            sample.payload_mut().copy_from_slice(&buf);
                            sample.send()?;
                        }
                        Err(TryRecvError::Empty) => break,
                        Err(TryRecvError::Disconnected) => return Ok(()),
                    }
                }

                while let Some(sample) = subscriber.receive()? {
                    let msg = postcard::from_bytes(sample.payload())?;
                    if !sample.user_header().is_echo() {
                        tsend.send(msg)?;
                    }
                }

                std::thread::sleep(CYCLE_TIME)
            }
        });

        Self { send, recv }
    }

    pub fn recv(&self) -> Option<T> {
        self.recv.try_recv().ok()
    }

    pub fn recv_timeout(&self, duration: Duration) -> Option<T> {
        self.recv.recv_timeout(duration).ok()
    }

    pub fn send(&self, msg: T) {
        let _ = self.send.send(msg);
    }

    fn ipc_config() -> Config {
        let mut config = Config::default();
        config.global.prefix = FileName::new(b"tabiew_").unwrap();
        config
    }
}

impl<T> Default for Channel<T>
where
    T: Sync + Send + 'static + Serialize + for<'de> Deserialize<'de>,
{
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug, ZeroCopySend)]
#[repr(C)]
struct Header {
    pid: u32,
}

impl Default for Header {
    fn default() -> Self {
        Self {
            pid: std::process::id(),
        }
    }
}

impl Header {
    pub fn is_echo(&self) -> bool {
        self.pid == std::process::id()
    }
}