summary refs log blame commit diff stats
path: root/src/list.rs
blob: 04af87a184848ddf6e1c2155c0e29caa41172da0 (plain) (tree)




































































































                                                                         
                                   























































































































































































































































































































                                                                                              
// This file is part of Hexchat Plugin API Bindings for Rust
// Copyright (C) 2022 Soni L.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

//! List support module.

use std::borrow::Cow;
use std::cell::Cell;
use std::ffi::CStr;
use std::rc::Rc;

use crate::Context;
use crate::wrap_context;

mod sealed {
    pub trait Sealed {
    }
}

/// A list. This trait is sealed.
pub trait List: sealed::Sealed {
    fn name(&self) -> Cow<'static, str>;
}

// List types

/// A "channels" list.
pub struct Contexts;

/// A "dcc" list.
pub struct Dcc;

/// An "ignore" list.
pub struct Ignore;

/// A "notify" list.
pub struct Notify;

/// An "users" list.
pub struct Users;

/// Entries.
pub struct Entries<'a, 'ph, T> {
    pub(crate) context: &'a crate::ValidContext<'a, 'ph>,
    pub(crate) list: *mut crate::internals::HexchatList,
    pub(crate) t: &'a T,
    pub(crate) valid: Rc<Cell<usize>>,
}

/// Fields.
pub struct Fields<'a, 'ph, T> {
    context: &'a crate::ValidContext<'a, 'ph>,
    list: *mut crate::internals::HexchatList,
    _t: &'a T,
    id: usize,
    valid: Rc<Cell<usize>>,
}

// impls

impl sealed::Sealed for Contexts {}
impl sealed::Sealed for Dcc {}
impl sealed::Sealed for Ignore {}
impl sealed::Sealed for Notify {}
impl sealed::Sealed for Users {}

impl List for Contexts {
    fn name(&self) -> Cow<'static, str> { "channels".into() }
}

impl List for Dcc {
    fn name(&self) -> Cow<'static, str> { "dcc".into() }
}

impl List for Ignore {
    fn name(&self) -> Cow<'static, str> { "ignore".into() }
}

impl List for Notify {
    fn name(&self) -> Cow<'static, str> { "notify".into() }
}

impl List for Users {
    fn name(&self) -> Cow<'static, str> { "users".into() }
}

impl<'a, 'ph, T> Drop for Entries<'a, 'ph, T> {
    fn drop(&mut self) {
        let ph = &self.context.ph;
        self.valid.set(usize::MAX);
        unsafe {
            ph_call!(hexchat_list_free(ph, self.list));
        }
    }
}

impl<'a, 'ph, T> Iterator for Entries<'a, 'ph, T> {
    type Item = Fields<'a, 'ph, T>;

    fn next(&mut self) -> Option<Self::Item> {
        let ph = &self.context.ph;
        let id = self.valid.get();
        if unsafe {
            ph_call!(hexchat_list_next(ph, self.list))
        } != 0 {
            debug_assert!(self.valid.get() < usize::MAX);
            self.valid.set(id + 1);
            Some(Fields {
                context: self.context,
                list: self.list,
                _t: self.t,
                valid: Rc::clone(&self.valid),
                id: id,
            })
        } else {
            self.valid.set(usize::MAX);
            None
        }
    }
}

macro_rules! field {
    ($(#[$m:meta])* $f:ident, unsafe {$n:expr}, $r:ty, $ph:ident, $c:ident, $($conv:tt)+) => {
        $(#[$m])* pub fn $f(&self) -> $r {
            assert_eq!(
                self.id, self.valid.get(),
                "instances of Fields are invalidated by next()",
            );
            let $ph = &self.context.ph;
            const NAME: &'static CStr = unsafe {
                CStr::from_bytes_with_nul_unchecked(
                    $n.as_bytes()
                )
            };
            match unsafe {
                ph_call!($c($ph, self.list, NAME.as_ptr()))
            } {
                $($conv)+
            }
        }
    };
    ($(#[$m:meta])* $f:ident, $r:ty, $ph:ident, $c:ident, $($conv:tt)+) => {
        field!(
            $(#[$m])* $f,
            unsafe { ::std::concat!(::std::stringify!($f), "\0") },
            $r,
            $ph,
            $c,
            $($conv)+
        );
    };
}

macro_rules! field_int {
    ($(#[$m:meta])* $f:ident, unsafe { $n:expr }) => {
        field!($(#[$m])* $f, unsafe { $n }, i32, ph, hexchat_list_int, r => r as i32);
    };
    ($(#[$m:meta])* $f:ident) => {
        field_int!($(#[$m])* $f, unsafe { ::std::concat!(::std::stringify!($f), "\0") });
    };
}

macro_rules! field_bool {
    ($(#[$m:meta])* $f:ident, unsafe { $n:expr }) => {
        field!($(#[$m])* $f, unsafe { $n }, bool, ph, hexchat_list_int, r => r != 0);
    };
    ($(#[$m:meta])* $f:ident) => {
        field_bool!($(#[$m])* $f, unsafe { ::std::concat!(::std::stringify!($f), "\0") });
    };
}

macro_rules! field_str {
    ($(#[$m:meta])* $f:ident, unsafe { $n:expr }) => {
        field!(
            $(#[$m])* $f, unsafe { $n }, Option<String>, ph, hexchat_list_str,
            r if r.is_null() => {
                None
            },
            r => Some(unsafe {
                CStr::from_ptr(r)
            }.to_owned().into_string().unwrap())
        );
    };
    ($(#[$m:meta])* $f:ident) => {
        field_str!($(#[$m])* $f, unsafe { ::std::concat!(::std::stringify!($f), "\0") });
    };
}


macro_rules! field_time {
    ($(#[$m:meta])* $f:ident) => {
        $(#[$m])* pub fn $f(&self) -> ! {
            todo!()
        }
    };
}

/// Contexts fields.
impl<'a, 'ph> Fields<'a, 'ph, Contexts> {
    field_str!(
        /// The context's name.
        name, unsafe { "channel" }
    );
    field_str!(
        /// The channel key.
        channelkey
    );
    field_str!(
        /// The server's channel modes. Requires HexChat 2.12.2+.
        chanmodes
    );
    field_str!(
        /// The server's channel types.
        chantypes
    );
    field!(
        /// The context.
        context, Context<'ph>, ph, hexchat_list_str, r => unsafe {
            wrap_context(ph, r as *const _)
        }
    );
    field_int!(
        /// Raw flags about this context.
        raw_flags, unsafe { "flags" }
    );
    field_int!(
        /// The server ID.
        server_id, unsafe { "id" }
    );
    field_int!(
        /// The latency to server in milliseconds.
        lag
    );
    field_int!(
        /// The maximum number of mode changes per line accepted by the server.
        maxmodes
    );
    field_str!(
        /// The network name.
        network
    );
    field_str!(
        /// The server's nick prefixes.
        nickprefixes
    );
    field_str!(
        /// The server's nick modes.
        nickmodes
    );
    field_int!(
        /// The current length of the send-queue, in bytes.
        queue_len, unsafe { "queue" }
    );
    field_str!(
        /// The server's name.
        server
    );
    field_int!(
        /// The context's raw type.
        raw_type, unsafe { "type" }
    );
    field_int!(
        /// The number of users in the channel.
        users
    );
}

/// Dcc fields.
impl<'a, 'ph> Fields<'a, 'ph, Dcc> {
    field_int!(
        /// The raw 32-bit IPv4 address of the remote user.
        address32
    );
    field_int!(
        /// The transfer rate (speed), in bytes per second.
        rate, unsafe { "cps" }
    );
    field_str!(
        /// The destination file path.
        destfile
    );
    field_str!(
        /// The filename.
        file
    );
    field_str!(
        /// The remote user's nick.
        nick
    );
    field_int!(
        /// The listener's port number.
        port
    );
    field_int!(
        /// File position, LSB 32 bits.
        raw_pos, unsafe { "pos" }
    );
    field_int!(
        /// File position, MSB 32 bits.
        raw_poshigh, unsafe { "poshigh" }
    );
    field_int!(
        /// Resumed position, LSB 32 bits.
        raw_resume, unsafe { "resume" }
    );
    field_int!(
        /// Resumed position, MSB 32 bits.
        raw_resumehigh, unsafe { "resumehigh" }
    );
    field_int!(
        /// File size, LSB 32 bits.
        raw_size, unsafe { "size" }
    );
    field_int!(
        /// File size, MSB 32 bits.
        raw_sizehigh, unsafe { "sizehigh" }
    );
    field_int!(
        /// Raw DCC status.
        raw_status, unsafe { "status" }
    );
    field_int!(
        /// Raw type.
        raw_type, unsafe { "type" }
    );
}

/// Ignore fields.
impl<'a, 'ph> Fields<'a, 'ph, Ignore> {
    field_str!(
        /// The ignore mask.
        mask
    );
    field_int!(
        /// Raw ignore flags.
        raw_flags, unsafe { "flags" }
    );
}

/// Notify fields.
impl<'a, 'ph> Fields<'a, 'ph, Notify> {
    field_str!(
        /// Comma-separated list of networks this notify entry applies to.
        networks
    );
    field_str!(
        /// The nick.
        nick
    );
    field_int!(
        /// Raw flags.
        raw_flags, unsafe { "flags" }
    );
    field_time!(
        /// Time when the user went online. [NYI]
        on
    );
    field_time!(
        /// Time when the user went offline. [NYI]
        off
    );
    field_time!(
        /// Time when the user was last seen. [NYI]
        seen
    );
}

/// Users fields.
impl<'a, 'ph> Fields<'a, 'ph, Users> {
    field_str!(
        /// Account name.
        account
    );
    field_bool!(
        /// Whether the user is away.
        away
    );
    field_time!(
        /// Time when the user last talked. [NYI]
        lasttalk
    );
    field_str!(
        /// User's nick.
        nick
    );
    field_str!(
        /// User's user and hostname (`user@host`).
        host
    );
    field_str!(
        /// User's prefix.
        prefix
    );
    field_str!(
        /// User's userdata.
        userdata, unsafe { "realname" }
    );
    field_bool!(
        /// Whether the user is selected in the UI.
        selected
    );
}