diff --git a/CHANGELOG.md b/CHANGELOG.md index c8cbbcc06..14c046cc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ but must read the entire attribute at once). - Added support in `hdf5-sys` for the new functions in `hdf5` `1.10.6` and `1.10.7`. - Added support for creating external links on a `Group` with `link_external`. +- Added `Location` methods: `get_info`, `get_info_by_name`, `loc_type`, and `open_by_token`. +- Added `Group` methods: `iter_visit`, `iter_visit_default`, `get_all_of_type`, `datasets`, `groups`, and `named_datatypes`. ### Changed diff --git a/hdf5-sys/src/h5o.rs b/hdf5-sys/src/h5o.rs index d51ad1342..0a54cc1b7 100644 --- a/hdf5-sys/src/h5o.rs +++ b/hdf5-sys/src/h5o.rs @@ -9,6 +9,12 @@ pub use { }; #[cfg(hdf5_1_12_0)] pub use {H5O_info2_t as H5O_info_t, H5O_iterate2_t as H5O_iterate_t}; +#[cfg(not(hdf5_1_10_3))] +pub use { + H5Oget_info1 as H5Oget_info, H5Oget_info_by_idx1 as H5Oget_info_by_idx, + H5Oget_info_by_name1 as H5Oget_info_by_name, H5Ovisit1 as H5Ovisit, + H5Ovisit_by_name1 as H5Ovisit_by_name, +}; use crate::internal_prelude::*; @@ -201,19 +207,24 @@ pub type H5O_mcdt_search_cb_t = #[cfg(not(hdf5_1_10_3))] extern "C" { - pub fn H5Oget_info(loc_id: hid_t, oinfo: *mut H5O_info1_t) -> herr_t; - pub fn H5Oget_info_by_name( + #[link_name = "H5Oget_info"] + pub fn H5Oget_info1(loc_id: hid_t, oinfo: *mut H5O_info1_t) -> herr_t; + #[link_name = "H5Oget_info_by_name"] + pub fn H5Oget_info_by_name1( loc_id: hid_t, name: *const c_char, oinfo: *mut H5O_info1_t, lapl_id: hid_t, ) -> herr_t; - pub fn H5Oget_info_by_idx( + #[link_name = "H5Oget_info_by_idx"] + pub fn H5Oget_info_by_idx1( loc_id: hid_t, group_name: *const c_char, idx_type: H5_index_t, order: H5_iter_order_t, n: hsize_t, oinfo: *mut H5O_info1_t, lapl_id: hid_t, ) -> herr_t; - pub fn H5Ovisit( + #[link_name = "H5Ovisit"] + pub fn H5Ovisit1( obj_id: hid_t, idx_type: H5_index_t, order: H5_iter_order_t, op: H5O_iterate1_t, op_data: *mut c_void, ) -> herr_t; - pub fn H5Ovisit_by_name( + #[link_name = "H5Ovisit_by_name"] + pub fn H5Ovisit_by_name1( loc_id: hid_t, obj_name: *const c_char, idx_type: H5_index_t, order: H5_iter_order_t, op: H5O_iterate1_t, op_data: *mut c_void, lapl_id: hid_t, ) -> herr_t; @@ -353,7 +364,7 @@ extern "C" { pub const H5O_MAX_TOKEN_SIZE: usize = 16; #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg(hdf5_1_12_0)] pub struct H5O_token_t { __data: [u8; H5O_MAX_TOKEN_SIZE], @@ -370,15 +381,15 @@ impl Default for H5O_token_t { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct H5O_info2_t { - fileno: c_ulong, - token: H5O_token_t, - type_: H5O_type_t, - rc: c_uint, - atime: time_t, - mtime: time_t, - ctime: time_t, - btime: time_t, - num_attrs: hsize_t, + pub fileno: c_ulong, + pub token: H5O_token_t, + pub type_: H5O_type_t, + pub rc: c_uint, + pub atime: time_t, + pub mtime: time_t, + pub ctime: time_t, + pub btime: time_t, + pub num_attrs: hsize_t, } #[cfg(hdf5_1_12_0)] diff --git a/src/hl.rs b/src/hl.rs index 4f0201611..3c5268b48 100644 --- a/src/hl.rs +++ b/src/hl.rs @@ -24,8 +24,8 @@ pub use self::{ dataspace::Dataspace, datatype::{Conversion, Datatype}, file::{File, FileBuilder, OpenMode}, - group::Group, - location::Location, + group::{Group, LinkInfo, LinkType}, + location::{Location, LocationInfo, LocationToken, LocationType}, object::Object, plist::PropertyList, }; diff --git a/src/hl/group.rs b/src/hl/group.rs index 4682672d7..7bb759690 100644 --- a/src/hl/group.rs +++ b/src/hl/group.rs @@ -7,14 +7,16 @@ use hdf5_sys::{ h5d::H5Dopen2, h5g::{H5G_info_t, H5Gcreate2, H5Gget_info, H5Gopen2}, h5l::{ - H5L_info_t, H5L_iterate_t, H5Lcreate_external, H5Lcreate_hard, H5Lcreate_soft, H5Ldelete, - H5Lexists, H5Literate, H5Lmove, H5L_SAME_LOC, + H5L_info_t, H5L_iterate_t, H5L_type_t, H5Lcreate_external, H5Lcreate_hard, H5Lcreate_soft, + H5Ldelete, H5Lexists, H5Literate, H5Lmove, H5L_SAME_LOC, }, h5p::{H5Pcreate, H5Pset_create_intermediate_group}, + h5t::H5T_cset_t, }; use crate::globals::H5P_LINK_CREATE; use crate::internal_prelude::*; +use crate::{Location, LocationType}; /// Represents the HDF5 group object. #[repr(transparent)] @@ -212,37 +214,193 @@ impl Group { let name = to_cstring(name)?; Dataset::from_id(h5try!(H5Dopen2(self.id(), name.as_ptr(), H5P_DEFAULT))) } +} - /// Returns names of all the members in the group, non-recursively. - pub fn member_names(&self) -> Result> { - extern "C" fn members_callback( - _id: hid_t, name: *const c_char, _info: *const H5L_info_t, op_data: *mut c_void, - ) -> herr_t { - panic::catch_unwind(|| { - let other_data: &mut Vec = unsafe { &mut *(op_data.cast::>()) }; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum TraversalOrder { + Name, + Creation, +} - other_data.push(string_from_cstr(name)); +impl Default for TraversalOrder { + fn default() -> Self { + Self::Name + } +} + +impl From for H5_index_t { + fn from(v: TraversalOrder) -> Self { + match v { + TraversalOrder::Name => Self::H5_INDEX_NAME, + TraversalOrder::Creation => Self::H5_INDEX_CRT_ORDER, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum IterationOrder { + Increasing, + Decreasing, + Native, +} + +impl Default for IterationOrder { + fn default() -> Self { + Self::Native + } +} - 0 // Continue iteration +impl From for H5_iter_order_t { + fn from(v: IterationOrder) -> Self { + match v { + IterationOrder::Increasing => Self::H5_ITER_INC, + IterationOrder::Decreasing => Self::H5_ITER_DEC, + IterationOrder::Native => Self::H5_ITER_NATIVE, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum LinkType { + Hard, + Soft, + External, +} + +impl From for LinkType { + fn from(link_type: H5L_type_t) -> Self { + match link_type { + H5L_type_t::H5L_TYPE_HARD => Self::Hard, + H5L_type_t::H5L_TYPE_SOFT => Self::Soft, + _ => Self::External, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct LinkInfo { + pub link_type: LinkType, + pub creation_order: Option, + pub is_utf8: bool, +} + +impl From<&H5L_info_t> for LinkInfo { + fn from(link: &H5L_info_t) -> Self { + let link_type = link.type_.into(); + let creation_order = if link.corder_valid == 1 { Some(link.corder) } else { None }; + let is_utf8 = link.cset == H5T_cset_t::H5T_CSET_UTF8; + Self { link_type, creation_order, is_utf8 } + } +} + +/// Iteration methods +impl Group { + /// Visits all objects in the group + pub fn iter_visit( + &self, iteration_order: IterationOrder, traversal_order: TraversalOrder, mut val: G, + mut op: F, + ) -> Result + where + F: Fn(&Self, &str, LinkInfo, &mut G) -> bool, + { + /// Struct used to pass a tuple + struct Vtable<'a, F, D> { + f: &'a mut F, + d: &'a mut D, + } + // Maps a closure to a C callback + // + // This function will be called multiple times, but never concurrently + extern "C" fn callback( + id: hid_t, name: *const c_char, info: *const H5L_info_t, op_data: *mut c_void, + ) -> herr_t + where + F: FnMut(&Group, &str, LinkInfo, &mut G) -> bool, + { + panic::catch_unwind(|| { + let vtable = op_data.cast::>(); + let vtable = unsafe { vtable.as_mut().expect("iter_visit: null op_data ptr") }; + unsafe { name.as_ref().expect("iter_visit: null name ptr") }; + let name = unsafe { std::ffi::CStr::from_ptr(name) }; + let info = unsafe { info.as_ref().expect("iter_vist: null info ptr") }; + let handle = Handle::try_new(id).expect("iter_visit: unable to create a handle"); + handle.incref(); + let group = Group::from_handle(handle); + if (vtable.f)(&group, name.to_string_lossy().as_ref(), info.into(), vtable.d) { + 0 + } else { + 1 + } }) .unwrap_or(-1) } - let callback_fn: H5L_iterate_t = Some(members_callback); - let iteration_position: *mut hsize_t = &mut { 0_u64 }; - let mut result: Vec = Vec::new(); - let other_data: *mut c_void = (&mut result as *mut Vec).cast::(); + let callback_fn: H5L_iterate_t = Some(callback::); + let iter_pos: *mut hsize_t = &mut 0_u64; + + // Store our references on the heap + let mut vtable = Vtable { f: &mut op, d: &mut val }; + let other_data = (&mut vtable as *mut Vtable<_, _>).cast::(); h5call!(H5Literate( self.id(), - H5_index_t::H5_INDEX_NAME, - H5_iter_order_t::H5_ITER_INC, - iteration_position, + traversal_order.into(), + iteration_order.into(), + iter_pos, callback_fn, other_data - ))?; + )) + .map(|_| val) + } + + /// Visits all objects in the group using default iteration/traversal order. + pub fn iter_visit_default(&self, val: G, op: F) -> Result + where + F: Fn(&Self, &str, LinkInfo, &mut G) -> bool, + { + self.iter_visit(IterationOrder::default(), TraversalOrder::default(), val, op) + } + + fn get_all_of_type(&self, loc_type: LocationType) -> Result> { + self.iter_visit_default(vec![], |group, name, _info, objects| { + if let Ok(info) = group.get_info_by_name(name) { + if info.loc_type == loc_type { + if let Ok(loc) = group.open_by_token(info.token) { + objects.push(loc); + return true; // ok, object extracted and pushed + } + } else { + return true; // ok, object is of another type, skipped + } + } + false // an error occured somewhere along the way + }) + } + + /// Returns all groups in the group, non-recursively + pub fn groups(&self) -> Result> { + self.get_all_of_type(LocationType::Group) + .map(|vec| vec.into_iter().map(|obj| unsafe { obj.cast() }).collect()) + } - Ok(result) + /// Returns all datasets in the group, non-recursively + pub fn datasets(&self) -> Result> { + self.get_all_of_type(LocationType::Dataset) + .map(|vec| vec.into_iter().map(|obj| unsafe { obj.cast() }).collect()) + } + + /// Returns all named types in the group, non-recursively + pub fn named_datatypes(&self) -> Result> { + self.get_all_of_type(LocationType::NamedDatatype) + .map(|vec| vec.into_iter().map(|obj| unsafe { obj.cast() }).collect()) + } + + /// Returns the names of all objects in the group, non-recursively. + pub fn member_names(&self) -> Result> { + self.iter_visit_default(vec![], |_, name, _, names| { + names.push(name.to_owned()); + true + }) } } @@ -471,4 +629,32 @@ pub mod tests { assert_eq!(dset2.read_scalar::().unwrap(), 13); }) } + + #[test] + pub fn test_iterators() { + with_tmp_file(|file| { + file.create_group("a").unwrap(); + file.create_group("b").unwrap(); + let group_a = file.group("a").unwrap(); + let _group_b = file.group("b").unwrap(); + file.new_dataset::().shape((10, 20)).create("a/foo").unwrap(); + file.new_dataset::().shape((10, 20)).create("a/123").unwrap(); + file.new_dataset::().shape((10, 20)).create("a/bar").unwrap(); + + let groups = file.groups().unwrap(); + assert_eq!(groups.len(), 2); + for group in groups { + assert!(matches!(group.name().as_ref(), "/a" | "/b")); + } + + let datasets = file.datasets().unwrap(); + assert_eq!(datasets.len(), 0); + + let datasets = group_a.datasets().unwrap(); + assert_eq!(datasets.len(), 3); + for dataset in datasets { + assert!(matches!(dataset.name().as_ref(), "/a/foo" | "/a/123" | "/a/bar")); + } + }) + } } diff --git a/src/hl/location.rs b/src/hl/location.rs index 14e93289a..4e25bae54 100644 --- a/src/hl/location.rs +++ b/src/hl/location.rs @@ -1,14 +1,27 @@ use std::fmt::{self, Debug}; +use std::mem::MaybeUninit; use std::ops::Deref; use std::ptr; #[allow(deprecated)] use hdf5_sys::h5o::H5Oset_comment; +#[cfg(hdf5_1_10_3)] +use hdf5_sys::h5o::H5O_INFO_BASIC; +#[cfg(hdf5_1_12_0)] +use hdf5_sys::h5o::{ + H5O_info2_t, H5O_token_t, H5Oget_info3, H5Oget_info_by_name3, H5Oopen_by_token, +}; +#[cfg(not(hdf5_1_10_3))] +use hdf5_sys::h5o::{H5Oget_info1, H5Oget_info_by_name1}; +#[cfg(all(hdf5_1_10_3, not(hdf5_1_12_0)))] +use hdf5_sys::h5o::{H5Oget_info2, H5Oget_info_by_name2}; +#[cfg(not(hdf5_1_12_0))] +use hdf5_sys::{h5::haddr_t, h5o::H5O_info1_t, h5o::H5Oopen_by_addr}; use hdf5_sys::{ h5a::H5Aopen, h5f::H5Fget_name, h5i::{H5Iget_file_id, H5Iget_name}, - h5o::H5Oget_comment, + h5o::{H5O_type_t, H5Oget_comment}, }; use crate::internal_prelude::*; @@ -111,6 +124,148 @@ impl Location { pub fn attr_names(&self) -> Result> { Attribute::attr_names(self) } + + pub fn get_info(&self) -> Result { + H5O_get_info(self.id()) + } + + pub fn loc_type(&self) -> Result { + Ok(self.get_info()?.loc_type) + } + + pub fn get_info_by_name(&self, name: &str) -> Result { + let name = to_cstring(name)?; + H5O_get_info_by_name(self.id(), name.as_ptr()) + } + + pub fn open_by_token(&self, token: LocationToken) -> Result { + H5O_open_by_token(self.id(), token) + } +} + +#[cfg(hdf5_1_12_0)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct LocationToken(H5O_token_t); + +#[cfg(not(hdf5_1_12_0))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct LocationToken(haddr_t); + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum LocationType { + Group, + Dataset, + NamedDatatype, + #[cfg(hdf5_1_12_0)] + TypeMap, +} + +impl From for LocationType { + fn from(loc_type: H5O_type_t) -> Self { + // we're assuming here that if a C API call returns H5O_TYPE_UNKNOWN (-1), then + // an error has occured anyway and has been pushed on the error stack so we'll + // catch it, and the value of -1 will never reach this conversion function + match loc_type { + H5O_type_t::H5O_TYPE_DATASET => Self::Dataset, + H5O_type_t::H5O_TYPE_NAMED_DATATYPE => Self::NamedDatatype, + #[cfg(hdf5_1_12_0)] + H5O_type_t::H5O_TYPE_MAP => Self::TypeMap, + _ => Self::Group, // see the comment above + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +/// Information about a [`Location`] +pub struct LocationInfo { + pub fileno: u64, + pub token: LocationToken, + pub loc_type: LocationType, + pub refcount: usize, + /// Not available when requesting only basic info + atime: i64, + /// Not available when requesting only basic info + mtime: i64, + /// Not available when requesting only basic info + ctime: i64, + /// Not available when requesting only basic info + btime: i64, + /// Not available when requesting only basic info + num_attrs: usize, +} + +#[cfg(not(hdf5_1_12_0))] +impl From for LocationInfo { + fn from(info: H5O_info1_t) -> Self { + Self { + fileno: info.fileno as _, + token: LocationToken(info.addr), + loc_type: info.type_.into(), + refcount: info.rc as _, + atime: info.atime as _, + mtime: info.mtime as _, + ctime: info.ctime as _, + btime: info.btime as _, + num_attrs: info.num_attrs as _, + } + } +} + +#[cfg(hdf5_1_12_0)] +impl From for LocationInfo { + fn from(info: H5O_info2_t) -> Self { + Self { + fileno: info.fileno as _, + token: LocationToken(info.token), + loc_type: info.type_.into(), + refcount: info.rc as _, + atime: info.atime as _, + mtime: info.mtime as _, + ctime: info.ctime as _, + btime: info.btime as _, + num_attrs: info.num_attrs as _, + } + } +} + +#[allow(non_snake_case)] +fn H5O_get_info(loc_id: hid_t) -> Result { + let mut info_buf = MaybeUninit::uninit(); + let info_ptr = info_buf.as_mut_ptr(); + #[cfg(hdf5_1_12_0)] + h5call!(H5Oget_info3(loc_id, info_ptr, H5O_INFO_BASIC))?; + #[cfg(all(hdf5_1_10_3, not(hdf5_1_12_0)))] + h5call!(H5Oget_info2(loc_id, info_ptr, H5O_INFO_BASIC))?; + #[cfg(not(hdf5_1_10_3))] + h5call!(H5Oget_info1(loc_id, info_ptr))?; + let info = unsafe { info_buf.assume_init() }; + Ok(info.into()) +} + +#[allow(non_snake_case)] +fn H5O_get_info_by_name(loc_id: hid_t, name: *const c_char) -> Result { + let mut info_buf = MaybeUninit::uninit(); + let info_ptr = info_buf.as_mut_ptr(); + #[cfg(hdf5_1_12_0)] + h5call!(H5Oget_info_by_name3(loc_id, name, info_ptr, H5O_INFO_BASIC, H5P_DEFAULT))?; + #[cfg(all(hdf5_1_10_3, not(hdf5_1_12_0)))] + h5call!(H5Oget_info_by_name2(loc_id, name, info_ptr, H5O_INFO_BASIC, H5P_DEFAULT))?; + #[cfg(not(hdf5_1_10_3))] + h5call!(H5Oget_info_by_name1(loc_id, name, info_ptr, H5P_DEFAULT))?; + let info = unsafe { info_buf.assume_init() }; + Ok(info.into()) +} + +#[allow(non_snake_case)] +fn H5O_open_by_token(loc_id: hid_t, token: LocationToken) -> Result { + #[cfg(not(hdf5_1_12_0))] + { + Location::from_id(h5call!(H5Oopen_by_addr(loc_id, token.0))?) + } + #[cfg(hdf5_1_12_0)] + { + Location::from_id(h5call!(H5Oopen_by_token(loc_id, token.0))?) + } } #[cfg(test)] @@ -149,4 +304,40 @@ pub mod tests { assert!(file.comment().is_none()); }) } + + #[test] + pub fn test_location_info() { + with_tmp_file(|file| { + let token; + { + let group = file.create_group("group").unwrap(); + let info = group.get_info().unwrap(); + assert_eq!(info.refcount, 1); + assert_eq!(info.loc_type, LocationType::Group); + token = info.token; + } + let group = file.open_by_token(token).unwrap(); + assert_eq!(group.name(), "/group"); + let token; + { + let var = file.new_dataset::().create("var").unwrap(); + var.new_attr::().create("attr").unwrap(); + let info = var.get_info().unwrap(); + assert_eq!(info.refcount, 1); + assert_eq!(info.loc_type, LocationType::Dataset); + token = info.token; + } + let var = file.open_by_token(token).unwrap(); + assert_eq!(var.name(), "/var"); + + let info = file.get_info_by_name("group").unwrap(); + let group = file.open_by_token(info.token).unwrap(); + assert_eq!(group.name(), "/group"); + let info = file.get_info_by_name("var").unwrap(); + let var = file.open_by_token(info.token).unwrap(); + assert_eq!(var.name(), "/var"); + + assert!(file.get_info_by_name("gibberish").is_err()); + }) + } } diff --git a/src/lib.rs b/src/lib.rs index 24bec1640..41616e69f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,7 @@ #![cfg_attr(feature = "cargo-clippy", allow(clippy::unnecessary_unwrap))] #![cfg_attr(feature = "cargo-clippy", allow(clippy::unnecessary_wraps))] #![cfg_attr(feature = "cargo-clippy", allow(clippy::upper_case_acronyms))] +#![cfg_attr(feature = "cargo-clippy", allow(clippy::missing_panics_doc))] #![cfg_attr(all(feature = "cargo-clippy", test), allow(clippy::cyclomatic_complexity))] #![cfg_attr(not(test), allow(dead_code))] @@ -37,7 +38,8 @@ mod export { Attribute, AttributeBuilder, AttributeBuilderData, AttributeBuilderEmpty, AttributeBuilderEmptyShape, Container, Conversion, Dataset, DatasetBuilder, DatasetBuilderData, DatasetBuilderEmpty, DatasetBuilderEmptyShape, Dataspace, Datatype, - File, FileBuilder, Group, Location, Object, PropertyList, Reader, Writer, + File, FileBuilder, Group, LinkInfo, LinkType, Location, LocationInfo, LocationToken, + LocationType, Object, PropertyList, Reader, Writer, }, };