#![allow(dead_code)] use std::collections::HashSet; use std::fs::{File, OpenOptions}; use std::hash::{self, Hash}; use std::io::Read; use std::path::{Path, PathBuf}; use std::sync::LazyLock; use crate::java::{JAVA_EXT_CLASS, JAVA_EXT_SOURCE}; use crate::{DIR_SRC, DIR_TARGET, F_NEST_LOCK, F_NEST_TOML, PROJECT_ROOT}; use anyhow::Context; use semver::Version; use serde::{Deserialize, Serialize}; pub static NEST: LazyLock> = LazyLock::new(|| Nest::try_from(PROJECT_ROOT.join(F_NEST_TOML.as_path()))); #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Nest { pub package: Package, } impl TryFrom for Nest { type Error = anyhow::Error; fn try_from(value: File) -> Result { let mut value = value; let mut buf = String::new(); value .read_to_string(&mut buf) .context("Failed to read Nest")?; toml::from_str(buf.as_str()).context("Failed to deserialize Nest") } } impl TryFrom for Nest { type Error = anyhow::Error; fn try_from(value: PathBuf) -> Result { Nest::try_from( OpenOptions::new() .read(true) .open(value) .expect("Failed to load Nest.toml"), ) } } // Rename to Prey // Mark as deprecated soon. #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Package { pub name: String, pub version: semver::Version, } impl Default for Package { fn default() -> Self { Self { name: String::from("MyPackage"), version: Version::new(0, 1, 0), } } } #[derive(Debug, Default, Deserialize, Serialize)] pub struct NestLock { pub classes: HashSet, } impl NestLock { pub fn load() -> anyhow::Result { NestLock::try_from(PROJECT_ROOT.join(F_NEST_LOCK.as_path())) } /// Update, retaining all classes that still exist and whose paths are still files, rather than /// being shifted into a package. pub fn update(&mut self) { self.classes = self .classes .clone() .into_iter() .filter_map(|class| { // Paths are almost correct, except that the target classes are expecting to path // through: // target/main/Main.class, not target/Main.class // target/test/MainTest.class, not target/MainTest.class if DIR_SRC .join(&class.path) .with_extension(JAVA_EXT_SOURCE) .exists() && DIR_TARGET .join(&class.subpath()) .with_extension(JAVA_EXT_CLASS) .is_file() { return Some(class); } None }) .collect(); } } impl TryFrom for NestLock { type Error = anyhow::Error; fn try_from(value: File) -> Result { let mut value = value; let mut buf = String::new(); value.read_to_string(&mut buf)?; toml::from_str(buf.as_str()).context("Failed to deserialize NestLock") } } impl TryFrom for NestLock { type Error = anyhow::Error; fn try_from(value: PathBuf) -> Result { NestLock::try_from( OpenOptions::new() .read(true) .open(&value) .with_context(|| format!("Failed to open {}", value.display()))?, ) .context("") } } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct Class { path: PathBuf, checksum: String, } impl Class { /// Returns true if the class needs updating. /// This may also cautionarily return true if it cannot digest the file. pub fn is_updated(&self) -> bool { // Still doesn't handle inter-dependency checks. DIR_TARGET .join(self.subpath()) .with_extension(JAVA_EXT_CLASS) .exists() && sha256::try_digest(DIR_SRC.join(self.path()).with_extension(JAVA_EXT_SOURCE)) .is_ok_and(|hash| self.checksum == hash) } /// Returns the path such that src/ and target/ don't matter. /// E.g., `main/Main.java` as_subpath is `Main.java` /// allowing it to match with `target/main/Main.class` /// because Java diregards the top level subdir in `src/` fn subpath(&self) -> PathBuf { let mut p = self.path.components(); p.next(); // Remove the top level dir. p.as_path().to_path_buf() } pub fn path(&self) -> &Path { self.path.as_path() } } impl Hash for Class { fn hash(&self, state: &mut H) { self.path.hash(state); } } impl TryFrom for Class { type Error = anyhow::Error; fn try_from(mut value: PathBuf) -> Result { if value.is_relative() { value = value .canonicalize() .context("Failed to canonicalize path")?; } Ok(Self { path: PathBuf::from( pathsub::sub_paths(value.as_path(), DIR_SRC.as_path()) .context("Failed to subtract paths to get class path")?, ) .with_extension(""), checksum: sha256::try_digest(&value)?, }) } }