use crate::{
abilities::Abilities,
file::{CameraFile, CameraFilePath},
filesys::{CameraFS, StorageInfo},
helper::{as_ref, char_slice_to_cow, chars_to_string, to_c_string},
port::PortInfo,
try_gp_internal,
widget::{Widget, WidgetType},
Result,
};
use std::{ffi, os::raw::c_char, time::Duration};
#[derive(Debug)]
pub enum CameraEvent {
Unknown(String),
Timeout,
NewFile(CameraFilePath),
FileChanged(CameraFilePath),
NewFolder(CameraFilePath),
CaptureComplete,
}
pub struct Camera {
pub(crate) camera: *mut libgphoto2_sys::Camera,
pub(crate) context: *mut libgphoto2_sys::GPContext,
}
impl Drop for Camera {
fn drop(&mut self) {
unsafe {
libgphoto2_sys::gp_camera_unref(self.camera);
libgphoto2_sys::gp_context_unref(self.context);
}
}
}
as_ref!(Camera -> libgphoto2_sys::Camera, *self.camera);
impl Camera {
pub(crate) fn new(
camera: *mut libgphoto2_sys::Camera,
context: *mut libgphoto2_sys::GPContext,
) -> Self {
unsafe { libgphoto2_sys::gp_context_ref(context) }
Self { camera, context }
}
pub fn capture_image(&self) -> Result<CameraFilePath> {
try_gp_internal!(gp_camera_capture(
self.camera,
libgphoto2_sys::CameraCaptureType::GP_CAPTURE_IMAGE,
&out file_path_ptr,
self.context
));
Ok(file_path_ptr.into())
}
pub fn capture_preview(&self) -> Result<CameraFile> {
let camera_file = CameraFile::new()?;
try_gp_internal!(gp_camera_capture_preview(self.camera, camera_file.inner, self.context));
Ok(camera_file)
}
pub fn abilities(&self) -> Result<Abilities> {
try_gp_internal!(gp_camera_get_abilities(self.camera, &out abilities));
Ok(abilities.into())
}
pub fn summary(&self) -> Result<String> {
try_gp_internal!(gp_camera_get_summary(self.camera, &out summary, self.context));
Ok(char_slice_to_cow(&summary.text).into_owned())
}
pub fn about(&self) -> Result<String> {
try_gp_internal!(gp_camera_get_about(self.camera, &out about, self.context));
Ok(char_slice_to_cow(&about.text).into_owned())
}
pub fn manual(&self) -> Result<String> {
try_gp_internal!(gp_camera_get_manual(self.camera, &out manual, self.context));
Ok(char_slice_to_cow(&manual.text).into_owned())
}
pub fn storages(&self) -> Result<Vec<StorageInfo>> {
try_gp_internal!(gp_camera_get_storageinfo(
self.camera,
&out storages_ptr,
&out storages_len,
self.context
));
let storages = unsafe {
std::slice::from_raw_parts(
storages_ptr.cast::<StorageInfo>(),
storages_len as usize,
)
};
let result = storages.to_vec();
unsafe {
libc::free(storages_ptr.cast());
}
Ok(result)
}
pub fn fs(&self) -> CameraFS<'_> {
CameraFS::new(self)
}
pub fn wait_event(&self, timeout: Duration) -> Result<CameraEvent> {
use libgphoto2_sys::CameraEventType;
let duration_milliseconds = timeout.as_millis();
try_gp_internal!(gp_camera_wait_for_event(
self.camera,
duration_milliseconds as i32,
&out event_type,
&out event_data,
self.context
));
Ok(match event_type {
CameraEventType::GP_EVENT_UNKNOWN => {
let s = chars_to_string(event_data.cast::<c_char>());
unsafe {
libc::free(event_data);
}
CameraEvent::Unknown(s)
}
CameraEventType::GP_EVENT_TIMEOUT => CameraEvent::Timeout,
CameraEventType::GP_EVENT_FILE_ADDED
| CameraEventType::GP_EVENT_FOLDER_ADDED
| CameraEventType::GP_EVENT_FILE_CHANGED => {
let file_path =
CameraFilePath::from(unsafe { *event_data.cast::<libgphoto2_sys::CameraFilePath>() });
unsafe {
libc::free(event_data);
}
match event_type {
CameraEventType::GP_EVENT_FILE_ADDED => CameraEvent::NewFile(file_path),
CameraEventType::GP_EVENT_FOLDER_ADDED => CameraEvent::NewFolder(file_path),
CameraEventType::GP_EVENT_FILE_CHANGED => CameraEvent::FileChanged(file_path),
_ => unreachable!(),
}
}
CameraEventType::GP_EVENT_CAPTURE_COMPLETE => CameraEvent::CaptureComplete,
})
}
pub fn port_info(&self) -> Result<PortInfo> {
try_gp_internal!(gp_camera_get_port_info(self.camera, &out port_info));
Ok(PortInfo { inner: port_info })
}
pub fn config(&self) -> Result<Widget> {
try_gp_internal!(gp_camera_get_config(self.camera, &out root_widget, self.context));
Ok(Widget::new_owned(root_widget))
}
pub fn config_key(&self, key: &str) -> Result<Widget> {
try_gp_internal!(gp_camera_get_single_config(
self.camera,
to_c_string!(key),
&out widget,
self.context
));
Ok(Widget::new_owned(widget))
}
pub fn set_all_config(&self, config: &Widget) -> Result<()> {
if !matches!(config.widget_type()?, WidgetType::Window | WidgetType::Section) {
Err("Full config object must be of type Window or section")?;
}
try_gp_internal!(gp_camera_set_config(self.camera, config.inner, self.context));
Ok(())
}
pub fn set_config(&self, config: &Widget) -> Result<()> {
try_gp_internal!(gp_camera_set_single_config(
self.camera,
to_c_string!(config.name()?),
config.inner,
self.context
));
Ok(())
}
}