// 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 . //! 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::PluginHandle<'ph>, pub(crate) list: *mut crate::internals::HexchatList, pub(crate) t: &'a T, pub(crate) valid: Rc>, } /// Fields. pub struct Fields<'a, 'ph, T> { pub(crate) context: &'a crate::PluginHandle<'ph>, pub(crate) list: *mut crate::internals::HexchatList, pub(crate) _t: &'a T, pub(crate) id: usize, pub(crate) valid: Rc>, } // 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; 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 { let ph = &self.context; 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; 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, 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 ); }