diff --git a/src/export/profiles.rs b/src/export/profiles.rs index f132f07..d36a28b 100644 --- a/src/export/profiles.rs +++ b/src/export/profiles.rs @@ -6,65 +6,75 @@ pub trait ExportProfile { fn rules(&self) -> Vec>; } -pub struct CurseForgeProfile; -pub struct ModrinthProfile; -pub struct ServerPackProfile; +/// Implements [`ExportProfile`] for a unit struct with a static name and rule +/// list. +/// +/// Each rule entry is an expression evaluated as +/// `Box::new(super::rules::)`, supporting both bare unit struct names and +/// constructor calls with arguments. +/// +/// # Example +/// +/// ```ignore +/// export_profile! { +/// MyProfile => "my-profile" { +/// SomeRule, +/// AnotherRule::new("arg"), +/// } +/// } +/// ``` +macro_rules! export_profile { + ($struct:ident => $name:literal { $($rule:expr),* $(,)? }) => { + pub struct $struct; -impl ExportProfile for CurseForgeProfile { - fn name(&self) -> &'static str { - "curseforge" - } + impl ExportProfile for $struct { + fn name(&self) -> &'static str { + $name + } - fn rules(&self) -> Vec> { - vec![ - Box::new(super::rules::CopyProjectFilesRule), - Box::new(super::rules::FilterByPlatformRule), - Box::new(super::rules::MissingProjectsAsOverridesRule::new( - "curseforge", - )), - Box::new(super::rules::CopyOverridesRule), - Box::new(super::rules::CopyClientOverridesRule), - Box::new(super::rules::FilterServerOnlyRule), - Box::new(super::rules::GenerateManifestRule::curseforge()), - Box::new(super::rules::FilterNonRedistributableRule), - Box::new(super::rules::TextReplacementRule), - ] + fn rules(&self) -> Vec> { + use super::rules::*; + vec![ + $(Box::new($rule)),* + ] + } + } + }; +} + +export_profile! { + CurseForgeProfile => "curseforge" { + CopyProjectFilesRule, + FilterByPlatformRule, + MissingProjectsAsOverridesRule::new("curseforge"), + CopyOverridesRule, + CopyClientOverridesRule, + FilterServerOnlyRule, + GenerateManifestRule::curseforge(), + FilterNonRedistributableRule, + TextReplacementRule } } -impl ExportProfile for ModrinthProfile { - fn name(&self) -> &'static str { - "modrinth" - } - - fn rules(&self) -> Vec> { - vec![ - Box::new(super::rules::CopyProjectFilesRule), - Box::new(super::rules::FilterByPlatformRule), - Box::new(super::rules::MissingProjectsAsOverridesRule::new( - "modrinth", - )), - Box::new(super::rules::CopyOverridesRule), - Box::new(super::rules::CopyClientOverridesRule), - Box::new(super::rules::FilterServerOnlyRule), - Box::new(super::rules::GenerateManifestRule::modrinth()), - Box::new(super::rules::TextReplacementRule), - ] +export_profile! { + ModrinthProfile => "modrinth" { + CopyProjectFilesRule, + FilterByPlatformRule, + MissingProjectsAsOverridesRule::new("modrinth"), + CopyOverridesRule, + CopyClientOverridesRule, + FilterServerOnlyRule, + GenerateManifestRule::modrinth(), + TextReplacementRule } } -impl ExportProfile for ServerPackProfile { - fn name(&self) -> &'static str { - "serverpack" - } - - fn rules(&self) -> Vec> { - vec![ - Box::new(super::rules::CopyProjectFilesRule), - Box::new(super::rules::CopyServerOverridesRule), - Box::new(super::rules::FilterClientOnlyRule), - Box::new(super::rules::TextReplacementRule), - ] +export_profile! { + ServerPackProfile => "serverpack" { + CopyProjectFilesRule, + CopyServerOverridesRule, + FilterClientOnlyRule, + TextReplacementRule } } diff --git a/src/model/lockfile.rs b/src/model/lockfile.rs index dcda70d..bc80cee 100644 --- a/src/model/lockfile.rs +++ b/src/model/lockfile.rs @@ -578,6 +578,53 @@ pub struct LockFile { pub lockfile_version: u32, } +impl LockFile { + pub fn get_project(&self, pakku_id: &str) -> Option<&Project> { + self + .projects + .iter() + .find(|p| p.pakku_id.as_deref() == Some(pakku_id)) + } + + pub fn get_loader_names(&self) -> Vec { + self.loaders.keys().cloned().collect() + } + + pub fn remove_project(&mut self, pakku_id: &str) -> Option { + if let Some(pos) = self + .projects + .iter() + .position(|p| p.pakku_id.as_deref() == Some(pakku_id)) + { + Some(self.projects.remove(pos)) + } else { + None + } + } + + pub fn find_project(&self, pakku_id: &str) -> Option<&Project> { + self.get_project(pakku_id) + } + + pub fn find_project_mut(&mut self, pakku_id: &str) -> Option<&mut Project> { + self + .projects + .iter_mut() + .find(|p| p.pakku_id.as_deref() == Some(pakku_id)) + } + + pub fn find_project_by_platform_id( + &self, + platform: &str, + id: &str, + ) -> Option<&Project> { + self + .projects + .iter() + .find(|p| p.id.get(platform).is_some_and(|pid| pid == id)) + } +} + impl LockFile { pub fn load>(path: P) -> Result { Self::load_with_validation(path, true) @@ -720,52 +767,4 @@ impl LockFile { self.projects.push(project); self.projects.sort_by_key(super::project::Project::get_name); } - - pub fn get_project(&self, pakku_id: &str) -> Option<&Project> { - self - .projects - .iter() - .find(|p| p.pakku_id.as_deref() == Some(pakku_id)) - } - - pub fn get_loader_names(&self) -> Vec { - self.loaders.keys().cloned().collect() - } - - pub fn remove_project(&mut self, pakku_id: &str) -> Option { - if let Some(pos) = self - .projects - .iter() - .position(|p| p.pakku_id.as_deref() == Some(pakku_id)) - { - Some(self.projects.remove(pos)) - } else { - None - } - } - - pub fn find_project(&self, pakku_id: &str) -> Option<&Project> { - self - .projects - .iter() - .find(|p| p.pakku_id.as_deref() == Some(pakku_id)) - } - - pub fn find_project_mut(&mut self, pakku_id: &str) -> Option<&mut Project> { - self - .projects - .iter_mut() - .find(|p| p.pakku_id.as_deref() == Some(pakku_id)) - } - - pub fn find_project_by_platform_id( - &self, - platform: &str, - id: &str, - ) -> Option<&Project> { - self - .projects - .iter() - .find(|p| p.id.get(platform).is_some_and(|pid| pid == id)) - } } diff --git a/src/rate_limiter.rs b/src/rate_limiter.rs index 72dde8d..f5832f8 100644 --- a/src/rate_limiter.rs +++ b/src/rate_limiter.rs @@ -72,30 +72,29 @@ impl RateLimiter { let interval = Duration::from_secs(60) / rate.max(1); - let mut inner = self.inner.lock().await; - let now = Instant::now(); - let platform_requests = - inner.requests.entry(platform.to_string()).or_default(); + loop { + let mut inner = self.inner.lock().await; + let now = Instant::now(); + let platform_requests = + inner.requests.entry(platform.to_string()).or_default(); - platform_requests - .retain(|t| now.duration_since(*t) < Duration::from_secs(60)); + platform_requests + .retain(|t| now.duration_since(*t) < Duration::from_secs(60)); - if platform_requests.len() >= burst as usize - && let Some(oldest) = platform_requests.first() - { - let wait_time = interval.saturating_sub(now.duration_since(*oldest)); - if wait_time > Duration::ZERO { - drop(inner); - tokio::time::sleep(wait_time).await; + if platform_requests.len() >= burst as usize + && let Some(oldest) = platform_requests.first() + { + let wait_time = interval.saturating_sub(now.duration_since(*oldest)); + if wait_time > Duration::ZERO { + drop(inner); + tokio::time::sleep(wait_time).await; + continue; + } } + + platform_requests.push(Instant::now()); + return Ok(()); } - - let mut inner = self.inner.lock().await; - let platform_requests = - inner.requests.entry(platform.to_string()).or_default(); - platform_requests.push(Instant::now()); - - Ok(()) } pub async fn wait_for(&self, platform: &str) {