diff --git a/daedalus/src/lib.rs b/daedalus/src/lib.rs index 7a9ec53..fa60d68 100644 --- a/daedalus/src/lib.rs +++ b/daedalus/src/lib.rs @@ -17,8 +17,6 @@ use serde::{Deserialize, Serialize}; pub mod minecraft; /// Models and methods for fetching metadata for Minecraft mod loaders pub mod modded; -/// Custom version comparison for Minecraft versions -pub mod version; /// HTTP client configuration constants /// TCP keepalive interval for persistent connections diff --git a/daedalus/src/version.rs b/daedalus/src/version.rs deleted file mode 100644 index b3f686e..0000000 --- a/daedalus/src/version.rs +++ /dev/null @@ -1,376 +0,0 @@ -//! Minecraft version comparison utilities -//! -//! This module provides custom version comparison logic that handles all -//! Minecraft version formats correctly, including: -//! - Snapshots (YYwWWx format like 23w9a, 23w10a) -//! - Pre-releases (X.Y.Z-preN, X.Y.Z-rcN) -//! - Old format (X.Y.Z_preN) -//! - Forge/NeoForge versions (X.Y.Z-A.B.C.D) -//! - Regular releases (X.Y.Z) -//! -//! The standard `lenient_semver` crate fails on snapshot versions because -//! it uses lexicographic comparison which incorrectly orders "23w9a" > "23w10a" -//! (since '9' > '1' in ASCII). - -use std::cmp::Ordering; - -/// Parsed Minecraft version format -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum MinecraftVersion { - /// Snapshot version (YYwWWx format) - /// Example: 23w10a = year 23, week 10, revision a - Snapshot { - /// Year (e.g., 23 for 2023) - year: u32, - /// Week number (1-52) - week: u32, - /// Revision letter (e.g., "a", "b") - revision: String, - }, - /// Regular release version - /// Example: 1.20.4 - Release { - /// Major version number - major: u32, - /// Minor version number - minor: u32, - /// Patch version number - patch: u32, - /// Pre-release identifier (pre, rc, etc.) - prerelease: Option, - /// Build metadata (for Forge versions) - build: Option>, - }, -} - -/// Pre-release format -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Prerelease { - /// Pre-release (e.g., "pre1", "pre2") - Pre(u32), - /// Release candidate (e.g., "rc1", "rc2") - Rc(u32), - /// Other pre-release format (e.g., "alpha", "beta") - Other(String), -} - -impl MinecraftVersion { - /// Parse a Minecraft version string - pub fn parse(version: &str) -> Result { - // Try snapshot format first (YYwWWx) - if let Some(snapshot) = Self::try_parse_snapshot(version) { - return Ok(snapshot); - } - - // Try release/pre-release format - Self::parse_release(version) - } - - /// Try to parse as snapshot (YYwWWx format) - fn try_parse_snapshot(version: &str) -> Option { - // Check for 'w' character which is unique to snapshots - if !version.contains('w') { - return None; - } - - // Split on 'w' - let parts: Vec<&str> = version.split('w').collect(); - if parts.len() != 2 { - return None; - } - - // Parse year (before 'w') - let year = parts[0].parse::().ok()?; - - // Parse week and revision (after 'w') - // Week can be 1-2 digits, revision is everything after - let week_and_rev = parts[1]; - - // Find where the numeric week ends - let week_end = week_and_rev - .chars() - .position(|c| !c.is_ascii_digit()) - .unwrap_or(week_and_rev.len()); - - let week = week_and_rev[..week_end].parse::().ok()?; - let revision = week_and_rev[week_end..].to_string(); - - Some(MinecraftVersion::Snapshot { - year, - week, - revision, - }) - } - - /// Parse as release version - fn parse_release(version: &str) -> Result { - // Normalize: replace underscore with hyphen for old format compatibility - let normalized = version.replace('_', "-"); - - // Split on '-' to separate version from prerelease/build - let parts: Vec<&str> = normalized.split('-').collect(); - - // Parse base version (X.Y.Z or X.Y) - let version_parts: Vec<&str> = parts[0].split('.').collect(); - - let major = version_parts - .first() - .and_then(|s| s.parse::().ok()) - .ok_or_else(|| format!("Invalid major version in '{}'", version))?; - - let minor = version_parts - .get(1) - .and_then(|s| s.parse::().ok()) - .unwrap_or(0); - - let patch = version_parts - .get(2) - .and_then(|s| s.parse::().ok()) - .unwrap_or(0); - - // Parse pre-release if present (e.g., "pre1", "rc2") - let mut prerelease = None; - let mut build = None; - - if parts.len() > 1 { - // Check if it's a pre-release - let pre_str = parts[1]; - prerelease = Self::parse_prerelease(pre_str); - - // If not a prerelease, try parsing as build metadata (Forge format) - if prerelease.is_none() { - // Try parsing as numeric build (e.g., "14.23.5.2859" in Forge) - let build_parts: Vec = parts[1..] - .iter() - .flat_map(|s| s.split('.')) - .filter_map(|s| s.parse::().ok()) - .collect(); - - if !build_parts.is_empty() { - build = Some(build_parts); - } - } - } - - Ok(MinecraftVersion::Release { - major, - minor, - patch, - prerelease, - build, - }) - } - - /// Parse pre-release identifier - fn parse_prerelease(s: &str) -> Option { - if let Some(stripped) = s.strip_prefix("pre") { - return stripped.parse::().ok().map(Prerelease::Pre); - } - - if let Some(stripped) = s.strip_prefix("rc") { - return stripped.parse::().ok().map(Prerelease::Rc); - } - - // Other pre-release formats (alpha, beta, etc.) - if !s.chars().all(|c| c.is_ascii_digit() || c == '.') { - return Some(Prerelease::Other(s.to_string())); - } - - None - } -} - -impl PartialOrd for MinecraftVersion { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for MinecraftVersion { - fn cmp(&self, other: &Self) -> Ordering { - match (self, other) { - // Snapshot vs Snapshot - ( - MinecraftVersion::Snapshot { year: y1, week: w1, revision: r1 }, - MinecraftVersion::Snapshot { year: y2, week: w2, revision: r2 }, - ) => { - // Compare year first - match y1.cmp(y2) { - Ordering::Equal => { - // Then week - match w1.cmp(w2) { - Ordering::Equal => { - // Finally revision (lexicographic) - r1.cmp(r2) - } - other => other, - } - } - other => other, - } - } - - // Release vs Release - ( - MinecraftVersion::Release { major: maj1, minor: min1, patch: p1, prerelease: pre1, build: b1 }, - MinecraftVersion::Release { major: maj2, minor: min2, patch: p2, prerelease: pre2, build: b2 }, - ) => { - // Compare major.minor.patch - match maj1.cmp(maj2) { - Ordering::Equal => match min1.cmp(min2) { - Ordering::Equal => match p1.cmp(p2) { - Ordering::Equal => { - // Compare pre-release - match (pre1, pre2) { - (None, None) => { - // Compare build metadata if both are releases - compare_builds(b1, b2) - } - (Some(_), None) => Ordering::Less, // Pre-release < release - (None, Some(_)) => Ordering::Greater, // Release > pre-release - (Some(p1), Some(p2)) => compare_prereleases(p1, p2), - } - } - other => other, - }, - other => other, - }, - other => other, - } - } - - // Snapshot vs Release comparison heuristic - // - // This is a simplified comparison that treats ALL snapshots as greater than - // ALL releases, which matches the general pattern that snapshots are development - // versions that come after the previous release. - // - // Example: 23w51a (snapshot for 1.20.3/1.20.4) > 1.20.2 (release) - // - // This heuristic works for most use cases in daedalus_client where we're - // organizing versions chronologically within a loader. For precise timeline - // ordering, callers should use the Minecraft version manifest ordering. - // - // Note: This does NOT mean 23w51a > 1.20.4, but rather that when comparing - // a snapshot to any release without additional context, we assume the snapshot - // is newer development work. - (MinecraftVersion::Snapshot { .. }, MinecraftVersion::Release { .. }) => { - Ordering::Greater - } - (MinecraftVersion::Release { .. }, MinecraftVersion::Snapshot { .. }) => { - Ordering::Less - } - } - } -} - -/// Compare build metadata (Forge versions) -fn compare_builds(b1: &Option>, b2: &Option>) -> Ordering { - match (b1, b2) { - (None, None) => Ordering::Equal, - (Some(_), None) => Ordering::Greater, - (None, Some(_)) => Ordering::Less, - (Some(v1), Some(v2)) => { - // Compare element by element - for (x, y) in v1.iter().zip(v2.iter()) { - match x.cmp(y) { - Ordering::Equal => continue, - other => return other, - } - } - // If all equal so far, longer version is greater - v1.len().cmp(&v2.len()) - } - } -} - -/// Compare pre-release identifiers -fn compare_prereleases(p1: &Prerelease, p2: &Prerelease) -> Ordering { - match (p1, p2) { - (Prerelease::Pre(n1), Prerelease::Pre(n2)) => n1.cmp(n2), - (Prerelease::Rc(n1), Prerelease::Rc(n2)) => n1.cmp(n2), - (Prerelease::Pre(_), Prerelease::Rc(_)) => Ordering::Less, // pre < rc - (Prerelease::Rc(_), Prerelease::Pre(_)) => Ordering::Greater, // rc > pre - (Prerelease::Other(s1), Prerelease::Other(s2)) => s1.cmp(s2), - (Prerelease::Other(_), _) => Ordering::Less, // other < specific - (_, Prerelease::Other(_)) => Ordering::Greater, // specific > other - } -} - -/// Convenience function for comparing two version strings -pub fn compare_versions(v1: &str, v2: &str) -> Result { - let ver1 = MinecraftVersion::parse(v1)?; - let ver2 = MinecraftVersion::parse(v2)?; - Ok(ver1.cmp(&ver2)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_snapshot() { - let v = MinecraftVersion::parse("23w10a").unwrap(); - assert!(matches!(v, MinecraftVersion::Snapshot { year: 23, week: 10, .. })); - - let v = MinecraftVersion::parse("20w14infinite").unwrap(); - assert!(matches!(v, MinecraftVersion::Snapshot { year: 20, week: 14, .. })); - } - - #[test] - fn test_parse_release() { - let v = MinecraftVersion::parse("1.20.4").unwrap(); - assert!(matches!(v, MinecraftVersion::Release { major: 1, minor: 20, patch: 4, .. })); - } - - #[test] - fn test_parse_prerelease() { - let v = MinecraftVersion::parse("1.20.4-pre1").unwrap(); - if let MinecraftVersion::Release { ref prerelease, .. } = v { - assert!(matches!(prerelease, Some(Prerelease::Pre(1)))); - } else { - panic!("Expected Release variant"); - } - } - - #[test] - fn test_snapshot_ordering() { - let v1 = MinecraftVersion::parse("23w9a").unwrap(); - let v2 = MinecraftVersion::parse("23w10a").unwrap(); - assert!(v1 < v2, "23w9a should be less than 23w10a"); - } - - #[test] - fn test_release_ordering() { - let v1 = MinecraftVersion::parse("1.19.4").unwrap(); - let v2 = MinecraftVersion::parse("1.20.0").unwrap(); - assert!(v1 < v2); - } - - #[test] - fn test_prerelease_ordering() { - let release = MinecraftVersion::parse("1.20.4").unwrap(); - let pre = MinecraftVersion::parse("1.20.4-pre1").unwrap(); - assert!(pre < release, "pre-release should be less than release"); - } - - #[test] - fn test_old_format() { - let v = MinecraftVersion::parse("1.7.10_pre4").unwrap(); - if let MinecraftVersion::Release { ref prerelease, .. } = v { - assert!(matches!(prerelease, Some(Prerelease::Pre(4)))); - } else { - panic!("Expected Release variant"); - } - - let release = MinecraftVersion::parse("1.7.10").unwrap(); - assert!(v < release, "1.7.10_pre4 should be less than 1.7.10"); - } - - #[test] - fn test_forge_versions() { - let v1 = MinecraftVersion::parse("1.12.2-14.23.5.2851").unwrap(); - let v2 = MinecraftVersion::parse("1.12.2-14.23.5.2859").unwrap(); - assert!(v1 < v2, "Build 2851 should be less than 2859"); - } -} diff --git a/daedalus/tests/version_comparison.rs b/daedalus/tests/version_comparison.rs deleted file mode 100644 index 5d8dc7d..0000000 --- a/daedalus/tests/version_comparison.rs +++ /dev/null @@ -1,346 +0,0 @@ -/// Comprehensive version comparison tests for Minecraft versions -/// -/// This test suite validates that lenient_semver correctly handles all edge cases -/// in Minecraft version formats, including: -/// - Snapshots (e.g., 23w9a, 20w14infinite) -/// - Pre-releases (e.g., 1.20.4-pre1, 1.20.4-rc1) -/// - Forge versions (e.g., 1.12.2-14.23.5.2859) -/// - Old formats (e.g., 1.7.10_pre4) -/// - April Fools versions (e.g., 20w14infinite, 23w13a_or_b) -/// -/// These tests are critical for ensuring correct version ordering in the -/// daedalus_client when processing Minecraft and mod loader metadata. - -#[cfg(test)] -mod tests { - use daedalus::version::{MinecraftVersion, compare_versions}; - use std::cmp::Ordering; - - /// Test basic semantic version comparison - #[test] - fn test_basic_semver() { - // Basic version ordering - assert!(MinecraftVersion::parse("1.0.0").unwrap() < MinecraftVersion::parse("1.0.1").unwrap()); - assert!(MinecraftVersion::parse("1.0.1").unwrap() < MinecraftVersion::parse("1.1.0").unwrap()); - assert!(MinecraftVersion::parse("1.1.0").unwrap() < MinecraftVersion::parse("2.0.0").unwrap()); - - // Equality - assert_eq!(MinecraftVersion::parse("1.20.4").unwrap(), MinecraftVersion::parse("1.20.4").unwrap()); - - // Reverse ordering - assert!(MinecraftVersion::parse("2.0.0").unwrap() > MinecraftVersion::parse("1.20.4").unwrap()); - } - - /// Test snapshot version comparison (YYwWWx format) - /// - /// Snapshots use format: YYwWWx where: - /// - YY = year (two digits) - /// - WW = week number (two digits) - /// - x = optional letter revision (a, b, c, etc.) - #[test] - fn test_snapshot_versions() { - // Same year, different weeks - assert!( - MinecraftVersion::parse("23w9a").unwrap() < MinecraftVersion::parse("23w10a").unwrap(), - "23w9a should be less than 23w10a (week 9 < week 10)" - ); - - // Same week, different revisions - assert!( - MinecraftVersion::parse("23w10a").unwrap() < MinecraftVersion::parse("23w10b").unwrap(), - "23w10a should be less than 23w10b (revision a < b)" - ); - - // Different years - assert!( - MinecraftVersion::parse("22w50a").unwrap() < MinecraftVersion::parse("23w01a").unwrap(), - "22w50a should be less than 23w01a (year 22 < 23)" - ); - - // Complex comparison: older year with higher week - assert!( - MinecraftVersion::parse("22w51a").unwrap() < MinecraftVersion::parse("23w10a").unwrap(), - "22w51a should be less than 23w10a (year takes precedence)" - ); - } - - /// Test pre-release and release candidate versions - /// - /// Pre-releases use formats: - /// - X.Y.Z-preN (pre-release) - /// - X.Y.Z-rcN (release candidate) - #[test] - fn test_prerelease_versions() { - let release = MinecraftVersion::parse("1.20.4").unwrap(); - let pre1 = MinecraftVersion::parse("1.20.4-pre1").unwrap(); - let pre2 = MinecraftVersion::parse("1.20.4-pre2").unwrap(); - let rc1 = MinecraftVersion::parse("1.20.4-rc1").unwrap(); - - // Pre-releases should be less than release - assert!( - pre1 < release, - "1.20.4-pre1 should be less than 1.20.4" - ); - - assert!( - pre2 < release, - "1.20.4-pre2 should be less than 1.20.4" - ); - - assert!( - rc1 < release, - "1.20.4-rc1 should be less than 1.20.4" - ); - - // Pre-release ordering - assert!( - pre1 < pre2, - "1.20.4-pre1 should be less than 1.20.4-pre2" - ); - - // RC typically comes after pre-releases in semver - // But ordering depends on lenient_semver's lexicographic handling - // Document the actual behavior rather than assume semver rules - if rc1 < pre1 { - println!("Note: lenient_semver orders rc before pre (lexicographic)"); - } else { - println!("Note: lenient_semver orders rc after pre (semver-like)"); - } - } - - /// Test Forge version formats - /// - /// Forge versions use format: X.Y.Z-A.B.C.D where: - /// - X.Y.Z = Minecraft version - /// - A.B.C.D = Forge build number - #[test] - fn test_forge_versions() { - // Same Minecraft version, different Forge builds - assert!( - MinecraftVersion::parse("1.12.2-14.23.5.2851").unwrap() < MinecraftVersion::parse("1.12.2-14.23.5.2859").unwrap(), - "1.12.2-14.23.5.2851 should be less than 1.12.2-14.23.5.2859 (build 2851 < 2859)" - ); - - // Different Minecraft versions - assert!( - MinecraftVersion::parse("1.12.2-14.23.5.2859").unwrap() < MinecraftVersion::parse("1.16.5-36.2.39").unwrap(), - "1.12.2 should be less than 1.16.5" - ); - - // Different major Forge versions for same MC version - assert!( - MinecraftVersion::parse("1.12.2-14.23.5.2859").unwrap() < MinecraftVersion::parse("1.12.2-14.23.6.2859").unwrap(), - "14.23.5.2859 should be less than 14.23.6.2859" - ); - } - - /// Test old Minecraft version format (underscore instead of hyphen) - /// - /// Old format: X.Y.Z_preN (used in very old versions) - #[test] - fn test_old_format_versions() { - // Old format with underscore - let old_format = MinecraftVersion::parse("1.7.10_pre4").unwrap(); - let modern_format = MinecraftVersion::parse("1.7.10-pre4").unwrap(); - let release = MinecraftVersion::parse("1.7.10").unwrap(); - - // Both formats should be less than release - assert!( - old_format < release, - "1.7.10_pre4 should be less than 1.7.10" - ); - - // Note: lenient_semver may not treat _ and - identically - // Document the actual behavior - if old_format == modern_format { - println!("Note: lenient_semver treats underscore and hyphen identically"); - } else { - println!("Note: lenient_semver treats underscore and hyphen differently"); - } - } - - /// Test April Fools versions - /// - /// Special versions: - /// - 20w14infinite (Infinite dimensions) - /// - 23w13a_or_b (Vote update) - /// - 2point0_red, 2point0_blue, 2point0_purple (Super Duper Graphics Pack prank) - #[test] - fn test_april_fools_versions() { - // 20w14infinite is a snapshot from 2020 week 14 - let infinite = MinecraftVersion::parse("20w14infinite").unwrap(); - let normal_snapshot = MinecraftVersion::parse("20w14a").unwrap(); - - // April Fools versions should be comparable to regular snapshots - // The suffix "infinite" vs "a" determines ordering (lexicographic) - println!("20w14infinite vs 20w14a ordering:"); - if infinite < normal_snapshot { - println!(" 20w14infinite < 20w14a (lexicographic: 'a' > 'i')"); - } else { - println!(" 20w14infinite > 20w14a (lexicographic: 'i' > 'a')"); - } - - // 23w13a_or_b from 2023 - let vote_update = MinecraftVersion::parse("23w13a_or_b").unwrap(); - let same_week = MinecraftVersion::parse("23w13a").unwrap(); - - println!("23w13a_or_b vs 23w13a ordering:"); - if vote_update < same_week { - println!(" 23w13a_or_b < 23w13a"); - } else { - println!(" 23w13a_or_b > 23w13a"); - } - - // 2point0 variants - These are unparseable April Fools versions - // that don't follow any standard format. Document as unsupported edge case. - println!("2point0 variants (April Fools 2016) are not parseable:"); - assert!(MinecraftVersion::parse("2point0_red").is_err()); - assert!(MinecraftVersion::parse("2point0_blue").is_err()); - assert!(MinecraftVersion::parse("2point0_purple").is_err()); - println!(" Note: These versions don't follow any standard format and are intentionally unsupported"); - } - - /// Test version comparison across different formats - /// - /// This ensures that versions can be compared even when they use - /// different formatting conventions (important for daedalus_client) - #[test] - fn test_mixed_format_comparison() { - // Release vs snapshot - assert!( - MinecraftVersion::parse("1.19.4").unwrap() < MinecraftVersion::parse("1.20.0").unwrap(), - "Release versions should order correctly" - ); - - // Snapshot vs pre-release - let snapshot_1_20 = MinecraftVersion::parse("23w51a").unwrap(); - let pre_1_20_3 = MinecraftVersion::parse("1.20.3-pre1").unwrap(); - - println!("Snapshot vs pre-release comparison:"); - println!(" 23w51a vs 1.20.3-pre1: {:?}", snapshot_1_20.cmp(&pre_1_20_3)); - - // Old format vs new format - let old = MinecraftVersion::parse("1.7.10_pre4").unwrap(); - let new_pre = MinecraftVersion::parse("1.8.0-pre1").unwrap(); - - assert!( - old < new_pre, - "1.7.10_pre4 should be less than 1.8.0-pre1 (major version difference)" - ); - } - - /// Test edge cases and boundary conditions - #[test] - fn test_edge_cases() { - // Single digit versions - assert!( - MinecraftVersion::parse("1.0").unwrap() < MinecraftVersion::parse("1.1").unwrap(), - "Single digit versions should work" - ); - - // Very long version numbers (Forge) - let long_version = "1.12.2-14.23.5.2859"; - let parsed = lenient_semver::parse(long_version); - assert_eq!( - parsed, lenient_semver::parse(long_version), - "Long Forge versions should be parseable and comparable" - ); - - // Versions with many parts - let many_parts = MinecraftVersion::parse("1.16.5-36.2.39.256").unwrap(); - let fewer_parts = MinecraftVersion::parse("1.16.5-36.2.39").unwrap(); - - println!("Version with different part counts:"); - println!(" 1.16.5-36.2.39.256 vs 1.16.5-36.2.39: {:?}", many_parts.cmp(&fewer_parts)); - } - - /// Test version comparison accuracy for known Minecraft release timeline - /// - /// This validates ordering matches the actual Minecraft release history - #[test] - fn test_minecraft_release_timeline() { - // Historical version order (subset of actual timeline) - let versions = vec![ - "1.7.10", - "1.8.0", - "1.8.9", - "1.9.0", - "1.12.2", - "1.16.5", - "1.18.2", - "1.19.4", - "1.20.0", - "1.20.4", - ]; - - // Verify each version is less than the next - for i in 0..versions.len() - 1 { - let current = lenient_semver::parse(versions[i]); - let next = lenient_semver::parse(versions[i + 1]); - - assert!( - current < next, - "{} should be less than {} (historical release order)", - versions[i], - versions[i + 1] - ); - } - } - - /// Test NeoForge version format (similar to modern Forge) - /// - /// NeoForge versions use format: X.Y.Z-A.B.C where: - /// - X.Y.Z = Minecraft version - /// - A.B.C = NeoForge version - #[test] - fn test_neoforge_versions() { - // NeoForge started at 1.20.1 as a Forge fork - assert!( - MinecraftVersion::parse("1.20.1-47.1.0").unwrap() < MinecraftVersion::parse("1.20.1-47.1.3").unwrap(), - "NeoForge patch versions should order correctly" - ); - - assert!( - MinecraftVersion::parse("1.20.1-47.1.0").unwrap() < MinecraftVersion::parse("1.20.4-20.4.80").unwrap(), - "NeoForge versions across MC versions should order correctly" - ); - } - - /// Test Fabric version comparison - /// - /// Fabric versions are typically semantic versions without - /// the Minecraft version prefix - #[test] - fn test_fabric_versions() { - assert!( - MinecraftVersion::parse("0.14.0").unwrap() < MinecraftVersion::parse("0.15.0").unwrap(), - "Fabric minor versions should order correctly" - ); - - assert!( - MinecraftVersion::parse("0.14.21").unwrap() < MinecraftVersion::parse("0.14.22").unwrap(), - "Fabric patch versions should order correctly" - ); - - assert!( - MinecraftVersion::parse("0.15.11").unwrap() < MinecraftVersion::parse("1.0.0").unwrap(), - "Fabric major version bump should order correctly" - ); - } - - /// Test Quilt version comparison (similar to Fabric) - /// - /// Quilt uses semantic versioning - #[test] - fn test_quilt_versions() { - assert!( - MinecraftVersion::parse("0.18.0").unwrap() < MinecraftVersion::parse("0.19.0").unwrap(), - "Quilt minor versions should order correctly" - ); - - assert!( - MinecraftVersion::parse("0.19.0").unwrap() < MinecraftVersion::parse("0.19.1").unwrap(), - "Quilt patch versions should order correctly" - ); - } -} diff --git a/daedalus_client/src/loaders/mod.rs b/daedalus_client/src/loaders/mod.rs index 43905d2..c9bc21c 100644 --- a/daedalus_client/src/loaders/mod.rs +++ b/daedalus_client/src/loaders/mod.rs @@ -312,13 +312,20 @@ impl LoaderProcessor { }) } - // Note: Versions are now tracked in ManifestBuilder and uploaded separately - // in the main loop via manifest_builder.build_loader_manifest() + // Build the complete manifest and store it in ManifestBuilder + // Fabric/Quilt use daedalus::modded::Manifest format (game_versions array) + // which doesn't include release_time unlike LoaderManifestEntry + let manifest = daedalus::modded::Manifest { + game_versions: versions, + }; + + let versions_json = serde_json::to_value(&manifest.game_versions)?; + manifest_builder.set_loader_versions(self.strategy.manifest_path_prefix(), versions_json); info!( "✅ {} - Processed {} game versions", self.strategy.name(), - versions.len() + manifest.game_versions.len() ); Ok(()) @@ -602,14 +609,11 @@ impl LoaderProcessor { new_hash.clone() }; - manifest_builder.add_version( - self.strategy.manifest_path_prefix(), - loader.clone(), - version_hash.clone(), - version_bytes.len() as u64, - version_info.release_time, - ); - + // Store the CAS URL for this loader version + // Note: We don't call manifest_builder.add_version() here because Fabric/Quilt + // use a custom manifest format (daedalus::modded::Manifest) that doesn't include + // release_time. The manifest will be built at the end of retrieve_data() using + // manifest_builder.set_loader_versions(). let base_url = dotenvy::var("BASE_URL").unwrap(); let cas_url = format!( "{}/v{}/objects/{}/{}", diff --git a/daedalus_client/src/minecraft/mod.rs b/daedalus_client/src/minecraft/mod.rs index 9f2784c..b215f1d 100644 --- a/daedalus_client/src/minecraft/mod.rs +++ b/daedalus_client/src/minecraft/mod.rs @@ -645,7 +645,6 @@ pub async fn retrieve_data( lwjgl.version.clone(), lwjgl_hash, lwjgl_bytes.len() as u64, - lwjgl.release_time, ); } else { info!( diff --git a/daedalus_client/src/services/cas.rs b/daedalus_client/src/services/cas.rs index 7fe9056..c29a0ff 100644 --- a/daedalus_client/src/services/cas.rs +++ b/daedalus_client/src/services/cas.rs @@ -112,9 +112,6 @@ pub struct LoaderManifestEntry { pub hash: String, /// Size of the content in bytes pub size: u64, - /// When this version was originally released - #[serde(with = "chrono::serde::ts_seconds")] - pub release_time: DateTime, } /// Loader manifest containing all versions for a specific loader @@ -184,10 +181,10 @@ impl LoaderManifest { /// let forge_manifest = builder.build_loader_manifest("forge"); /// ``` pub struct ManifestBuilder { - /// Map of loader name → (version_id → (hash, size, release_time)) + /// Map of loader name → (version_id → (hash, size)) /// Using nested DashMap for concurrent access at both levels /// Used for simple loaders (forge, neoforge) that use LoaderManifestEntry schema - versions: DashMap)>>, + versions: DashMap>, /// Map of loader name → custom JSON for versions /// Used for complex loaders (minecraft, fabric, quilt) that provide full custom schemas @@ -206,7 +203,7 @@ impl ManifestBuilder { /// Add a version entry for a specific loader (simple mode) /// /// This is for simple loaders (forge, neoforge) that use the standard - /// LoaderManifestEntry schema (id, hash, size, release_time). + /// LoaderManifestEntry schema (id, hash, size). /// /// If the version already exists for this loader, it will be overwritten. /// This is idempotent and thread-safe. @@ -217,9 +214,8 @@ impl ManifestBuilder { /// * `version_id` - Version identifier (e.g., "49.0.3") /// * `hash` - SHA256 hash of the version's content /// * `size` - Size of the content in bytes - /// * `release_time` - When this version was originally released #[instrument(skip(self), level = "debug")] - pub fn add_version(&self, loader: &str, version_id: String, hash: String, size: u64, release_time: DateTime) { + pub fn add_version(&self, loader: &str, version_id: String, hash: String, size: u64) { // Get or create the version map for this loader let loader_map = self .versions @@ -227,7 +223,7 @@ impl ManifestBuilder { .or_default(); // Add the version entry - loader_map.insert(version_id, (hash, size, release_time)); + loader_map.insert(version_id, (hash, size)); } /// Set custom versions JSON for a loader (complex mode) @@ -297,12 +293,11 @@ impl ManifestBuilder { let mut entries: Vec = loader_map .iter() .map(|entry| { - let (version_id, (hash, size, release_time)) = entry.pair(); + let (version_id, (hash, size)) = entry.pair(); LoaderManifestEntry { id: version_id.clone(), hash: hash.clone(), size: *size, - release_time: *release_time, } }) .collect(); @@ -421,7 +416,6 @@ mod tests { id: "1.20.4".to_string(), hash: "abc123".to_string(), size: 1024, - release_time: Utc::now(), }; assert_eq!(entry.id, "1.20.4"); @@ -431,19 +425,16 @@ mod tests { #[test] fn test_loader_manifest_creation() { - let release_time = Utc::now(); let entries = vec![ LoaderManifestEntry { id: "1.20.4".to_string(), hash: "abc123".to_string(), size: 1024, - release_time, }, LoaderManifestEntry { id: "1.20.3".to_string(), hash: "def456".to_string(), size: 2048, - release_time, }, ]; @@ -478,7 +469,7 @@ mod tests { fn test_manifest_builder_add_version() { let builder = ManifestBuilder::new(); - builder.add_version("minecraft", "1.20.4".to_string(), "abc123".to_string(), 1024, Utc::now()); + builder.add_version("minecraft", "1.20.4".to_string(), "abc123".to_string(), 1024); assert_eq!(builder.loader_count(), 1); assert_eq!(builder.version_count("minecraft"), 1); @@ -487,11 +478,10 @@ mod tests { #[test] fn test_manifest_builder_multiple_loaders() { let builder = ManifestBuilder::new(); - let release_time = Utc::now(); - builder.add_version("minecraft", "1.20.4".to_string(), "abc123".to_string(), 1024, release_time); - builder.add_version("forge", "49.0.3".to_string(), "def456".to_string(), 2048, release_time); - builder.add_version("fabric", "0.15.0".to_string(), "ghi789".to_string(), 512, release_time); + builder.add_version("minecraft", "1.20.4".to_string(), "abc123".to_string(), 1024); + builder.add_version("forge", "49.0.3".to_string(), "def456".to_string(), 2048); + builder.add_version("fabric", "0.15.0".to_string(), "ghi789".to_string(), 512); assert_eq!(builder.loader_count(), 3); assert_eq!(builder.version_count("minecraft"), 1); @@ -505,11 +495,10 @@ mod tests { #[test] fn test_manifest_builder_multiple_versions() { let builder = ManifestBuilder::new(); - let release_time = Utc::now(); - builder.add_version("minecraft", "1.20.4".to_string(), "abc123".to_string(), 1024, release_time); - builder.add_version("minecraft", "1.20.3".to_string(), "def456".to_string(), 2048, release_time); - builder.add_version("minecraft", "1.20.2".to_string(), "ghi789".to_string(), 512, release_time); + builder.add_version("minecraft", "1.20.4".to_string(), "abc123".to_string(), 1024); + builder.add_version("minecraft", "1.20.3".to_string(), "def456".to_string(), 2048); + builder.add_version("minecraft", "1.20.2".to_string(), "ghi789".to_string(), 512); assert_eq!(builder.loader_count(), 1); assert_eq!(builder.version_count("minecraft"), 3); @@ -518,10 +507,9 @@ mod tests { #[test] fn test_manifest_builder_build_manifest() { let builder = ManifestBuilder::new(); - let release_time = Utc::now(); - builder.add_version("minecraft", "1.20.4".to_string(), "abc123".to_string(), 1024, release_time); - builder.add_version("minecraft", "1.20.3".to_string(), "def456".to_string(), 2048, release_time); + builder.add_version("minecraft", "1.20.4".to_string(), "abc123".to_string(), 1024); + builder.add_version("minecraft", "1.20.3".to_string(), "def456".to_string(), 2048); let manifest = builder.build_loader_manifest("minecraft").unwrap(); @@ -540,7 +528,7 @@ mod tests { fn test_manifest_builder_nonexistent_loader() { let builder = ManifestBuilder::new(); - builder.add_version("minecraft", "1.20.4".to_string(), "abc123".to_string(), 1024, Utc::now()); + builder.add_version("minecraft", "1.20.4".to_string(), "abc123".to_string(), 1024); assert!(builder.build_loader_manifest("forge").is_none()); assert_eq!(builder.version_count("forge"), 0); @@ -549,11 +537,10 @@ mod tests { #[test] fn test_manifest_builder_overwrite_version() { let builder = ManifestBuilder::new(); - let release_time = Utc::now(); // Add same version twice with different hashes - builder.add_version("minecraft", "1.20.4".to_string(), "abc123".to_string(), 1024, release_time); - builder.add_version("minecraft", "1.20.4".to_string(), "def456".to_string(), 2048, release_time); + builder.add_version("minecraft", "1.20.4".to_string(), "abc123".to_string(), 1024); + builder.add_version("minecraft", "1.20.4".to_string(), "def456".to_string(), 2048); assert_eq!(builder.version_count("minecraft"), 1); // Still 1, overwritten