vfsi_sync/client/
directory.rs1use super::*;
4
5impl<F: VectorBackend> FsClient<F> {
6 fn removal_metadata(&self, path: &Path, operation: &'static str) -> VfResult<Attrs> {
7 let mut filesystem = self.lock()?;
8 let follow = !filesystem.capabilities().contains(Capabilities::LSTAT);
9 filesystem
10 .metadata_path_impl(path, follow)
11 .map_err(|error| error.with_context(operation, path))
12 }
13}
14
15impl<F: VectorBackend> FsClient<F> {
16 #[doc(hidden)]
22 pub fn directory_page_batch_size(&self) -> VfResult<usize> {
23 Ok(self.lock()?.directory_page_batch_size().clamp(1, 32))
24 }
25
26 #[doc(hidden)]
28 pub fn read_dir_pages_with_fields(
29 &self,
30 paths: &[&Path],
31 fields: AttrMask,
32 cursors: Vec<Option<crate::DirPageCursor>>,
33 page_size: usize,
34 max_entries: usize,
35 follow_symlinks: bool,
36 ) -> VfResult<Vec<crate::DirectoryPage>> {
37 let pages = self.lock()?.vlistdir_pages_impl(
38 paths,
39 fields | AttrMask::MODE | AttrMask::SIZE,
40 cursors,
41 page_size,
42 max_entries,
43 follow_symlinks,
44 )?;
45 if pages.len() != paths.len() {
46 return Err(VfError::transport(
47 None,
48 "directory pages returned an invalid result count",
49 ));
50 }
51 pages
52 .into_iter()
53 .zip(paths)
54 .enumerate()
55 .map(|(index, ((attrs, next, children), path))| {
56 if children.len() != attrs.len()
57 || attrs.len() > page_size
58 || (attrs.is_empty() && next.is_some())
59 {
60 return Err(VfError::transport(
61 Some(index),
62 "invalid directory page progress",
63 ));
64 }
65 let mut seeds = Vec::new();
66 let entries = attrs
67 .into_iter()
68 .zip(children)
69 .map(|(attrs, child)| {
70 let entry_path = attrs
71 .file
72 .path()
73 .ok_or_else(|| {
74 VfError::transport(Some(index), "directory entry has no path")
75 })?
76 .to_path_buf();
77 if entry_path.parent() != Some(*path) {
78 return Err(VfError::transport(
79 Some(index),
80 "directory entry is outside its parent",
81 ));
82 }
83 if let Some(child) = child {
84 seeds.push((entry_path.clone(), child));
85 }
86 Ok(DirEntry::new(
87 entry_path,
88 vfsi_core::metadata_from_attrs(attrs),
89 ))
90 })
91 .collect::<VfResult<Vec<_>>>()?;
92 Ok((
93 DirectoryListing {
94 path: path.to_path_buf(),
95 entries,
96 },
97 next,
98 seeds,
99 ))
100 })
101 .collect()
102 }
103}
104
105impl<F: VectorBackend> FsClient<F> {
106 pub fn ensure_empty_dir(&self, path: impl AsRef<Path>) -> VfResult<()> {
109 let path = path.as_ref();
110 self.lock()?.ensure_empty_dir_impl(path)
111 }
112
113 pub(crate) fn remove_dir_contents_impl(
115 &self,
116 path: impl AsRef<Path>,
117 options: RemoveOptions,
118 ) -> VfResult<()> {
119 let path = path.as_ref();
120 if !self
121 .removal_metadata(path, "remove_dir_contents_impl")?
122 .is_dir()
123 {
124 return Err(
125 VfError::client(0, crate::ERR_NOTDIR).with_context("remove_dir_contents", path)
126 );
127 }
128 self.lock()?
129 .remove_dir_contents_path_with_options_impl(path, options)
130 .map_err(|error| error.with_context("remove_dir_contents", path))
131 }
132
133 pub(crate) fn vopen_dirs_impl<P: AsRef<Path>>(&self, paths: &[P]) -> VfResult<Vec<FsDir<F>>> {
134 if paths.is_empty() {
135 return Ok(Vec::new());
136 }
137 let mut output: Vec<_> = paths
140 .iter()
141 .map(|path| FsDir {
142 inner: Arc::clone(&self.inner),
143 dir: None,
144 path: path.as_ref().to_path_buf(),
145 })
146 .collect();
147 let mut backend = self.lock()?;
148 for (index, handle) in output.iter_mut().enumerate() {
149 let dir = backend.open_dir_impl(&handle.path).map_err(|error| {
150 crate::application::vector_index(error, index)
151 .with_context("vopen_dirs", &handle.path)
152 })?;
153 if !matches!(dir, VfDir::Descriptor { .. }) {
154 let _ = backend.close_dir_impl(&dir);
155 return Err(VfError::unsupported(index).with_context("vopen_dirs", &handle.path));
156 }
157 handle.dir = Some(dir);
158 }
159 Ok(output)
160 }
161
162 pub(crate) fn vremove_dir_contents_impl(
164 &self,
165 dirs: &[&FsDir<F>],
166 options: RemoveOptions,
167 ) -> VfResult<()> {
168 for (index, dir) in dirs.iter().enumerate() {
169 if !Arc::ptr_eq(&self.inner, &dir.inner) || dir.is_closed() {
170 return Err(VfError::client(index, crate::ERR_EBADF)
171 .with_context("vremove_dir_contents", &dir.path));
172 }
173 }
174 if dirs.is_empty() {
175 return Ok(());
176 }
177 let mut backend = self.lock()?;
178 let mut first_error = None;
179 for (index, dir) in dirs.iter().enumerate() {
180 if let Err(error) = backend.remove_dir_contents_handle_with_options_impl(
181 dir.dir.as_ref().expect("preflighted"),
182 options,
183 ) {
184 let error = crate::application::vector_index(error, index)
185 .with_context("vremove_dir_contents", &dir.path);
186 if error.is_transport() || !options.continue_on_error() {
187 return Err(error);
188 }
189 first_error.get_or_insert(error);
190 }
191 }
192 first_error.map_or(Ok(()), Err)
193 }
194}
195
196impl<F: VectorBackend> FsClient<F> {
197 #[doc(hidden)]
200 pub fn vremove_native<P: AsRef<Path>>(&self, paths: &[P], recursive: bool) -> VfResult<()> {
201 self.vremove_impl(paths, recursive, RemoveOptions::default())
202 }
203
204 #[doc(hidden)]
206 pub fn vremove_impl<P: AsRef<Path>>(
207 &self,
208 paths: &[P],
209 recursive: bool,
210 options: RemoveOptions,
211 ) -> VfResult<()> {
212 let paths: Vec<&Path> = paths.iter().map(AsRef::as_ref).collect();
213 self.lock()?
214 .remove_paths_with_options_impl(&paths, recursive, options)
215 .map_err(|error| {
216 with_indexed_context(error, "vremove_native", |index| paths.get(index).copied())
217 })
218 }
219}