use crate::bindings;
use crate::destination::{DestCallback, Destination};
use crate::error::{Error, Result};
use std::marker::PhantomData;
use std::os::raw::{c_int, c_void};
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConnectionFlags {
Scheduler = 0,
Device = 1,
}
impl From<ConnectionFlags> for u32 {
fn from(flags: ConnectionFlags) -> u32 {
match flags {
ConnectionFlags::Scheduler => 0,
ConnectionFlags::Device => 1,
}
}
}
pub struct HttpConnection {
http: *mut bindings::_http_s,
resource: String,
_phantom: PhantomData<bindings::_http_s>,
}
impl HttpConnection {
pub(crate) unsafe fn from_raw(http: *mut bindings::_http_s, resource: String) -> Result<Self> {
if http.is_null() {
return Err(Error::ConnectionFailed(
"Failed to establish connection".to_string(),
));
}
Ok(HttpConnection {
http,
resource,
_phantom: PhantomData,
})
}
pub fn as_ptr(&self) -> *mut bindings::_http_s {
self.http
}
pub fn resource_path(&self) -> &str {
&self.resource
}
pub fn close(&mut self) {
if !self.http.is_null() {
unsafe {
bindings::httpClose(self.http);
}
self.http = ptr::null_mut();
}
}
pub fn is_connected(&self) -> bool {
!self.http.is_null()
}
}
impl Drop for HttpConnection {
fn drop(&mut self) {
self.close();
}
}
impl Destination {
pub fn connect(
&self,
flags: ConnectionFlags,
timeout_ms: Option<i32>,
cancel: Option<&AtomicBool>,
) -> Result<HttpConnection> {
let dest_ptr = self.as_ptr();
if dest_ptr.is_null() {
return Err(Error::NullPointer);
}
let timeout = timeout_ms.unwrap_or(-1);
let mut cancel_int: c_int = 0;
let cancel_ptr = if cancel.is_some() {
&mut cancel_int as *mut c_int
} else {
ptr::null_mut()
};
const RESOURCE_SIZE: usize = 1024;
let mut resource_buf: Vec<u8> = vec![0; RESOURCE_SIZE];
let http_conn = unsafe {
bindings::cupsConnectDest(
dest_ptr,
flags.into(),
timeout,
cancel_ptr,
resource_buf.as_mut_ptr() as *mut ::std::os::raw::c_char,
RESOURCE_SIZE,
None, ptr::null_mut(), )
};
if let Some(cancel_flag) = cancel {
if cancel_int != 0 {
cancel_flag.store(true, Ordering::SeqCst);
}
}
if http_conn.is_null() {
return Err(Error::ConnectionFailed(format!(
"Failed to connect to destination '{}'",
self.name
)));
}
let resource_len = resource_buf.iter().position(|&x| x == 0).unwrap_or(0);
let resource = String::from_utf8_lossy(&resource_buf[..resource_len]).into_owned();
unsafe { HttpConnection::from_raw(http_conn, resource) }
}
pub fn connect_with_callback<T>(
&self,
flags: ConnectionFlags,
timeout_ms: Option<i32>,
cancel: Option<&AtomicBool>,
callback: &mut DestCallback<T>,
user_data: &mut T,
) -> Result<HttpConnection> {
let dest_ptr = self.as_ptr();
if dest_ptr.is_null() {
return Err(Error::NullPointer);
}
let timeout = timeout_ms.unwrap_or(-1);
let mut cancel_int: c_int = 0;
let cancel_ptr = if cancel.is_some() {
&mut cancel_int as *mut c_int
} else {
ptr::null_mut()
};
let mut context = ConnectContext {
callback,
user_data,
};
const RESOURCE_SIZE: usize = 1024;
let mut resource_buf: Vec<u8> = vec![0; RESOURCE_SIZE];
let http_conn = unsafe {
bindings::cupsConnectDest(
dest_ptr,
flags.into(),
timeout,
cancel_ptr,
resource_buf.as_mut_ptr() as *mut ::std::os::raw::c_char,
RESOURCE_SIZE,
Some(connect_dest_callback::<T>),
&mut context as *mut _ as *mut c_void,
)
};
if let Some(cancel_flag) = cancel {
if cancel_int != 0 {
cancel_flag.store(true, Ordering::SeqCst);
}
}
if http_conn.is_null() {
return Err(Error::ConnectionFailed(format!(
"Failed to connect to destination '{}' or connection was cancelled",
self.name
)));
}
let resource_len = resource_buf.iter().position(|&x| x == 0).unwrap_or(0);
let resource = String::from_utf8_lossy(&resource_buf[..resource_len]).into_owned();
unsafe { HttpConnection::from_raw(http_conn, resource) }
}
}
struct ConnectContext<'a, T> {
callback: &'a mut DestCallback<T>,
user_data: &'a mut T,
}
unsafe extern "C" fn connect_dest_callback<T>(
user_data: *mut c_void,
flags: u32,
dest_ptr: *mut bindings::cups_dest_s,
) -> c_int {
let context = unsafe { &mut *(user_data as *mut ConnectContext<T>) };
unsafe {
match Destination::from_raw(dest_ptr) {
Ok(dest) => {
if (context.callback)(flags, &dest, context.user_data) {
1 } else {
0 }
}
Err(_) => {
1
}
}
}
}
pub fn connect_to_destination(
dest: &Destination,
flags: ConnectionFlags,
timeout_ms: Option<i32>,
cancel: Option<&AtomicBool>,
) -> Result<HttpConnection> {
dest.connect(flags, timeout_ms, cancel)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::destination::get_all_destinations;
#[test]
fn test_connection_flags() {
assert_eq!(u32::from(ConnectionFlags::Scheduler), 0);
assert_eq!(u32::from(ConnectionFlags::Device), 1);
}
#[test]
fn test_connect_to_scheduler() {
if let Ok(destinations) = get_all_destinations() {
if let Some(dest) = destinations.first() {
match dest.connect(ConnectionFlags::Scheduler, Some(1000), None) {
Ok(conn) => {
assert!(conn.is_connected());
assert!(!conn.resource_path().is_empty());
println!("Connected to '{}' with resource path: '{}'",
dest.name, conn.resource_path());
}
Err(e) => {
println!("Connection failed (expected in test): {}", e);
}
}
}
}
}
}