|
24 | 24 | use std::ffi::CString; |
25 | 25 | use std::ops::Deref; |
26 | 26 |
|
| 27 | +use crate::cpl::CslStringList; |
27 | 28 | use crate::errors::{GdalError, Result}; |
28 | 29 | use crate::gdal_api::{call_gdal_api, GdalApi}; |
29 | 30 |
|
@@ -147,6 +148,36 @@ pub fn unlink_mem_file(api: &'static GdalApi, file_name: &str) -> Result<()> { |
147 | 148 | Ok(()) |
148 | 149 | } |
149 | 150 |
|
| 151 | +/// Return the directory separator for the specified path. |
| 152 | +/// |
| 153 | +/// Default is forward slash. The only exception currently is the Windows |
| 154 | +/// file system which returns backslash, unless the specified path is of the |
| 155 | +/// form "{drive_letter}:/{rest_of_the_path}". |
| 156 | +/// |
| 157 | +/// This function replicates the `VSIGetDirectorySeparator` function of GDAL 3.9+. |
| 158 | +/// We do not call the GDAL function directly since we want to be compatible with older |
| 159 | +/// GDAL versions. |
| 160 | +pub fn directory_separator_for_path(path: &str) -> &'static str { |
| 161 | + if path.starts_with("http://") || path.starts_with("https://") { |
| 162 | + "/" |
| 163 | + } else { |
| 164 | + #[cfg(windows)] |
| 165 | + { |
| 166 | + // Return forward slash for paths of the form |
| 167 | + // "{drive_letter}:/{rest_of_the_path}", and backslash otherwise. |
| 168 | + if path.len() >= 3 && path.as_bytes()[1] == b':' && path.as_bytes()[2] == b'/' { |
| 169 | + "/" |
| 170 | + } else { |
| 171 | + "\\" |
| 172 | + } |
| 173 | + } |
| 174 | + #[cfg(not(windows))] |
| 175 | + { |
| 176 | + "/" |
| 177 | + } |
| 178 | + } |
| 179 | +} |
| 180 | + |
150 | 181 | #[cfg(test)] |
151 | 182 | pub(crate) fn with_memfile<T>( |
152 | 183 | api: &'static GdalApi, |
@@ -208,6 +239,80 @@ pub fn get_vsi_mem_file_bytes_owned(api: &'static GdalApi, file_name: &str) -> R |
208 | 239 | Ok(buffer.as_ref().to_vec()) |
209 | 240 | } |
210 | 241 |
|
| 242 | +pub struct VsiDirEntry { |
| 243 | + pub name: String, |
| 244 | + pub mode: Option<i32>, |
| 245 | + pub size: Option<crate::gdal_dyn_bindgen::vsi_l_offset>, |
| 246 | + pub mtime: Option<crate::gdal_dyn_bindgen::GIntBig>, |
| 247 | +} |
| 248 | + |
| 249 | +pub struct VsiDir { |
| 250 | + api: &'static crate::gdal_api::GdalApi, |
| 251 | + handle: *mut crate::gdal_dyn_bindgen::VSIDIR, |
| 252 | +} |
| 253 | + |
| 254 | +impl VsiDir { |
| 255 | + pub fn next_entry(&mut self) -> Option<VsiDirEntry> { |
| 256 | + let entry = unsafe { (self.api.inner.VSIGetNextDirEntry?)(self.handle) }; |
| 257 | + if entry.is_null() { |
| 258 | + return None; |
| 259 | + } |
| 260 | + let entry = unsafe { &*entry }; |
| 261 | + |
| 262 | + let name = if entry.pszName.is_null() { |
| 263 | + String::new() |
| 264 | + } else { |
| 265 | + unsafe { std::ffi::CStr::from_ptr(entry.pszName) } |
| 266 | + .to_string_lossy() |
| 267 | + .into_owned() |
| 268 | + }; |
| 269 | + |
| 270 | + Some(VsiDirEntry { |
| 271 | + name, |
| 272 | + mode: (entry.bModeKnown != 0).then_some(entry.nMode), |
| 273 | + size: (entry.bSizeKnown != 0).then_some(entry.nSize), |
| 274 | + mtime: (entry.bMTimeKnown != 0).then_some(entry.nMTime), |
| 275 | + }) |
| 276 | + } |
| 277 | +} |
| 278 | + |
| 279 | +impl Iterator for VsiDir { |
| 280 | + type Item = VsiDirEntry; |
| 281 | + |
| 282 | + fn next(&mut self) -> Option<Self::Item> { |
| 283 | + self.next_entry() |
| 284 | + } |
| 285 | +} |
| 286 | + |
| 287 | +impl Drop for VsiDir { |
| 288 | + fn drop(&mut self) { |
| 289 | + if !self.handle.is_null() { |
| 290 | + if let Some(close) = self.api.inner.VSICloseDir { |
| 291 | + unsafe { close(self.handle) }; |
| 292 | + } |
| 293 | + self.handle = std::ptr::null_mut(); |
| 294 | + } |
| 295 | + } |
| 296 | +} |
| 297 | + |
| 298 | +pub fn open_dir( |
| 299 | + api: &'static crate::gdal_api::GdalApi, |
| 300 | + path: &str, |
| 301 | + recurse_depth: i32, |
| 302 | + options: Option<&CslStringList>, |
| 303 | +) -> crate::errors::Result<VsiDir> { |
| 304 | + let c_path = std::ffi::CString::new(path)?; |
| 305 | + let options_ptr: *const *const std::os::raw::c_char = options |
| 306 | + .map(|opts| opts.as_ptr() as *const *const std::os::raw::c_char) |
| 307 | + .unwrap_or(std::ptr::null()); |
| 308 | + let handle = |
| 309 | + unsafe { call_gdal_api!(api, VSIOpenDir, c_path.as_ptr(), recurse_depth, options_ptr) }; |
| 310 | + if handle.is_null() { |
| 311 | + return Err(api.last_null_pointer_err("VSIOpenDir")); |
| 312 | + } |
| 313 | + Ok(VsiDir { api, handle }) |
| 314 | +} |
| 315 | + |
211 | 316 | #[cfg(all(test, feature = "gdal-sys"))] |
212 | 317 | mod tests { |
213 | 318 | use super::*; |
@@ -300,78 +405,24 @@ mod tests { |
300 | 405 | }) |
301 | 406 | .unwrap(); |
302 | 407 | } |
303 | | -} |
304 | | - |
305 | | -pub struct VsiDirEntry { |
306 | | - pub name: String, |
307 | | - pub mode: Option<i32>, |
308 | | - pub size: Option<crate::gdal_dyn_bindgen::vsi_l_offset>, |
309 | | - pub mtime: Option<crate::gdal_dyn_bindgen::GIntBig>, |
310 | | -} |
311 | | - |
312 | | -pub struct VsiDir { |
313 | | - api: &'static crate::gdal_api::GdalApi, |
314 | | - handle: *mut crate::gdal_dyn_bindgen::VSIDIR, |
315 | | -} |
316 | 408 |
|
317 | | -impl VsiDir { |
318 | | - pub fn next_entry(&mut self) -> Option<VsiDirEntry> { |
319 | | - let entry = unsafe { (self.api.inner.VSIGetNextDirEntry?)(self.handle) }; |
320 | | - if entry.is_null() { |
321 | | - return None; |
| 409 | + #[test] |
| 410 | + fn test_directory_separator_for_path() { |
| 411 | + #[cfg(windows)] |
| 412 | + { |
| 413 | + assert_eq!(directory_separator_for_path("/vsis3/bucket/prefix"), r"\"); |
| 414 | + assert_eq!(directory_separator_for_path("https://host/data.tif"), "/"); |
| 415 | + assert_eq!(directory_separator_for_path(r"C:\data\dir"), r"\"); |
| 416 | + assert_eq!(directory_separator_for_path(r"C:/data/dir"), "/"); |
| 417 | + assert_eq!(directory_separator_for_path("/tmp/data"), r"\"); |
322 | 418 | } |
323 | | - let entry = unsafe { &*entry }; |
324 | | - |
325 | | - let name = if entry.pszName.is_null() { |
326 | | - String::new() |
327 | | - } else { |
328 | | - unsafe { std::ffi::CStr::from_ptr(entry.pszName) } |
329 | | - .to_string_lossy() |
330 | | - .into_owned() |
331 | | - }; |
332 | | - |
333 | | - Some(VsiDirEntry { |
334 | | - name, |
335 | | - mode: (entry.bModeKnown != 0).then_some(entry.nMode), |
336 | | - size: (entry.bSizeKnown != 0).then_some(entry.nSize), |
337 | | - mtime: (entry.bMTimeKnown != 0).then_some(entry.nMTime), |
338 | | - }) |
339 | | - } |
340 | | -} |
341 | | - |
342 | | -impl Iterator for VsiDir { |
343 | | - type Item = VsiDirEntry; |
344 | | - |
345 | | - fn next(&mut self) -> Option<Self::Item> { |
346 | | - self.next_entry() |
347 | | - } |
348 | | -} |
349 | | - |
350 | | -impl Drop for VsiDir { |
351 | | - fn drop(&mut self) { |
352 | | - if !self.handle.is_null() { |
353 | | - if let Some(close) = self.api.inner.VSICloseDir { |
354 | | - unsafe { close(self.handle) }; |
355 | | - } |
356 | | - self.handle = std::ptr::null_mut(); |
| 419 | + #[cfg(not(windows))] |
| 420 | + { |
| 421 | + assert_eq!(directory_separator_for_path("/vsis3/bucket/prefix"), "/"); |
| 422 | + assert_eq!(directory_separator_for_path("https://host/data.tif"), "/"); |
| 423 | + assert_eq!(directory_separator_for_path(r"C:\data\dir"), "/"); |
| 424 | + assert_eq!(directory_separator_for_path(r"C:/data/dir"), "/"); |
| 425 | + assert_eq!(directory_separator_for_path("/tmp/data"), "/"); |
357 | 426 | } |
358 | 427 | } |
359 | 428 | } |
360 | | - |
361 | | -pub fn open_dir( |
362 | | - api: &'static crate::gdal_api::GdalApi, |
363 | | - path: &str, |
364 | | - recurse_depth: i32, |
365 | | - options: Option<&crate::cpl::CslStringList>, |
366 | | -) -> crate::errors::Result<VsiDir> { |
367 | | - let c_path = std::ffi::CString::new(path)?; |
368 | | - let options_ptr: *const *const std::os::raw::c_char = options |
369 | | - .map(|opts| opts.as_ptr() as *const *const std::os::raw::c_char) |
370 | | - .unwrap_or(std::ptr::null()); |
371 | | - let handle = |
372 | | - unsafe { (api.inner.VSIOpenDir.unwrap())(c_path.as_ptr(), recurse_depth, options_ptr) }; |
373 | | - if handle.is_null() { |
374 | | - return Err(api.last_null_pointer_err("VSIOpenDir")); |
375 | | - } |
376 | | - Ok(VsiDir { api, handle }) |
377 | | -} |
|
0 commit comments