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 self.lock()?.ensure_empty_dir_impl(path.as_ref())
110 }
111
112 pub(crate) fn remove_dir_contents_impl(
114 &self,
115 path: impl AsRef<Path>,
116 options: RemoveOptions,
117 ) -> VfResult<()> {
118 let path = path.as_ref();
119 if !self
120 .removal_metadata(path, "remove_dir_contents_impl")?
121 .is_dir()
122 {
123 return Err(
124 VfError::client(0, crate::ERR_NOTDIR).with_context("remove_dir_contents", path)
125 );
126 }
127 self.lock()?
128 .remove_dir_contents_path_with_options_impl(path, options)
129 .map_err(|error| error.with_context("remove_dir_contents", path))
130 }
131
132 pub(crate) fn vopen_dirs_impl<P: AsRef<Path>>(&self, paths: &[P]) -> VfResult<Vec<FsDir<F>>> {
133 if paths.is_empty() {
134 return Ok(Vec::new());
135 }
136 let mut backend = self.lock()?;
137 let mut output = Vec::with_capacity(paths.len());
138 for (index, path) in paths.iter().enumerate() {
139 let path = path.as_ref();
140 let dir = backend.open_dir_impl(path).map_err(|error| {
141 crate::application::vector_index(error, index).with_context("vopen_dirs", path)
142 })?;
143 if !matches!(dir, VfDir::Descriptor { .. }) {
144 let _ = backend.close_dir_impl(&dir);
145 return Err(VfError::unsupported(index).with_context("vopen_dirs", path));
146 }
147 output.push(FsDir {
148 inner: Arc::clone(&self.inner),
149 dir: Some(dir),
150 path: path.to_path_buf(),
151 });
152 }
153 Ok(output)
154 }
155
156 pub(crate) fn vremove_dir_contents_impl(
158 &self,
159 dirs: &[&FsDir<F>],
160 options: RemoveOptions,
161 ) -> VfResult<()> {
162 for (index, dir) in dirs.iter().enumerate() {
163 if !Arc::ptr_eq(&self.inner, &dir.inner) || dir.is_closed() {
164 return Err(VfError::client(index, crate::ERR_EBADF)
165 .with_context("vremove_dir_contents", &dir.path));
166 }
167 }
168 if dirs.is_empty() {
169 return Ok(());
170 }
171 let mut backend = self.lock()?;
172 let mut first_error = None;
173 for (index, dir) in dirs.iter().enumerate() {
174 if let Err(error) = backend.remove_dir_contents_handle_with_options_impl(
175 dir.dir.as_ref().expect("preflighted"),
176 options,
177 ) {
178 let error = crate::application::vector_index(error, index)
179 .with_context("vremove_dir_contents", &dir.path);
180 if error.is_transport() || !options.continue_on_error() {
181 return Err(error);
182 }
183 first_error.get_or_insert(error);
184 }
185 }
186 first_error.map_or(Ok(()), Err)
187 }
188}
189
190impl<F: VectorBackend> FsClient<F> {
191 #[doc(hidden)]
194 pub fn vremove_native<P: AsRef<Path>>(&self, paths: &[P], recursive: bool) -> VfResult<()> {
195 self.vremove_impl(paths, recursive, RemoveOptions::default())
196 }
197
198 #[doc(hidden)]
200 pub fn vremove_impl<P: AsRef<Path>>(
201 &self,
202 paths: &[P],
203 recursive: bool,
204 options: RemoveOptions,
205 ) -> VfResult<()> {
206 let paths: Vec<&Path> = paths.iter().map(AsRef::as_ref).collect();
207 self.lock()?
208 .remove_paths_with_options_impl(&paths, recursive, options)
209 .map_err(|error| {
210 error
211 .index()
212 .and_then(|index| paths.get(index))
213 .map_or(error.clone(), |path| {
214 error.with_context("vremove_native", path)
215 })
216 })
217 }
218}