1use crate::{
4 file::{CameraFile, FileType},
5 helper::{bitflags, char_slice_to_cow, to_c_string, UninitBox},
6 list::{CameraList, FileListIter},
7 task::Task,
8 try_gp_internal, Camera, Error, Result,
9};
10use libgphoto2_sys::time_t;
11use std::{borrow::Cow, ffi, fmt, fs, path::Path};
12
13macro_rules! storage_info {
14 ($(# $attr:tt)* $name:ident: $bitflag_ty:ident, |$inner:ident: $inner_ty:ident| { $($(# $field_attr:tt)* $field:ident: $ty:ty = $bitflag:ident, $expr:expr;)* }) => {
15 $(# $attr)*
16 #[repr(transparent)]
17 #[derive(Clone)]
18 pub struct $name {
19 inner: libgphoto2_sys::$inner_ty,
20 }
21
22 impl $name {
23 #[allow(dead_code)]
24 pub(crate) fn from_inner_ref(ptr: &libgphoto2_sys::$inner_ty) -> &Self {
25 let ptr: *const _ = ptr;
26 unsafe { &*ptr.cast::<Self>() }
28 }
29
30 $(
31 $(# $field_attr)*
32 pub fn $field(&self) -> Option<$ty> {
33 let $inner = &self.inner;
34 if ($inner.fields & libgphoto2_sys::$bitflag_ty::$bitflag).0 != 0 {
35 Some($expr)
36 } else {
37 None
38 }
39 }
40 )*
41 }
42
43 impl fmt::Debug for $name {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 f.debug_struct(stringify!($name))
46 $(
47 .field(stringify!($field), &self.$field())
48 )*
49 .finish()
50 }
51 }
52 };
53}
54
55#[derive(Debug, Hash, PartialEq, Eq)]
57#[cfg_attr(feature = "serde", derive(serde::Serialize))]
58pub enum StorageType {
59 Unknown,
61 FixedRom,
63 RemovableRom,
65 FixedRam,
67 RemovableRam,
69}
70
71#[derive(Debug, Hash, PartialEq, Eq)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize))]
74pub enum FilesystemType {
75 Unknown,
77 Flat,
79 Tree,
81 Dcf,
83}
84
85#[derive(Debug, Hash, PartialEq, Eq)]
87#[cfg_attr(feature = "serde", derive(serde::Serialize))]
88pub enum AccessType {
89 Rw,
91 Ro,
93 RoDelete,
95}
96
97bitflags!(
98 FileStatus = CameraFileStatus {
100 downloaded: GP_FILE_STATUS_DOWNLOADED,
102 }
103);
104
105bitflags!(
106 FilePermissions = CameraFilePermissions {
108 read: GP_FILE_PERM_READ,
110
111 delete: GP_FILE_PERM_DELETE,
113 }
114);
115
116storage_info!(
117 FileInfoPreview: CameraFileInfoFields, |info: CameraFileInfoPreview| {
119 status: FileStatus = GP_FILE_INFO_STATUS, info.status.into();
121 size: u64 = GP_FILE_INFO_SIZE, info.size;
123 mime_type: Cow<str> = GP_FILE_INFO_TYPE, char_slice_to_cow(&info.type_);
125 width: u32 = GP_FILE_INFO_WIDTH, info.width;
127 height: u32 = GP_FILE_INFO_HEIGHT, info.height;
129 }
130);
131
132storage_info!(
133 FileInfoFile: CameraFileInfoFields, |info: CameraFileInfoFile| {
135 status: FileStatus = GP_FILE_INFO_STATUS, info.status.into();
137 size: u64 = GP_FILE_INFO_SIZE, info.size;
139 mime_type: Cow<str> = GP_FILE_INFO_TYPE, char_slice_to_cow(&info.type_);
141 width: u32 = GP_FILE_INFO_WIDTH, info.width;
143 height: u32 = GP_FILE_INFO_HEIGHT, info.height;
145 permissions: FilePermissions = GP_FILE_INFO_PERMISSIONS, info.permissions.into();
147 mtime: time_t = GP_FILE_INFO_MTIME, info.mtime;
149 }
150);
151
152storage_info!(
153 FileInfoAudio: CameraFileInfoFields, |info: CameraFileInfoAudio| {
155 status: FileStatus = GP_FILE_INFO_STATUS, info.status.into();
157 size: u64 = GP_FILE_INFO_SIZE, info.size;
159 mime_type: Cow<str> = GP_FILE_INFO_TYPE, char_slice_to_cow(&info.type_);
161 }
162);
163
164pub struct FileInfo {
166 pub(crate) inner: Box<libgphoto2_sys::CameraFileInfo>,
168}
169
170impl FileInfo {
171 pub fn preview(&self) -> &FileInfoPreview {
173 FileInfoPreview::from_inner_ref(&self.inner.preview)
174 }
175
176 pub fn file(&self) -> &FileInfoFile {
178 FileInfoFile::from_inner_ref(&self.inner.file)
179 }
180
181 pub fn audio(&self) -> &FileInfoAudio {
183 FileInfoAudio::from_inner_ref(&self.inner.audio)
184 }
185}
186
187impl fmt::Debug for FileInfo {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 f.debug_struct("FileInfo")
190 .field("preview", &self.preview())
191 .field("file", &self.file())
192 .field("audio", &self.audio())
193 .finish()
194 }
195}
196
197pub struct CameraFS<'a> {
199 pub(crate) camera: &'a Camera,
200}
201
202impl From<libgphoto2_sys::CameraStorageType> for StorageType {
203 fn from(storage_type: libgphoto2_sys::CameraStorageType) -> Self {
204 use libgphoto2_sys::CameraStorageType;
205
206 match storage_type {
207 CameraStorageType::GP_STORAGEINFO_ST_UNKNOWN => Self::Unknown,
208 CameraStorageType::GP_STORAGEINFO_ST_FIXED_ROM => Self::FixedRom,
209 CameraStorageType::GP_STORAGEINFO_ST_REMOVABLE_ROM => Self::RemovableRom,
210 CameraStorageType::GP_STORAGEINFO_ST_FIXED_RAM => Self::FixedRam,
211 CameraStorageType::GP_STORAGEINFO_ST_REMOVABLE_RAM => Self::RemovableRam,
212 }
213 }
214}
215
216impl From<libgphoto2_sys::CameraStorageFilesystemType> for FilesystemType {
217 fn from(fs_type: libgphoto2_sys::CameraStorageFilesystemType) -> Self {
218 use libgphoto2_sys::CameraStorageFilesystemType as GPFsType;
219
220 match fs_type {
221 GPFsType::GP_STORAGEINFO_FST_UNDEFINED => Self::Unknown,
222 GPFsType::GP_STORAGEINFO_FST_GENERICFLAT => Self::Flat,
223 GPFsType::GP_STORAGEINFO_FST_GENERICHIERARCHICAL => Self::Tree,
224 GPFsType::GP_STORAGEINFO_FST_DCF => Self::Dcf,
225 }
226 }
227}
228
229impl From<libgphoto2_sys::CameraStorageAccessType> for AccessType {
230 fn from(access_type: libgphoto2_sys::CameraStorageAccessType) -> Self {
231 use libgphoto2_sys::CameraStorageAccessType as GPAccessType;
232
233 match access_type {
234 GPAccessType::GP_STORAGEINFO_AC_READWRITE => Self::Rw,
235 GPAccessType::GP_STORAGEINFO_AC_READONLY => Self::Ro,
236 GPAccessType::GP_STORAGEINFO_AC_READONLY_WITH_DELETE => Self::RoDelete,
237 }
238 }
239}
240
241storage_info!(
242 StorageInfo: CameraStorageInfoFields, |info: CameraStorageInformation| {
244 label: Cow<str> = GP_STORAGEINFO_LABEL, char_slice_to_cow(&info.label);
246 base_directory: Cow<str> = GP_STORAGEINFO_BASE, char_slice_to_cow(&info.basedir);
248 description: Cow<str> = GP_STORAGEINFO_DESCRIPTION, char_slice_to_cow(&info.description);
250 storage_type: StorageType = GP_STORAGEINFO_STORAGETYPE, info.type_.into();
252 filesystem_type: FilesystemType = GP_STORAGEINFO_FILESYSTEMTYPE, info.fstype.into();
254 access_type: AccessType = GP_STORAGEINFO_ACCESS, info.access.into();
256 capacity_kb: u64 = GP_STORAGEINFO_MAXCAPACITY, info.capacitykbytes * 1024;
258 free_kb: u64 = GP_STORAGEINFO_FREESPACEKBYTES, info.freekbytes * 1024;
260 free_images: u64 = GP_STORAGEINFO_FREESPACEIMAGES, info.freeimages;
262 }
263);
264
265impl<'a> CameraFS<'a> {
266 pub(crate) fn new(camera: &'a Camera) -> Self {
267 Self { camera }
268 }
269
270 pub fn delete_file(&self, folder: &str, file: &str) -> Task<Result<()>> {
272 let camera = self.camera.camera;
273 let context = self.camera.context.inner;
274 let (folder, file) = (folder.to_owned(), file.to_owned());
275
276 unsafe {
277 Task::new(move || {
278 try_gp_internal!(gp_camera_file_delete(
279 *camera,
280 to_c_string!(folder),
281 to_c_string!(file),
282 *context
283 )?);
284 Ok(())
285 })
286 }
287 .context(context)
288 }
289
290 pub fn file_info(&self, folder: &str, file: &str) -> Task<Result<FileInfo>> {
292 let camera = self.camera.camera;
293 let context = self.camera.context.inner;
294 let (folder, file) = (folder.to_owned(), file.to_owned());
295
296 unsafe {
297 Task::new(move || {
298 let mut inner = UninitBox::uninit();
299
300 try_gp_internal!(gp_camera_file_get_info(
301 *camera,
302 to_c_string!(folder),
303 to_c_string!(file),
304 inner.as_mut_ptr(),
305 *context
306 )?);
307
308 Ok(FileInfo { inner: inner.assume_init() })
309 })
310 }
311 .context(context)
312 }
313
314 pub fn download_to(&self, folder: &str, file: &str, path: &Path) -> Task<Result<CameraFile>> {
316 self.to_camera_file(folder, file, FileType::Normal, Some(path))
317 }
318
319 pub fn download(&self, folder: &str, file: &str) -> Task<Result<CameraFile>> {
321 self.to_camera_file(folder, file, FileType::Normal, None)
322 }
323
324 pub fn download_preview(&self, folder: &str, file: &str) -> Task<Result<CameraFile>> {
326 self.to_camera_file(folder, file, FileType::Preview, None)
327 }
328
329 pub fn download_exif(&self, folder: &str, file: &str) -> Task<Result<CameraFile>> {
331 self.to_camera_file(folder, file, FileType::Exif, None)
332 }
333
334 #[allow(clippy::boxed_local)]
336 pub fn upload_file(&self, folder: &str, filename: &str, data: Box<[u8]>) -> Task<Result<()>> {
337 let camera = self.camera.camera;
338 let context = self.camera.context.inner;
339
340 let (folder, filename) = (folder.to_owned(), filename.to_owned());
341
342 unsafe {
343 Task::new(move || {
344 try_gp_internal!(gp_file_new(&out file)?);
345 try_gp_internal!(gp_file_append(file, data.as_ptr().cast(), data.len().try_into()?)?);
346 try_gp_internal!(gp_camera_folder_put_file(
347 *camera,
348 to_c_string!(folder),
349 to_c_string!(filename),
350 FileType::Normal.into(),
351 file,
352 *context
353 )?);
354
355 Ok(())
356 })
357 }
358 .context(context)
359 }
360
361 pub fn delete_all_in_folder(&self, folder: &str) -> Task<Result<()>> {
363 let camera = self.camera.camera;
364 let context = self.camera.context.inner;
365 let folder = folder.to_owned();
366
367 unsafe {
368 Task::new(move || {
369 try_gp_internal!(gp_camera_folder_delete_all(*camera, to_c_string!(folder), *context)?);
370 Ok(())
371 })
372 }
373 .context(context)
374 }
375
376 pub fn list_files(&self, folder: &str) -> Task<Result<FileListIter>> {
378 let camera = self.camera.camera;
379 let context = self.camera.context.inner;
380
381 let folder = folder.to_owned();
382
383 unsafe {
384 Task::new(move || {
385 let file_list = CameraList::new()?;
386
387 try_gp_internal!(gp_camera_folder_list_files(
388 *camera,
389 to_c_string!(folder),
390 *file_list.inner,
391 *context
392 )?);
393
394 Ok(FileListIter::new(file_list))
395 })
396 }
397 .context(context)
398 }
399
400 pub fn list_folders(&self, folder: &str) -> Task<Result<FileListIter>> {
402 let camera = self.camera.camera;
403 let context = self.camera.context.inner;
404
405 let folder = folder.to_owned();
406
407 unsafe {
408 Task::new(move || {
409 let folder_list = CameraList::new()?;
410
411 try_gp_internal!(gp_camera_folder_list_folders(
412 *camera,
413 to_c_string!(folder),
414 *folder_list.inner,
415 *context
416 )?);
417
418 Ok(FileListIter::new(folder_list))
419 })
420 }
421 .context(context)
422 }
423
424 pub fn create_directory(&self, parent_folder: &str, new_folder: &str) -> Task<Result<()>> {
426 let (parent_folder, new_folder) = (parent_folder.to_owned(), new_folder.to_owned());
427 let camera = self.camera.camera;
428 let context = self.camera.context.inner;
429
430 unsafe {
431 Task::new(move || {
432 try_gp_internal!(gp_camera_folder_make_dir(
433 *camera,
434 to_c_string!(parent_folder),
435 to_c_string!(new_folder),
436 *context
437 )?);
438
439 Ok(())
440 })
441 }
442 .context(context)
443 }
444
445 pub fn remove_directory(&self, parent: &str, to_remove: &str) -> Task<Result<()>> {
447 let (parent, to_remove) = (parent.to_owned(), to_remove.to_owned());
448 let camera = self.camera.camera;
449 let context = self.camera.context.inner;
450
451 unsafe {
452 Task::new(move || {
453 try_gp_internal!(gp_camera_folder_remove_dir(
454 *camera,
455 to_c_string!(parent),
456 to_c_string!(to_remove),
457 *context
458 )?);
459
460 Ok(())
461 })
462 }
463 .context(context)
464 }
465}
466
467impl CameraFS<'_> {
469 fn to_camera_file(
470 &self,
471 folder: &str,
472 file: &str,
473 type_: FileType,
474 path: Option<&Path>,
475 ) -> Task<Result<CameraFile>> {
476 let (folder, file, path) = (folder.to_owned(), file.to_owned(), path.map(ToOwned::to_owned));
477 let camera = self.camera.camera;
478 let context = self.camera.context.inner;
479
480 unsafe {
481 Task::new(move || {
482 let camera_file = match &path {
483 Some(dest_path) => CameraFile::new_file(dest_path)?,
484 None => CameraFile::new()?,
485 };
486
487 try_gp_internal!(gp_camera_file_get(
488 *camera,
489 to_c_string!(folder),
490 to_c_string!(file),
491 type_.into(),
492 *camera_file.inner,
493 *context
494 )
495 .map_err(|e| {
496 if let Some(path) = path {
497 if let Err(error) = fs::remove_file(path) {
498 return Into::<Error>::into(error);
499 }
500 }
501
502 e
503 })?);
504
505 Ok(camera_file)
506 })
507 }
508 .context(context)
509 }
510}
511
512impl AsRef<crate::Context> for &CameraFS<'_> {
513 fn as_ref(&self) -> &crate::Context {
514 &self.camera.context
515 }
516}