PlugSnatcher/src-tauri/src/lib.rs

328 lines
12 KiB
Rust

// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
// Standard library imports
use std::path::Path;
use std::env;
// Serde for serialization/deserialization
// Tauri related imports
// Internal modules
pub mod models;
pub mod services;
pub mod commands;
pub mod crawlers;
pub mod platform_matcher;
// Import our models
pub use models::server::{ServerType, ServerInfo, ScanResult, ScanProgress};
pub use models::plugin::{Plugin, PluginMeta};
pub use models::repository::{RepositorySource, RepositoryPlugin, PotentialPluginMatch};
// Import our services
pub use services::http::HttpClient;
pub use services::plugin_scanner::{scan_server_directory, perform_scan, extract_plugin_metadata, calculate_file_hash, is_file_locked};
pub use services::update_manager::{check_for_plugin_updates, check_single_plugin_update, backup_plugin, replace_plugin, normalize_version, compare_plugin_versions};
// Import our commands
pub use commands::plugin_commands::*;
pub use commands::scan_commands::*;
// Import our crawlers
pub use crawlers::HangarCrawler;
pub use crawlers::SpigotMCCrawler;
pub use crawlers::ModrinthCrawler;
pub use crawlers::GitHubCrawler;
pub use crawlers::Repository;
// Import platform matchers
pub use platform_matcher::{get_compatible_modrinth_loaders, is_version_compatible_with_server};
use futures::future::{BoxFuture, FutureExt}; // Import necessary future types
/// Search for plugins in repositories
pub async fn lib_search_plugins_in_repositories(
query: &str,
repositories: Vec<RepositorySource>
) -> Result<Vec<RepositoryPlugin>, String> {
// Check for empty query
if query.is_empty() {
return Err("Search query cannot be empty".to_string());
}
// Check for empty repository list
if repositories.is_empty() {
return Err("No repositories specified for search".to_string());
}
// Create a list to store tasks (boxed futures)
let mut tasks: Vec<BoxFuture<Result<(String, Vec<RepositoryPlugin>), (String, String)>>> = Vec::new();
// Add tasks for each repository
for repo in repositories {
match repo {
RepositorySource::HangarMC => {
let crawler = crawlers::HangarCrawler::new();
let query_owned = query.to_string();
let task = async move {
match crawler.search(&query_owned).await {
Ok(repo_results) => Ok((crawler.get_repository_name(), repo_results)),
Err(e) => Err((crawler.get_repository_name(), e.to_string()))
}
}.boxed(); // Box the future
tasks.push(task);
},
RepositorySource::SpigotMC => {
let crawler = crawlers::SpigotMCCrawler::new();
let query_owned = query.to_string();
let task = async move {
match crawler.search(&query_owned).await {
Ok(repo_results) => Ok((crawler.get_repository_name(), repo_results)),
Err(e) => Err((crawler.get_repository_name(), e.to_string()))
}
}.boxed(); // Box the future
tasks.push(task);
},
RepositorySource::Modrinth => {
let crawler = crawlers::ModrinthCrawler::new();
let query_owned = query.to_string();
let task = async move {
match crawler.search(&query_owned).await {
Ok(repo_results) => Ok((crawler.get_repository_name(), repo_results)),
Err(e) => Err((crawler.get_repository_name(), e.to_string()))
}
}.boxed(); // Box the future
tasks.push(task);
},
RepositorySource::GitHub => {
let crawler = crawlers::GitHubCrawler::new();
let query_owned = query.to_string();
let task = async move {
match crawler.search(&query_owned).await {
Ok(repo_results) => Ok((crawler.get_repository_name(), repo_results)),
Err(e) => Err((crawler.get_repository_name(), e.to_string()))
}
}.boxed(); // Box the future
tasks.push(task);
},
_ => {
eprintln!("Repository {:?} not supported for search", repo);
}
}
}
// Execute all tasks concurrently
let results = futures::future::join_all(tasks).await;
// Collect successful results
let mut plugins = Vec::new();
let mut errors = Vec::new();
for result in results {
match result {
Ok((_, repo_plugins)) => {
plugins.extend(repo_plugins);
},
Err((repo, error)) => {
errors.push(format!("[{}] {}", repo, error));
}
}
}
// Check if we found any results
if plugins.is_empty() {
if !errors.is_empty() {
return Err(errors.join("; "));
} else {
return Ok(Vec::new());
}
}
Ok(plugins)
}
/// Generate search variations for a plugin name
fn generate_search_variations(plugin_name: &str) -> Vec<String> {
let mut variations = Vec::new();
// Add original name
variations.push(plugin_name.to_string());
// Convert to lowercase
let name_lower = plugin_name.to_lowercase();
if name_lower != plugin_name {
variations.push(name_lower.clone());
}
// Add variations with common prefixes/suffixes removed
let prefixes = ["plugin", "mc", "minecraft"];
let suffixes = ["plugin", "spigot", "bukkit", "paper", "mc"];
for prefix in prefixes.iter() {
let prefix_str = format!("{} ", prefix);
if name_lower.starts_with(&prefix_str) {
variations.push(name_lower[prefix_str.len()..].to_string());
}
}
for suffix in suffixes.iter() {
let suffix_str = format!(" {}", suffix);
if name_lower.ends_with(&suffix_str) {
variations.push(name_lower[0..name_lower.len() - suffix_str.len()].to_string());
}
}
// Remove duplicates
variations.sort();
variations.dedup();
variations
}
/// Search for plugin variations
pub async fn search_with_variations(plugin_name: &str, repositories: &[RepositorySource]) -> Result<Vec<RepositoryPlugin>, String> {
let variations = generate_search_variations(plugin_name);
let mut all_results = Vec::new();
for variation in variations {
match lib_search_plugins_in_repositories(&variation, repositories.to_vec()).await {
Ok(results) => {
all_results.extend(results);
},
Err(e) => {
println!("Error searching for variation '{}': {}", variation, e);
// Continue with other variations even if this one fails
}
}
}
// Remove duplicates by plugin ID and repository
all_results.sort_by(|a, b| {
let a_key = format!("{:?}:{}", a.repository, a.id);
let b_key = format!("{:?}:{}", b.repository, b.id);
a_key.cmp(&b_key)
});
all_results.dedup_by(|a, b| {
a.id == b.id && a.repository == b.repository
});
Ok(all_results)
}
/// Get plugin details from a repository
pub async fn lib_get_plugin_details_from_repository(
plugin_id: &str,
repository: RepositorySource,
server_type: Option<&ServerType>
) -> Result<RepositoryPlugin, String> {
match repository {
RepositorySource::HangarMC => {
let crawler = crawlers::HangarCrawler::new();
crawler.get_plugin_details(plugin_id).await
.map_err(|e| format!("Failed to get plugin details from HangarMC: {}", e))
},
RepositorySource::SpigotMC => {
let crawler = crawlers::SpigotMCCrawler::new();
crawler.get_plugin_details(plugin_id).await
.map_err(|e| format!("Failed to get plugin details from SpigotMC: {}", e))
},
RepositorySource::Modrinth => {
let crawler = crawlers::ModrinthCrawler::new();
// Use server type aware version if provided
if let Some(server_type) = server_type {
crawler.get_plugin_details_with_server_type(plugin_id, Some(server_type)).await
.map_err(|e| format!("Failed to get plugin details from Modrinth: {}", e))
} else {
crawler.get_plugin_details(plugin_id).await
.map_err(|e| format!("Failed to get plugin details from Modrinth: {}", e))
}
},
RepositorySource::GitHub => {
let crawler = crawlers::GitHubCrawler::new();
crawler.get_plugin_details(plugin_id).await
.map_err(|e| format!("Failed to get plugin details from GitHub: {}", e))
},
_ => Err(format!("Repository source {:?} not supported for plugin details", repository))
}
}
/// Download a plugin from a repository
pub async fn lib_download_plugin_from_repository(
plugin_id: &str,
version: &str,
repository: RepositorySource,
destination: &str,
server_type: Option<&ServerType>
) -> Result<String, String> {
match repository {
RepositorySource::HangarMC => {
let crawler = crawlers::HangarCrawler::new();
crawler.download_plugin(plugin_id, version, Path::new(destination)).await
.map_err(|e| format!("Failed to download plugin from HangarMC: {}", e))
},
RepositorySource::SpigotMC => {
let crawler = crawlers::SpigotMCCrawler::new();
crawler.download_plugin(plugin_id, version, Path::new(destination)).await
.map_err(|e| format!("Failed to download plugin from SpigotMC: {}", e))
},
RepositorySource::Modrinth => {
let crawler = crawlers::ModrinthCrawler::new();
// Use server type aware version if provided
if let Some(server_type) = server_type {
crawler.download_plugin_with_server_type(plugin_id, version, Path::new(destination), Some(server_type)).await
.map_err(|e| format!("Failed to download plugin from Modrinth: {}", e))
} else {
crawler.download_plugin(plugin_id, version, Path::new(destination)).await
.map_err(|e| format!("Failed to download plugin from Modrinth: {}", e))
}
},
RepositorySource::GitHub => {
let crawler = crawlers::GitHubCrawler::new();
crawler.download_plugin(plugin_id, version, Path::new(destination)).await
.map_err(|e| format!("Failed to download plugin from GitHub: {}", e))
},
_ => Err(format!("Repository source {:?} not supported for downloads", repository))
}
}
/// Configure and run the Tauri application
pub fn run() {
// Build the Tauri application
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.invoke_handler(tauri::generate_handler![
// Plugin discovery commands
scan_server_dir,
scan_server_dir_sync,
// Plugin repository commands
search_plugins,
get_plugin_details,
// Update commands
update_plugin,
check_plugin_updates,
check_single_plugin_update_command,
backup_plugin_command,
// Plugin management commands
download_plugin,
set_plugin_repository,
get_plugin_versions,
load_plugin_data,
save_plugin_data,
// Utility commands
get_potential_plugin_matches,
compare_versions,
is_plugin_compatible,
greet
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}