diff --git a/src/provider.rs b/src/provider.rs index 63527ac..5db3611 100644 --- a/src/provider.rs +++ b/src/provider.rs @@ -1,8 +1,9 @@ use crate::position::RegionPosition; use crate::region::Region; -use std::fs::{File, OpenOptions}; +use std::fs::{File, OpenOptions, read_dir}; use std::path::Path; use std::{fs, io}; +use std::str::FromStr; pub trait RegionProvider { fn get_region(&self, region_pos: RegionPosition) -> Result, io::Error>; @@ -19,6 +20,17 @@ impl<'a> FolderRegionProvider<'a> { FolderRegionProvider { folder_path } } + + // leave implementing this to the specific provider, + // makes function declaration bearable for now + pub fn iter_positions(&self) -> Result, io::Error> { + let positions: Vec<_> = read_dir(self.folder_path)? + .filter_map(|dir| dir.ok()) + .filter_map(|dir| region_pos_from_filename(&dir.path()).ok()) + .collect(); + + Ok(positions.into_iter()) + } } impl<'a> RegionProvider for FolderRegionProvider<'a> { @@ -27,7 +39,7 @@ impl<'a> RegionProvider for FolderRegionProvider<'a> { fs::create_dir(self.folder_path)?; } - let region_name = format!("r.{}.{}.mca", position.x, position.z); + let region_name = region_position_filename(position); let region_path = self.folder_path.join(region_name); let file = OpenOptions::new() @@ -39,3 +51,56 @@ impl<'a> RegionProvider for FolderRegionProvider<'a> { Region::load(position, file) } } + +fn region_pos_from_filename(path: &Path) -> Result { + // we can use lossy because of the bound check later + let filename = path.file_name().unwrap_or_default().to_string_lossy(); + let parts: Vec<_> = filename.split('.').collect(); + + let (x, z) = parse_coords(parts).ok_or_else(|| io::ErrorKind::InvalidInput)?; + + Ok(RegionPosition::new(x, z)) +} + +fn region_position_filename(pos: RegionPosition) -> String { + format!("r.{}.{}.mca", pos.x, pos.z) +} + +fn parse_coords(parts: Vec<&str>) -> Option<(i32, i32)> { + let incorrect_format = + parts.len() != 4 || + parts[0] != "r" || + parts[3] != "mca"; + + if incorrect_format { + return None; + } + + Some((i32::from_str(parts[1]).ok()?, + i32::from_str(parts[2]).ok()?)) +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + use crate::position::RegionPosition; + use crate::provider::region_pos_from_filename; + + #[test] + fn test_position_parse() { + let mut path = PathBuf::new(); + path.set_file_name("r.-1.1.mca"); + + let pos = region_pos_from_filename(&path).unwrap(); + assert_eq!(RegionPosition{ x: -1, z: 1}, pos) + } + + #[test] + #[should_panic] + fn test_position_parse_invalid_format() { + let mut path = PathBuf::new(); + path.set_file_name("this is not a valid region.filename"); + + region_pos_from_filename(&path).unwrap(); + } +} \ No newline at end of file