mod.rs (11675B)
1 use std::cell::RefCell; 2 use std::collections::{HashMap, HashSet}; 3 use std::fs; 4 use std::os::unix::fs::MetadataExt; 5 use std::sync::OnceLock; 6 7 use log::{trace, warn}; 8 use send_wrapper::SendWrapper; 9 use sha2::{Digest, Sha256}; 10 use walkdir::WalkDir; 11 12 use kanit_common::constants; 13 use kanit_common::error::{Context, Result, StaticError}; 14 use kanit_unit::formats::{DependencyGrouping, Unit}; 15 use kanit_unit::{RcUnit, UnitName, wrap_unit}; 16 use sort::{SortableUnit, obtain_load_order}; 17 18 mod sort; 19 20 static LOADER: OnceLock<SendWrapper<RefCell<Loader>>> = OnceLock::new(); 21 22 pub struct Loader { 23 pub started: HashSet<UnitName>, // TODO; should become statuses 24 pub enabled: HashMap<Box<str>, HashSet<UnitName>>, 25 pub grouping: HashMap<Box<str>, Vec<Vec<RcUnit>>>, 26 pub units: HashMap<UnitName, RcUnit>, 27 // pub ev_lock: Rc<Mutex<()>>, // i am pro at rust 28 } 29 30 enum UnitLoad { 31 FailedRead(String), 32 FailedParse(String), 33 Loaded(Box<Unit>), 34 } 35 36 #[derive(Copy, Clone, Eq, PartialEq)] 37 enum DrillRound { 38 All, 39 Positional, 40 Needs, 41 } 42 43 impl Loader { 44 // initialize populates the loader with units and if they're enabled 45 // it won't create the load order as `units` will add more units 46 pub fn initialize() -> Result<()> { 47 let mut loader = Loader { 48 started: HashSet::new(), 49 enabled: HashMap::new(), 50 grouping: HashMap::new(), 51 units: HashMap::new(), 52 // ev_lock: Rc::new(Mutex::new(())), 53 }; 54 55 loader.reload()?; 56 57 let _ = LOADER.set(SendWrapper::new(RefCell::new(loader))); 58 59 Ok(()) 60 } 61 62 // TODO; should we separate reload 63 pub fn reload(&mut self) -> Result<()> { 64 // TODO; async? 65 self.units = fs::read_dir(constants::KAN_UNIT_DIR) 66 .context("failed to read units directory")? 67 .filter_map(|e| e.ok()) 68 .map(|f| { 69 fs::read_to_string(f.path()) 70 .map(|c| { 71 ( 72 UnitName::from(f.file_name().to_string_lossy()), 73 c.parse::<Unit>() 74 .map(|u| UnitLoad::Loaded(Box::new(u))) 75 .unwrap_or_else(|e| UnitLoad::FailedParse(e.to_string())), 76 ) 77 }) 78 .unwrap_or_else(|e| { 79 ( 80 UnitName::from(f.file_name().to_string_lossy()), 81 UnitLoad::FailedRead(e.to_string()), 82 ) 83 }) 84 }) 85 .filter_map(|(name, load)| match load { 86 UnitLoad::FailedRead(e) => { 87 warn!("failed to read unit {name}: {e}"); 88 None 89 } 90 UnitLoad::FailedParse(e) => { 91 warn!("failed to parse unit {name}: {e}"); 92 None 93 } 94 UnitLoad::Loaded(u) => { 95 trace!("loaded unit {name}"); 96 Some((name.clone(), wrap_unit((name, *u)))) 97 } 98 }) 99 .collect::<HashMap<_, _>>(); 100 101 // technically we don't care about the contents of the file 102 self.enabled = fs::read_dir(constants::KAN_ENABLED_DIR) 103 .context("failed to read enabled dir")? 104 .filter_map(|e| { 105 e.ok().and_then(|f| { 106 f.file_type() 107 .map(|t| t.is_dir()) 108 .ok() 109 .filter(|t| *t) 110 .map(|_| f) 111 }) 112 }) 113 .filter_map(|f| { 114 fs::read_dir(f.path()) 115 .ok() 116 .map(|r| { 117 r.filter_map(|e| e.ok()) 118 .map(|e| UnitName::from(e.file_name().to_string_lossy())) 119 .collect::<HashSet<_>>() 120 }) 121 .map(|e| (Box::from(f.file_name().to_string_lossy()), e)) 122 }) 123 .collect::<HashMap<_, _>>(); 124 125 Ok(()) 126 } 127 128 // rebuilding levels is such an inefficient process 129 // that's why we cache 130 // we attempt: 131 // * all requirements fulfilled (needs, wants, before, after) 132 // * no positional (needs, wants) 133 // * only needs 134 // give up if it fails 135 136 // recursively 137 fn get_edges(unit: &SortableUnit, round: DrillRound) -> Vec<UnitName> { 138 let deps = unit.dependencies.clone(); 139 140 match round { 141 DrillRound::Needs => deps.needs.clone(), 142 DrillRound::Positional => deps 143 .needs 144 .iter() 145 .chain(deps.wants.iter()) 146 .cloned() 147 .collect(), 148 DrillRound::All => deps 149 .needs 150 .iter() 151 .chain(deps.wants.iter()) 152 .chain(deps.before.iter()) 153 .chain(deps.after.iter()) 154 .cloned() 155 .collect(), 156 } 157 } 158 159 fn drill( 160 to_drill: Vec<UnitName>, 161 info: &HashMap<UnitName, SortableUnit>, 162 round: DrillRound, 163 seen: &mut HashSet<UnitName>, 164 ) -> Result<()> { 165 for unit in to_drill { 166 if seen.contains(&unit) { 167 continue; 168 } 169 170 seen.insert(unit.clone()); 171 172 let unit_info = info 173 .get(&unit) 174 .with_context(move || format!("failed to find unit `{unit}`"))?; 175 176 Self::drill(Self::get_edges(unit_info, round), info, round, seen)?; 177 } 178 179 Ok(()) 180 } 181 182 fn rebuild_level( 183 &self, 184 info: &HashMap<UnitName, SortableUnit>, 185 enabled: &HashSet<UnitName>, 186 ) -> Result<Vec<Vec<RcUnit>>> { 187 let mut to_load = HashSet::new(); 188 189 let rounds = [DrillRound::All, DrillRound::Positional, DrillRound::Needs]; 190 191 for round in rounds { 192 if Self::drill(enabled.iter().cloned().collect(), info, round, &mut to_load).is_err() { 193 continue; 194 } 195 196 let units = to_load 197 .iter() 198 .filter_map(|n| info.get(n)) 199 .cloned() 200 .collect(); 201 202 match obtain_load_order(units) { 203 Ok(order) => { 204 return Ok(order 205 .iter() 206 .map(|n| { 207 n.iter() 208 .map(|m| self.units.get(&m.name).cloned().unwrap()) 209 .collect::<Vec<_>>() 210 }) 211 .collect::<Vec<_>>()); 212 } 213 Err(e) => { 214 if round == DrillRound::Needs { 215 return Err(e); 216 } else { 217 continue; 218 } 219 } 220 } 221 } 222 223 unreachable!() 224 } 225 226 fn hash_enable_dir() -> [u8; 32] { 227 WalkDir::new(constants::KAN_ENABLED_DIR) 228 .follow_links(true) 229 .into_iter() 230 .filter_map(|e| e.ok()) 231 .filter_map(|e| e.metadata().ok().map(|e| e.mtime())) 232 .fold(Sha256::new(), |acc, m| acc.chain_update(m.to_le_bytes())) 233 .finalize() 234 .as_slice() 235 .try_into() 236 .unwrap() // sha256 hash is always 32 bytes (32 * 8 = 256) 237 } 238 239 fn try_cache_map(&mut self) -> Result<()> { 240 let grouping = fs::read_to_string(constants::KAN_DEPENDENCY_MAP) 241 .context("failed to read dependency map")? 242 .parse::<DependencyGrouping>() 243 .context("failed to parse grouping")?; 244 245 let hash = Self::hash_enable_dir(); 246 247 if hash.as_slice() == grouping.hash.as_slice() { 248 self.grouping = grouping 249 .groups 250 .into_iter() 251 .map(|(k, v)| -> Result<_> { 252 Ok(( 253 k, 254 v.into_iter() 255 .map(|v| { 256 v.into_iter() 257 .map(|u| { 258 self.units.get(&u).cloned().context("failed to find unit") 259 }) 260 .collect::<Result<Vec<_>>>() 261 }) 262 .collect::<Result<Vec<_>>>()?, 263 )) 264 }) 265 .collect::<Result<HashMap<_, _>>>()?; 266 } else { 267 Err(StaticError("invalid hashes"))?; 268 } 269 270 Ok(()) 271 } 272 273 pub fn rebuild(&mut self, force: bool) -> Result<()> { 274 if !force && self.try_cache_map().is_ok() { 275 return Ok(()); 276 } 277 278 let info = self 279 .units 280 .iter() 281 .map(|(k, v)| (k.clone(), v.into())) 282 .collect::<HashMap<_, SortableUnit>>(); 283 284 for (name, level) in self.enabled.iter() { 285 let grouping = self.rebuild_level(&info, level)?; 286 287 self.grouping.insert(name.clone(), grouping); 288 } 289 290 Ok(()) 291 } 292 293 pub fn save(&self) -> Result<()> { 294 let groups = self 295 .grouping 296 .iter() 297 .map(|(k, v)| { 298 ( 299 k.clone(), 300 v.iter() 301 .map(|g| g.iter().map(|u| u.borrow().name()).collect()) 302 .collect(), 303 ) 304 }) 305 .collect(); 306 307 let group = DependencyGrouping { 308 hash: Self::hash_enable_dir(), 309 groups, 310 }; 311 312 fs::write(constants::KAN_DEPENDENCY_MAP, group.to_string()) 313 .context("failed to write dependency map")?; 314 315 Ok(()) 316 } 317 318 pub fn obtain() -> Result<&'static SendWrapper<RefCell<Self>>> { 319 let loader = LOADER.get().context("loader not initialized")?; 320 321 if !loader.valid() { 322 Err(StaticError("cannot obtain loader from different thread"))?; 323 } 324 325 Ok(loader) 326 } 327 328 pub fn extend_units<T>(&mut self, iter: T) 329 where 330 T: IntoIterator<Item = (UnitName, RcUnit)>, 331 { 332 self.units.extend(iter); 333 } 334 335 pub fn extend_enabled<T>(&mut self, iter: T) 336 where 337 T: IntoIterator<Item = (Box<str>, HashSet<UnitName>)>, 338 { 339 iter.into_iter().for_each(|(k, v)| { 340 if let Some(enabled) = self.enabled.get_mut(&k) { 341 enabled.extend(v); 342 } else { 343 self.enabled.insert(k, v); 344 } 345 }) 346 } 347 348 pub fn mark_started(&mut self, name: UnitName) { 349 self.started.insert(name); 350 } 351 352 // pub fn mark_stopped(&mut self, name: &UnitName) { 353 // self.started.remove(name); 354 // } 355 // 356 // pub fn is_started(&self, name: &UnitName) -> bool { 357 // self.started.contains(name) 358 // } 359 } 360 361 pub fn init_loader() -> Result<()> { 362 Loader::initialize()?; 363 364 let mut loader = Loader::obtain()?.borrow_mut(); 365 366 #[cfg(feature = "units")] 367 { 368 loader.extend_units(kanit_units::units().map(|u| { 369 let name = u.borrow().name(); 370 (name, u) 371 })); 372 373 loader.extend_enabled(kanit_units::default_enabled()); 374 375 for (unit, _) in loader.units.iter() { 376 trace!("has unit {unit}"); 377 } 378 379 for (level, group) in loader.enabled.iter() { 380 group 381 .iter() 382 .for_each(|unit| trace!("enabling unit {level}->{unit}")); 383 } 384 385 loader.rebuild(false)?; 386 } 387 388 Ok(()) 389 }