use crate::{
abilities::AbilitiesList,
camera::Camera,
helper::{as_ref, chars_to_string, libtool_lock, to_c_string},
list::CameraList,
list::{CameraDescriptor, CameraListIter},
port::PortInfoList,
try_gp_internal, Error, Result,
};
use std::{ffi, rc::Rc};
pub trait ProgressHandler: 'static {
fn start(&mut self, target: f32, message: String) -> u32;
fn update(&mut self, id: u32, progress: f32);
fn stop(&mut self, id: u32);
}
pub struct Context {
pub(crate) inner: *mut libgphoto2_sys::GPContext,
progress_handler: Option<Rc<dyn ProgressHandler>>,
}
impl Drop for Context {
fn drop(&mut self) {
unsafe { libgphoto2_sys::gp_context_unref(self.inner) }
}
}
impl Clone for Context {
fn clone(&self) -> Self {
unsafe {
libgphoto2_sys::gp_context_ref(self.inner);
}
Self { inner: self.inner, progress_handler: self.progress_handler.clone() }
}
}
as_ref!(Context -> libgphoto2_sys::GPContext, *self.inner);
impl Context {
pub fn new() -> Result<Self> {
#[cfg(feature = "extended_logs")]
crate::helper::hook_gp_log();
let context_ptr = unsafe { libgphoto2_sys::gp_context_new() };
if context_ptr.is_null() {
return Err(Error::new(libgphoto2_sys::GP_ERROR_NO_MEMORY, None));
}
#[cfg(not(feature = "extended_logs"))]
crate::helper::hook_gp_context_log_func(context_ptr);
Ok(Self { inner: context_ptr, progress_handler: None })
}
pub fn list_cameras(&self) -> Result<CameraListIter> {
let _lock = libtool_lock();
let camera_list = CameraList::new()?;
try_gp_internal!(gp_camera_autodetect(camera_list.inner, self.inner)?);
Ok(CameraListIter::new(camera_list))
}
pub fn autodetect_camera(&self) -> Result<Camera> {
let _lock = libtool_lock();
try_gp_internal!(gp_camera_new(&out camera_ptr)?);
try_gp_internal!(gp_camera_init(camera_ptr, self.inner)?);
Ok(Camera::new(camera_ptr, self.clone()))
}
pub fn get_camera(&self, camera_desc: &CameraDescriptor) -> Result<Camera> {
let abilities_list = AbilitiesList::new(self)?;
let port_info_list = PortInfoList::new()?;
try_gp_internal!(gp_camera_new(&out camera)?);
try_gp_internal!(let model_index = gp_abilities_list_lookup_model(
abilities_list.inner,
to_c_string!(camera_desc.model.as_str())
)?);
try_gp_internal!(gp_abilities_list_get_abilities(
abilities_list.inner,
model_index,
&out model_abilities
)?);
try_gp_internal!(gp_camera_set_abilities(camera, model_abilities)?);
try_gp_internal!(let p = gp_port_info_list_lookup_path(
port_info_list.inner,
to_c_string!(camera_desc.port.as_str())
)?);
let port_info = port_info_list.get_port_info(p)?;
try_gp_internal!(gp_camera_set_port_info(camera, port_info.inner)?);
Ok(Camera::new(camera, self.clone()))
}
pub fn set_progress_functions<H: ProgressHandler>(&mut self, handler: H) {
use std::os::raw::{c_char, c_float, c_uint, c_void};
unsafe fn as_handler<H>(data: *mut c_void) -> &'static mut H {
&mut *data.cast()
}
unsafe extern "C" fn start_func<H: ProgressHandler>(
_ctx: *mut libgphoto2_sys::GPContext,
target: c_float,
message: *const c_char,
data: *mut c_void,
) -> c_uint {
as_handler::<H>(data).start(target, chars_to_string(message))
}
unsafe extern "C" fn update_func<H: ProgressHandler>(
_ctx: *mut libgphoto2_sys::GPContext,
id: c_uint,
current: c_float,
data: *mut c_void,
) {
as_handler::<H>(data).update(id, current)
}
unsafe extern "C" fn stop_func<H: ProgressHandler>(
_ctx: *mut libgphoto2_sys::GPContext,
id: c_uint,
data: *mut c_void,
) {
as_handler::<H>(data).stop(id)
}
let progress_handler = Rc::new(handler);
#[allow(clippy::as_conversions)]
let data_ptr = Rc::as_ptr(&progress_handler) as *mut c_void;
unsafe {
libgphoto2_sys::gp_context_set_progress_funcs(
self.inner,
Some(start_func::<H>),
Some(update_func::<H>),
Some(stop_func::<H>),
data_ptr,
);
}
self.progress_handler = Some(progress_handler);
}
}
#[cfg(all(test, feature = "test"))]
mod tests {
#[test]
fn test_list_cameras() {
let cameras = crate::sample_context().list_cameras().unwrap().collect::<Vec<_>>();
insta::assert_debug_snapshot!(cameras);
}
#[test]
fn test_progress() {
use std::fmt::Write;
let mut context = crate::sample_context();
#[derive(Default)]
struct TestProgress {
log_lines: String,
next_progress_id: u32,
}
impl Drop for TestProgress {
fn drop(&mut self) {
insta::assert_snapshot!("progress", self.log_lines);
}
}
impl crate::context::ProgressHandler for TestProgress {
fn start(&mut self, target: f32, message: String) -> u32 {
let id = self.next_progress_id;
#[cfg(windows)]
let target = target / 2.0;
self.next_progress_id += 1;
writeln!(
self.log_lines,
"start #{id}: target: {target}, message: {message}",
message = message.replace(
&libgphoto2_sys::test_utils::libgphoto2_dir().to_str().unwrap().replace('\\', "/"),
"$LIBGPHOTO2_DIR"
),
)
.unwrap();
id
}
fn update(&mut self, id: u32, progress: f32) {
writeln!(self.log_lines, "update #{id}: progress: {progress}").unwrap();
}
fn stop(&mut self, id: u32) {
writeln!(self.log_lines, "stop #{id}").unwrap();
}
}
context.set_progress_functions(TestProgress::default());
let _ignore = context.list_cameras();
}
}