use std::fmt::{self, Debug};
use std::mem::MaybeUninit;
use std::ops::Deref;
use std::ptr;
use hdf5_sys::h5o::H5Ocopy;
#[allow(deprecated)]
use hdf5_sys::h5o::H5Oset_comment;
#[cfg(feature = "1.12.0")]
use hdf5_sys::h5o::{
H5O_info2_t, H5O_token_t, H5Oget_info3, H5Oget_info_by_name3, H5Oopen_by_token,
};
#[cfg(not(feature = "1.10.3"))]
use hdf5_sys::h5o::{H5Oget_info1, H5Oget_info_by_name1};
#[cfg(all(feature = "1.10.3", not(feature = "1.12.0")))]
use hdf5_sys::h5o::{H5Oget_info2, H5Oget_info_by_name2};
#[cfg(feature = "1.10.3")]
use hdf5_sys::h5o::{H5O_INFO_BASIC, H5O_INFO_NUM_ATTRS, H5O_INFO_TIME};
#[cfg(not(feature = "1.12.0"))]
use hdf5_sys::{h5::haddr_t, h5o::H5O_info1_t, h5o::H5Oopen_by_addr};
use hdf5_sys::{
h5a::{H5Adelete, H5Aopen},
h5f::H5Fget_name,
h5i::{H5Iget_file_id, H5Iget_name},
h5o::{H5O_type_t, H5Oget_comment},
};
use crate::internal_prelude::*;
use super::attribute::AttributeBuilderEmpty;
#[repr(transparent)]
#[derive(Clone)]
pub struct Location(Handle);
impl ObjectClass for Location {
const NAME: &'static str = "location";
const VALID_TYPES: &'static [H5I_type_t] =
&[H5I_FILE, H5I_GROUP, H5I_DATATYPE, H5I_DATASET, H5I_ATTR];
fn from_handle(handle: Handle) -> Self {
Self(handle)
}
fn handle(&self) -> &Handle {
&self.0
}
fn short_repr(&self) -> Option<String> {
Some(format!("\"{}\"", self.name()))
}
}
impl Debug for Location {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.debug_fmt(f)
}
}
impl Deref for Location {
type Target = Object;
fn deref(&self) -> &Object {
unsafe { self.transmute() }
}
}
impl Location {
pub fn name(&self) -> String {
h5lock!(get_h5_str(|m, s| H5Iget_name(self.id(), m, s)).unwrap_or_else(|_| String::new()))
}
pub fn filename(&self) -> String {
h5lock!(get_h5_str(|m, s| H5Fget_name(self.id(), m, s)).unwrap_or_else(|_| String::new()))
}
pub fn file(&self) -> Result<File> {
File::from_id(h5try!(H5Iget_file_id(self.id())))
}
pub fn comment(&self) -> Option<String> {
let comment = h5lock!(get_h5_str(|m, s| H5Oget_comment(self.id(), m, s)).ok());
comment.and_then(|c| if c.is_empty() { None } else { Some(c) })
}
#[deprecated(note = "attributes are preferred to comments")]
pub fn set_comment(&self, comment: &str) -> Result<()> {
let comment = to_cstring(comment)?;
#[allow(deprecated)]
h5call!(H5Oset_comment(self.id(), comment.as_ptr())).and(Ok(()))
}
#[deprecated(note = "attributes are preferred to comments")]
pub fn clear_comment(&self) -> Result<()> {
#[allow(deprecated)]
h5call!(H5Oset_comment(self.id(), ptr::null_mut())).and(Ok(()))
}
pub fn new_attr<T: H5Type>(&self) -> AttributeBuilderEmpty {
AttributeBuilder::new(self).empty::<T>()
}
pub fn new_attr_builder(&self) -> AttributeBuilder {
AttributeBuilder::new(self)
}
pub fn attr(&self, name: &str) -> Result<Attribute> {
let name = to_cstring(name)?;
Attribute::from_id(h5try!(H5Aopen(self.id(), name.as_ptr(), H5P_DEFAULT)))
}
pub fn attr_names(&self) -> Result<Vec<String>> {
Attribute::attr_names(self)
}
pub fn delete_attr(&self, name: &str) -> Result<()> {
let name = to_cstring(name)?;
h5call!(H5Adelete(self.id(), name.as_ptr()))?;
Ok(())
}
pub fn loc_info(&self) -> Result<LocationInfo> {
H5O_get_info(self.id(), true)
}
pub fn loc_type(&self) -> Result<LocationType> {
Ok(H5O_get_info(self.id(), false)?.loc_type)
}
pub fn loc_info_by_name(&self, name: &str) -> Result<LocationInfo> {
let name = to_cstring(name)?;
H5O_get_info_by_name(self.id(), name.as_ptr(), true)
}
pub fn loc_type_by_name(&self, name: &str) -> Result<LocationType> {
let name = to_cstring(name)?;
Ok(H5O_get_info_by_name(self.id(), name.as_ptr(), false)?.loc_type)
}
pub fn open_by_token(&self, token: LocationToken) -> Result<Self> {
H5O_open_by_token(self.id(), token)
}
pub fn reference<R: ObjectReference>(&self, name: &str) -> Result<R> {
R::create(self, name)
}
pub fn dereference<R: ObjectReference>(&self, reference: &R) -> Result<ReferencedObject> {
reference.dereference(self)
}
pub fn copy_to(&self, dst_loc: &Location, dst_name: &str) -> Result<()> {
self.copy_to_with_props(dst_loc, dst_name, None, None)
}
pub fn copy_to_with_props(
&self, dst_loc: &Location, dst_name: &str, ocpypl: Option<&PropertyList>,
lcpl: Option<&PropertyList>,
) -> Result<()> {
if let Some(pl) = ocpypl {
if !pl.is_class(PropertyListClass::ObjectCopy) {
fail!("Property list must be of class ObjectCopy");
}
}
if let Some(pl) = lcpl {
if !pl.is_class(PropertyListClass::LinkCreate) {
fail!("Property list must be of class LinkCreate");
}
}
let dst_name = to_cstring(dst_name)?;
let ocpypl_id = ocpypl.map_or(H5P_DEFAULT, |p| p.id());
let lcpl_id = lcpl.map_or(H5P_DEFAULT, |p| p.id());
h5call!(H5Ocopy(
self.id(), b".\0".as_ptr() as *const c_char, dst_loc.id(), dst_name.as_ptr(), ocpypl_id, lcpl_id ))?;
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LocationToken(
#[cfg(not(feature = "1.12.0"))] haddr_t,
#[cfg(feature = "1.12.0")] H5O_token_t,
);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LocationType {
Group,
Dataset,
NamedDatatype,
#[cfg(feature = "1.12.0")]
#[cfg_attr(docsrs, doc(cfg(feature = "1.12.0")))]
TypeMap,
}
impl From<H5O_type_t> for LocationType {
fn from(loc_type: H5O_type_t) -> Self {
match loc_type {
H5O_type_t::H5O_TYPE_DATASET => Self::Dataset,
H5O_type_t::H5O_TYPE_NAMED_DATATYPE => Self::NamedDatatype,
#[cfg(feature = "1.12.0")]
H5O_type_t::H5O_TYPE_MAP => Self::TypeMap,
_ => Self::Group, }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LocationInfo {
pub fileno: u64,
pub token: LocationToken,
pub loc_type: LocationType,
pub num_links: usize,
pub atime: i64,
pub mtime: i64,
pub ctime: i64,
pub btime: i64,
pub num_attrs: usize,
}
#[cfg(not(feature = "1.12.0"))]
impl From<H5O_info1_t> for LocationInfo {
fn from(info: H5O_info1_t) -> Self {
Self {
fileno: info.fileno as _,
token: LocationToken(info.addr),
loc_type: info.type_.into(),
num_links: 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(feature = "1.12.0")]
impl From<H5O_info2_t> for LocationInfo {
fn from(info: H5O_info2_t) -> Self {
Self {
fileno: info.fileno as _,
token: LocationToken(info.token),
loc_type: info.type_.into(),
num_links: 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(feature = "1.10.3")]
fn info_fields(full: bool) -> c_uint {
if full {
H5O_INFO_BASIC | H5O_INFO_NUM_ATTRS | H5O_INFO_TIME
} else {
H5O_INFO_BASIC
}
}
#[allow(non_snake_case, unused_variables)]
fn H5O_get_info(loc_id: hid_t, full: bool) -> Result<LocationInfo> {
let mut info_buf = MaybeUninit::uninit();
let info_ptr = info_buf.as_mut_ptr();
#[cfg(feature = "1.12.0")]
h5call!(H5Oget_info3(loc_id, info_ptr, info_fields(full)))?;
#[cfg(all(feature = "1.10.3", not(feature = "1.12.0")))]
h5call!(H5Oget_info2(loc_id, info_ptr, info_fields(full)))?;
#[cfg(not(feature = "1.10.3"))]
h5call!(H5Oget_info1(loc_id, info_ptr))?;
let info = unsafe { info_buf.assume_init() };
Ok(info.into())
}
#[allow(non_snake_case, unused_variables)]
fn H5O_get_info_by_name(loc_id: hid_t, name: *const c_char, full: bool) -> Result<LocationInfo> {
let mut info_buf = MaybeUninit::uninit();
let info_ptr = info_buf.as_mut_ptr();
#[cfg(feature = "1.12.0")]
h5call!(H5Oget_info_by_name3(loc_id, name, info_ptr, info_fields(full), H5P_DEFAULT))?;
#[cfg(all(feature = "1.10.3", not(feature = "1.12.0")))]
h5call!(H5Oget_info_by_name2(loc_id, name, info_ptr, info_fields(full), H5P_DEFAULT))?;
#[cfg(not(feature = "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<Location> {
#[cfg(not(feature = "1.12.0"))]
{
Location::from_id(h5call!(H5Oopen_by_addr(loc_id, token.0))?)
}
#[cfg(feature = "1.12.0")]
{
Location::from_id(h5call!(H5Oopen_by_token(loc_id, token.0))?)
}
}
#[cfg(test)]
pub mod tests {
use crate::{hl::plist::object_copy::ObjectCopy, internal_prelude::*, plist::LinkCreate};
#[test]
pub fn test_filename() {
with_tmp_path(|path| {
assert_eq!(File::create(&path).unwrap().filename(), path.to_str().unwrap());
})
}
#[test]
pub fn test_name() {
with_tmp_file(|file| {
assert_eq!(file.name(), "/");
})
}
#[test]
pub fn test_file() {
with_tmp_file(|file| {
assert_eq!(file.file().unwrap().id(), file.id());
})
}
#[test]
pub fn test_comment() {
#[allow(deprecated)]
with_tmp_file(|file| {
assert!(file.comment().is_none());
assert!(file.set_comment("foo").is_ok());
assert_eq!(file.comment().unwrap(), "foo");
assert!(file.clear_comment().is_ok());
assert!(file.comment().is_none());
})
}
#[test]
pub fn test_location_info() {
let new_file = |path| {
cfg_if::cfg_if! {
if #[cfg(feature = "1.10.2")] {
File::with_options().with_fapl(|p| p.libver_v110()).create(path)
} else {
File::create(path)
}
}
};
with_tmp_path(|path| {
let file = new_file(path).unwrap();
let token = {
let group = file.create_group("group").unwrap();
assert_eq!(file.loc_type_by_name("group").unwrap(), LocationType::Group);
let info = group.loc_info().unwrap();
assert_eq!(info.num_links, 1);
assert_eq!(info.loc_type, LocationType::Group);
cfg_if::cfg_if! {
if #[cfg(feature = "1.10.2")] {
assert!(info.btime > 0);
} else {
assert_eq!(info.btime, 0);
}
}
assert_eq!(info.btime == 0, info.mtime == 0);
assert_eq!(info.btime == 0, info.ctime == 0);
assert_eq!(info.btime == 0, info.atime == 0);
assert_eq!(info.num_attrs, 0);
info.token
};
let group = file.open_by_token(token).unwrap().as_group().unwrap();
assert_eq!(group.name(), "/group");
let token = {
let var = group
.new_dataset_builder()
.obj_track_times(true)
.empty::<i8>()
.create("var")
.unwrap();
var.new_attr::<i16>().create("attr1").unwrap();
var.new_attr::<i32>().create("attr2").unwrap();
group.link_hard("var", "hard1").unwrap();
group.link_hard("var", "hard2").unwrap();
group.link_hard("var", "hard3").unwrap();
group.link_hard("var", "hard4").unwrap();
group.link_hard("var", "hard5").unwrap();
group.link_soft("var", "soft1").unwrap();
group.link_soft("var", "soft2").unwrap();
group.link_soft("var", "soft3").unwrap();
assert_eq!(file.loc_type_by_name("/group/var").unwrap(), LocationType::Dataset);
let info = var.loc_info().unwrap();
assert_eq!(info.num_links, 6); assert_eq!(info.loc_type, LocationType::Dataset);
assert!(info.ctime > 0);
cfg_if::cfg_if! {
if #[cfg(feature = "1.10.2")] {
assert!(info.btime > 0);
} else {
assert_eq!(info.btime, 0);
}
}
assert_eq!(info.btime == 0, info.mtime == 0);
assert_eq!(info.btime == 0, info.atime == 0);
assert_eq!(info.num_attrs, 2);
info.token
};
let var = file.open_by_token(token).unwrap();
assert!(var.name().starts_with("/group/hard"));
let info = file.loc_info_by_name("group").unwrap();
let group = file.open_by_token(info.token).unwrap();
assert_eq!(group.name(), "/group");
let info = file.loc_info_by_name("/group/var").unwrap();
let var = file.open_by_token(info.token).unwrap();
assert!(var.name().starts_with("/group/hard"));
assert!(file.loc_info_by_name("gibberish").is_err());
})
}
#[test]
pub fn test_copy_dataset_between_files() {
with_tmp_path(|src_path| {
with_tmp_path(|dst_path| {
let src_file = File::create(&src_path).unwrap();
let src_group = src_file.create_group("src_group").unwrap();
let src_dataset =
src_group.new_dataset::<i32>().shape([5]).create("src_group").unwrap();
src_dataset.write(&[1, 2, 3, 4, 5]).unwrap();
let src_attr = src_dataset.new_attr::<f64>().create("src_attr").unwrap();
src_attr.write_scalar(&42.0).unwrap();
let dst_file = File::create(&dst_path).unwrap();
let dst_group = dst_file.create_group("dst_group").unwrap();
src_dataset.copy_to(&dst_group, "copied_dataset").unwrap();
let copied_dataset = dst_group.dataset("copied_dataset").unwrap();
let read_data: Vec<i32> = copied_dataset.read_1d().unwrap().to_vec();
assert_eq!(read_data, &[1, 2, 3, 4, 5]);
let copied_attr = copied_dataset.attr("src_attr").unwrap();
let attr_value: f64 = copied_attr.read_scalar().unwrap();
assert_eq!(attr_value, 42.0);
})
})
}
#[test]
pub fn test_copy_group_with_nested_content() {
with_tmp_path(|src_path| {
with_tmp_path(|dst_path| {
let src_file = File::create(&src_path).unwrap();
let group1 = src_file.create_group("group1").unwrap();
let subgroup = group1.create_group("subgroup").unwrap();
let ds1 = group1.new_dataset::<i32>().shape([3]).create("dataset1").unwrap();
ds1.write(&[10, 20, 30]).unwrap();
let ds2 = subgroup.new_dataset::<f64>().shape([2]).create("dataset2").unwrap();
ds2.write(&[1.5, 2.5]).unwrap();
let dst_file = File::create(&dst_path).unwrap();
group1.copy_to(&dst_file, "copied_group1").unwrap();
let copied_group = dst_file.group("copied_group1").unwrap();
let copied_ds1 = copied_group.dataset("dataset1").unwrap();
let data1: Vec<i32> = copied_ds1.read_1d().unwrap().to_vec();
assert_eq!(data1, vec![10, 20, 30]);
let copied_subgroup = copied_group.group("subgroup").unwrap();
let copied_ds2 = copied_subgroup.dataset("dataset2").unwrap();
let data2: Vec<f64> = copied_ds2.read_1d().unwrap().to_vec();
assert_eq!(data2, vec![1.5, 2.5]);
})
})
}
#[test]
pub fn test_copy_without_attributes() {
with_tmp_path(|src_path| {
with_tmp_path(|dst_path| {
let src_file = File::create(&src_path).unwrap();
let dataset = src_file.new_dataset::<i32>().shape([3]).create("data").unwrap();
dataset.write(&[10, 20, 30]).unwrap();
let src_attr = dataset.new_attr::<i32>().create("foo_attr").unwrap();
src_attr.write_scalar(&100).unwrap();
let dst_file = File::create(&dst_path).unwrap();
let ocpypl = ObjectCopy::build().copy_without_attr(true).finish().unwrap();
dataset
.copy_to_with_props(&dst_file, "copied_no_attrs", Some(&ocpypl), None)
.unwrap();
src_file.close().unwrap();
let copied = dst_file.dataset("copied_no_attrs").unwrap();
let copied_data: Vec<i32> = copied.read_1d().unwrap().to_vec();
assert_eq!(copied_data, vec![10, 20, 30]);
let attr_names = copied.attr_names().unwrap();
assert!(attr_names.is_empty(), "Expected no attributes, but found: {attr_names:?}",);
})
})
}
#[test]
pub fn test_copy_with_link_create_intermediate_groups() {
with_tmp_path(|src_path| {
with_tmp_path(|dst_path| {
let src_file = File::create(&src_path).unwrap();
let dataset = src_file.new_dataset::<i32>().shape([3]).create("data").unwrap();
dataset.write(&[100, 200, 300]).unwrap();
let dst_file = File::create(&dst_path).unwrap();
assert!(dataset.copy_to(&dst_file, "level1/level2/level3/copied_data").is_err());
dataset
.copy_to_with_props(
&dst_file,
"level1/level2/level3/copied_data",
None,
Some(
&LinkCreate::build().create_intermediate_group(true).finish().unwrap(),
),
)
.unwrap();
assert!(dst_file.group("level1").is_ok());
assert!(dst_file.group("level1/level2").is_ok());
assert!(dst_file.group("level1/level2/level3").is_ok());
let copied = dst_file.dataset("level1/level2/level3/copied_data").unwrap();
let data: Vec<i32> = copied.read_1d().unwrap().to_vec();
assert_eq!(data, vec![100, 200, 300]);
})
})
}
}