use crate::{
file::{CameraFile, FileType},
helper::{bitflags, char_slice_to_cow, to_c_string, UninitBox},
list::{CameraList, FileListIter},
task::Task,
try_gp_internal, Camera, Error, Result,
};
use libgphoto2_sys::time_t;
use std::{borrow::Cow, ffi, fmt, fs, path::Path};
macro_rules! storage_info {
($(# $attr:tt)* $name:ident: $bitflag_ty:ident, |$inner:ident: $inner_ty:ident| { $($(# $field_attr:tt)* $field:ident: $ty:ty = $bitflag:ident, $expr:expr;)* }) => {
$(# $attr)*
#[repr(transparent)]
#[derive(Clone)]
pub struct $name {
inner: libgphoto2_sys::$inner_ty,
}
impl $name {
#[allow(dead_code)]
pub(crate) fn from_inner_ref(ptr: &libgphoto2_sys::$inner_ty) -> &Self {
let ptr: *const _ = ptr;
unsafe { &*ptr.cast::<Self>() }
}
$(
$(# $field_attr)*
pub fn $field(&self) -> Option<$ty> {
let $inner = &self.inner;
if ($inner.fields & libgphoto2_sys::$bitflag_ty::$bitflag).0 != 0 {
Some($expr)
} else {
None
}
}
)*
}
impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct(stringify!($name))
$(
.field(stringify!($field), &self.$field())
)*
.finish()
}
}
};
}
#[derive(Debug, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum StorageType {
Unknown,
FixedRom,
RemovableRom,
FixedRam,
RemovableRam,
}
#[derive(Debug, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum FilesystemType {
Unknown,
Flat,
Tree,
Dcf,
}
#[derive(Debug, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum AccessType {
Rw,
Ro,
RoDelete,
}
bitflags!(
FileStatus = CameraFileStatus {
downloaded: GP_FILE_STATUS_DOWNLOADED,
}
);
bitflags!(
FilePermissions = CameraFilePermissions {
read: GP_FILE_PERM_READ,
delete: GP_FILE_PERM_DELETE,
}
);
storage_info!(
FileInfoPreview: CameraFileInfoFields, |info: CameraFileInfoPreview| {
status: FileStatus = GP_FILE_INFO_STATUS, info.status.into();
size: u64 = GP_FILE_INFO_SIZE, info.size;
mime_type: Cow<str> = GP_FILE_INFO_TYPE, char_slice_to_cow(&info.type_);
width: u32 = GP_FILE_INFO_WIDTH, info.width;
height: u32 = GP_FILE_INFO_HEIGHT, info.height;
}
);
storage_info!(
FileInfoFile: CameraFileInfoFields, |info: CameraFileInfoFile| {
status: FileStatus = GP_FILE_INFO_STATUS, info.status.into();
size: u64 = GP_FILE_INFO_SIZE, info.size;
mime_type: Cow<str> = GP_FILE_INFO_TYPE, char_slice_to_cow(&info.type_);
width: u32 = GP_FILE_INFO_WIDTH, info.width;
height: u32 = GP_FILE_INFO_HEIGHT, info.height;
permissions: FilePermissions = GP_FILE_INFO_PERMISSIONS, info.permissions.into();
mtime: time_t = GP_FILE_INFO_MTIME, info.mtime;
}
);
storage_info!(
FileInfoAudio: CameraFileInfoFields, |info: CameraFileInfoAudio| {
status: FileStatus = GP_FILE_INFO_STATUS, info.status.into();
size: u64 = GP_FILE_INFO_SIZE, info.size;
mime_type: Cow<str> = GP_FILE_INFO_TYPE, char_slice_to_cow(&info.type_);
}
);
pub struct FileInfo {
pub(crate) inner: Box<libgphoto2_sys::CameraFileInfo>,
}
impl FileInfo {
pub fn preview(&self) -> &FileInfoPreview {
FileInfoPreview::from_inner_ref(&self.inner.preview)
}
pub fn file(&self) -> &FileInfoFile {
FileInfoFile::from_inner_ref(&self.inner.file)
}
pub fn audio(&self) -> &FileInfoAudio {
FileInfoAudio::from_inner_ref(&self.inner.audio)
}
}
impl fmt::Debug for FileInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FileInfo")
.field("preview", &self.preview())
.field("file", &self.file())
.field("audio", &self.audio())
.finish()
}
}
pub struct CameraFS<'a> {
pub(crate) camera: &'a Camera,
}
impl From<libgphoto2_sys::CameraStorageType> for StorageType {
fn from(storage_type: libgphoto2_sys::CameraStorageType) -> Self {
use libgphoto2_sys::CameraStorageType;
match storage_type {
CameraStorageType::GP_STORAGEINFO_ST_UNKNOWN => Self::Unknown,
CameraStorageType::GP_STORAGEINFO_ST_FIXED_ROM => Self::FixedRom,
CameraStorageType::GP_STORAGEINFO_ST_REMOVABLE_ROM => Self::RemovableRom,
CameraStorageType::GP_STORAGEINFO_ST_FIXED_RAM => Self::FixedRam,
CameraStorageType::GP_STORAGEINFO_ST_REMOVABLE_RAM => Self::RemovableRam,
}
}
}
impl From<libgphoto2_sys::CameraStorageFilesystemType> for FilesystemType {
fn from(fs_type: libgphoto2_sys::CameraStorageFilesystemType) -> Self {
use libgphoto2_sys::CameraStorageFilesystemType as GPFsType;
match fs_type {
GPFsType::GP_STORAGEINFO_FST_UNDEFINED => Self::Unknown,
GPFsType::GP_STORAGEINFO_FST_GENERICFLAT => Self::Flat,
GPFsType::GP_STORAGEINFO_FST_GENERICHIERARCHICAL => Self::Tree,
GPFsType::GP_STORAGEINFO_FST_DCF => Self::Dcf,
}
}
}
impl From<libgphoto2_sys::CameraStorageAccessType> for AccessType {
fn from(access_type: libgphoto2_sys::CameraStorageAccessType) -> Self {
use libgphoto2_sys::CameraStorageAccessType as GPAccessType;
match access_type {
GPAccessType::GP_STORAGEINFO_AC_READWRITE => Self::Rw,
GPAccessType::GP_STORAGEINFO_AC_READONLY => Self::Ro,
GPAccessType::GP_STORAGEINFO_AC_READONLY_WITH_DELETE => Self::RoDelete,
}
}
}
storage_info!(
StorageInfo: CameraStorageInfoFields, |info: CameraStorageInformation| {
label: Cow<str> = GP_STORAGEINFO_LABEL, char_slice_to_cow(&info.label);
base_directory: Cow<str> = GP_STORAGEINFO_BASE, char_slice_to_cow(&info.basedir);
description: Cow<str> = GP_STORAGEINFO_DESCRIPTION, char_slice_to_cow(&info.description);
storage_type: StorageType = GP_STORAGEINFO_STORAGETYPE, info.type_.into();
filesystem_type: FilesystemType = GP_STORAGEINFO_FILESYSTEMTYPE, info.fstype.into();
access_type: AccessType = GP_STORAGEINFO_ACCESS, info.access.into();
capacity_kb: u64 = GP_STORAGEINFO_MAXCAPACITY, info.capacitykbytes * 1024;
free_kb: u64 = GP_STORAGEINFO_FREESPACEKBYTES, info.freekbytes * 1024;
free_images: u64 = GP_STORAGEINFO_FREESPACEIMAGES, info.freeimages;
}
);
impl<'a> CameraFS<'a> {
pub(crate) fn new(camera: &'a Camera) -> Self {
Self { camera }
}
pub fn delete_file(&self, folder: &str, file: &str) -> Task<Result<()>> {
let camera = self.camera.camera;
let context = self.camera.context.inner;
let (folder, file) = (folder.to_owned(), file.to_owned());
unsafe {
Task::new(move || {
try_gp_internal!(gp_camera_file_delete(
*camera,
to_c_string!(folder),
to_c_string!(file),
*context
)?);
Ok(())
})
}
.context(context)
}
pub fn file_info(&self, folder: &str, file: &str) -> Task<Result<FileInfo>> {
let camera = self.camera.camera;
let context = self.camera.context.inner;
let (folder, file) = (folder.to_owned(), file.to_owned());
unsafe {
Task::new(move || {
let mut inner = UninitBox::uninit();
try_gp_internal!(gp_camera_file_get_info(
*camera,
to_c_string!(folder),
to_c_string!(file),
inner.as_mut_ptr(),
*context
)?);
Ok(FileInfo { inner: inner.assume_init() })
})
}
.context(context)
}
pub fn download_to(&self, folder: &str, file: &str, path: &Path) -> Task<Result<CameraFile>> {
self.to_camera_file(folder, file, FileType::Normal, Some(path))
}
pub fn download(&self, folder: &str, file: &str) -> Task<Result<CameraFile>> {
self.to_camera_file(folder, file, FileType::Normal, None)
}
pub fn download_preview(&self, folder: &str, file: &str) -> Task<Result<CameraFile>> {
self.to_camera_file(folder, file, FileType::Preview, None)
}
pub fn download_exif(&self, folder: &str, file: &str) -> Task<Result<CameraFile>> {
self.to_camera_file(folder, file, FileType::Exif, None)
}
#[allow(clippy::boxed_local)]
pub fn upload_file(&self, folder: &str, filename: &str, data: Box<[u8]>) -> Task<Result<()>> {
let camera = self.camera.camera;
let context = self.camera.context.inner;
let (folder, filename) = (folder.to_owned(), filename.to_owned());
unsafe {
Task::new(move || {
try_gp_internal!(gp_file_new(&out file)?);
try_gp_internal!(gp_file_append(file, data.as_ptr().cast(), data.len().try_into()?)?);
try_gp_internal!(gp_camera_folder_put_file(
*camera,
to_c_string!(folder),
to_c_string!(filename),
FileType::Normal.into(),
file,
*context
)?);
Ok(())
})
}
.context(context)
}
pub fn delete_all_in_folder(&self, folder: &str) -> Task<Result<()>> {
let camera = self.camera.camera;
let context = self.camera.context.inner;
let folder = folder.to_owned();
unsafe {
Task::new(move || {
try_gp_internal!(gp_camera_folder_delete_all(*camera, to_c_string!(folder), *context)?);
Ok(())
})
}
.context(context)
}
pub fn list_files(&self, folder: &str) -> Task<Result<FileListIter>> {
let camera = self.camera.camera;
let context = self.camera.context.inner;
let folder = folder.to_owned();
unsafe {
Task::new(move || {
let file_list = CameraList::new()?;
try_gp_internal!(gp_camera_folder_list_files(
*camera,
to_c_string!(folder),
*file_list.inner,
*context
)?);
Ok(FileListIter::new(file_list))
})
}
.context(context)
}
pub fn list_folders(&self, folder: &str) -> Task<Result<FileListIter>> {
let camera = self.camera.camera;
let context = self.camera.context.inner;
let folder = folder.to_owned();
unsafe {
Task::new(move || {
let folder_list = CameraList::new()?;
try_gp_internal!(gp_camera_folder_list_folders(
*camera,
to_c_string!(folder),
*folder_list.inner,
*context
)?);
Ok(FileListIter::new(folder_list))
})
}
.context(context)
}
pub fn create_directory(&self, parent_folder: &str, new_folder: &str) -> Task<Result<()>> {
let (parent_folder, new_folder) = (parent_folder.to_owned(), new_folder.to_owned());
let camera = self.camera.camera;
let context = self.camera.context.inner;
unsafe {
Task::new(move || {
try_gp_internal!(gp_camera_folder_make_dir(
*camera,
to_c_string!(parent_folder),
to_c_string!(new_folder),
*context
)?);
Ok(())
})
}
.context(context)
}
pub fn remove_directory(&self, parent: &str, to_remove: &str) -> Task<Result<()>> {
let (parent, to_remove) = (parent.to_owned(), to_remove.to_owned());
let camera = self.camera.camera;
let context = self.camera.context.inner;
unsafe {
Task::new(move || {
try_gp_internal!(gp_camera_folder_remove_dir(
*camera,
to_c_string!(parent),
to_c_string!(to_remove),
*context
)?);
Ok(())
})
}
.context(context)
}
}
impl CameraFS<'_> {
fn to_camera_file(
&self,
folder: &str,
file: &str,
type_: FileType,
path: Option<&Path>,
) -> Task<Result<CameraFile>> {
let (folder, file, path) = (folder.to_owned(), file.to_owned(), path.map(ToOwned::to_owned));
let camera = self.camera.camera;
let context = self.camera.context.inner;
unsafe {
Task::new(move || {
let camera_file = match &path {
Some(dest_path) => CameraFile::new_file(dest_path)?,
None => CameraFile::new()?,
};
try_gp_internal!(gp_camera_file_get(
*camera,
to_c_string!(folder),
to_c_string!(file),
type_.into(),
*camera_file.inner,
*context
)
.map_err(|e| {
if let Some(path) = path {
if let Err(error) = fs::remove_file(path) {
return Into::<Error>::into(error);
}
}
e
})?);
Ok(camera_file)
})
}
.context(context)
}
}