Skip to main content

hdf5_pure_rust/hl/
mutable_file.rs

1use std::collections::HashMap;
2use std::fs;
3use std::io::BufReader;
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6
7use parking_lot::Mutex;
8
9use crate::error::Result;
10use crate::format::superblock::Superblock;
11use crate::hl::dataset::Dataset;
12use crate::hl::file::{FileInner, FileIntent};
13use crate::hl::group::Group;
14use crate::io::reader::HdfReader;
15
16#[path = "mutable_file/attr_mutation.rs"]
17mod attr_mutation;
18#[path = "mutable_file/chunk_btree_v1.rs"]
19mod chunk_btree_v1;
20#[path = "mutable_file/chunk_btree_v2.rs"]
21mod chunk_btree_v2;
22#[path = "mutable_file/chunk_extensible_array.rs"]
23mod chunk_extensible_array;
24#[path = "mutable_file/chunk_fixed_array.rs"]
25mod chunk_fixed_array;
26#[path = "mutable_file/group_mutation.rs"]
27mod group_mutation;
28#[path = "mutable_file/resize.rs"]
29mod resize;
30#[path = "mutable_file/support.rs"]
31mod support;
32#[path = "mutable_file/write_chunk.rs"]
33mod write_chunk;
34
35/// A mutable HDF5 file opened for read-write access.
36///
37/// Supports resizing chunked datasets and writing new chunks.
38pub struct MutableFile {
39    /// Read path (for parsing)
40    inner: Arc<Mutex<FileInner<BufReader<fs::File>>>>,
41    /// Write path (for modifying)
42    write_handle: fs::File,
43    superblock: Superblock,
44    path: PathBuf,
45}
46
47impl MutableFile {
48    /// Open an existing HDF5 file for read-write access.
49    pub fn open_rw<P: AsRef<Path>>(path: P) -> Result<Self> {
50        let path = path.as_ref().to_path_buf();
51
52        // Open for reading
53        let read_file = fs::File::open(&path)?;
54        let mut reader = HdfReader::new(BufReader::new(read_file));
55        let superblock = Superblock::read(&mut reader)?;
56
57        let inner = Arc::new(Mutex::new(FileInner {
58            reader,
59            superblock: superblock.clone(),
60            path: Some(path.clone()),
61            intent: FileIntent::ReadWrite,
62            access_plist: crate::hl::plist::file_access::FileAccess::default(),
63            dset_no_attrs_hint: false,
64            open_objects: HashMap::new(),
65            next_object_id: 1,
66        }));
67
68        // Open separately for writing
69        let write_handle = fs::OpenOptions::new().write(true).open(&path)?;
70
71        Ok(Self {
72            inner,
73            write_handle,
74            superblock,
75            path,
76        })
77    }
78
79    /// Get the root group (read-only access).
80    pub fn root_group(&self) -> Result<Group> {
81        Group::open(self.inner.clone(), "/", self.superblock.root_addr)
82    }
83
84    /// Visit member names in the root group.
85    pub fn visit_member_names<F>(&self, visitor: F) -> Result<()>
86    where
87        F: FnMut(&str) -> Result<()>,
88    {
89        self.root_group()?.visit_member_names(visitor)
90    }
91
92    /// Open a dataset by path.
93    pub fn dataset(&self, path: &str) -> Result<Dataset> {
94        let path_str = path.trim_start_matches('/');
95        if let Some(last_slash) = path_str.rfind('/') {
96            let group_path = &path_str[..last_slash];
97            let ds_name = &path_str[last_slash + 1..];
98            let root = self.root_group()?;
99            let mut current = root;
100            for part in group_path.split('/').filter(|s| !s.is_empty()) {
101                current = current.open_group(part)?;
102            }
103            current.open_dataset(ds_name)
104        } else {
105            self.root_group()?.open_dataset(path_str)
106        }
107    }
108
109    /// Open a group by path.
110    pub fn group(&self, path: &str) -> Result<Group> {
111        let path_str = path.trim_matches('/');
112        if path_str.is_empty() {
113            return self.root_group();
114        }
115        let mut current = self.root_group()?;
116        for part in path_str.split('/').filter(|s| !s.is_empty()) {
117            current = current.open_group(part)?;
118        }
119        Ok(current)
120    }
121}