use std::collections::{HashMap, HashSet}; use crate::{ error::{PakkerError, Result}, model::{LockFile, Project}, platform::PlatformClient, }; pub struct DependencyResolver { visited: HashSet, path: Vec, } impl DependencyResolver { pub fn new() -> Self { Self { visited: HashSet::new(), path: Vec::new(), } } pub fn resolve<'a>( &'a mut self, project: &'a mut Project, lockfile: &'a mut LockFile, platforms: &'a HashMap>, ) -> std::pin::Pin< Box>> + 'a>, > { Box::pin(async move { let mut resolved = Vec::new(); if let Some(ref pakku_id) = project.pakku_id { if lockfile.get_project(pakku_id).is_some() { log::debug!("Project already in lockfile: {}", project.get_name()); return Ok(resolved); } if self.path.contains(pakku_id) { let cycle_path = self.path.join(" -> "); return Err(PakkerError::CircularDependency(format!( "{cycle_path} -> {pakku_id}" ))); } self.path.push(pakku_id.clone()); } else { return Ok(resolved); } let mut dependencies_set: HashSet = HashSet::new(); for file in &project.files { for dep_id in &file.required_dependencies { dependencies_set.insert(dep_id.clone()); } } let dependencies: Vec = dependencies_set.into_iter().collect(); for dep_id in dependencies { let existing_pakku_id = lockfile .find_project_by_platform_id("modrinth", &dep_id) .or_else(|| { lockfile.find_project_by_platform_id("curseforge", &dep_id) }) .or_else(|| lockfile.find_project_by_platform_id("github", &dep_id)) .map(|p| p.pakku_id.clone()); if let Some(Some(existing_id)) = existing_pakku_id { if let Some(ref my_id) = project.pakku_id { project.pakku_links.insert(existing_id.clone()); if let Some(existing_mut) = lockfile.find_project_mut(&existing_id) { existing_mut.pakku_links.insert(my_id.clone()); } } continue; } let mut dep_project = self.fetch_dependency(&dep_id, lockfile, platforms).await?; if let (Some(dep_id), Some(my_id)) = (&dep_project.pakku_id, &project.pakku_id) { project.pakku_links.insert(dep_id.clone()); dep_project.pakku_links.insert(my_id.clone()); } let mut sub_deps = self.resolve(&mut dep_project, lockfile, platforms).await?; resolved.push(dep_project); resolved.append(&mut sub_deps); } if let Some(ref pakku_id) = project.pakku_id { self.visited.insert(pakku_id.clone()); } self.path.pop(); Ok(resolved) }) } async fn fetch_dependency( &self, dep_id: &str, lockfile: &LockFile, platforms: &HashMap>, ) -> Result { let mut projects = Vec::new(); for (platform_name, client) in platforms { match client .request_project_with_files( dep_id, &lockfile.mc_versions, &lockfile.get_loader_names(), ) .await { Ok(project) => { log::info!("Found dependency {dep_id} on {platform_name}"); projects.push(project); }, Err(e) => { log::debug!("Could not find {dep_id} on {platform_name}: {e}"); }, } } if projects.is_empty() { return Err(PakkerError::ProjectNotFound(dep_id.to_string())); } if projects.len() == 1 { Ok(projects.into_iter().next().unwrap()) } else { let mut merged = projects.remove(0); for project in projects { merged.merge(project); } Ok(merged) } } } impl Default for DependencyResolver { fn default() -> Self { Self::new() } }