diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cli/install.rs | 8 | ||||
-rw-r--r-- | src/cli/mod.rs | 2 | ||||
-rw-r--r-- | src/cli/updates.rs | 39 | ||||
-rw-r--r-- | src/errors.rs | 18 | ||||
-rw-r--r-- | src/main.rs | 83 | ||||
-rw-r--r-- | src/util.rs | 137 |
6 files changed, 287 insertions, 0 deletions
diff --git a/src/cli/install.rs b/src/cli/install.rs new file mode 100644 index 0000000..a8ec5eb --- /dev/null +++ b/src/cli/install.rs @@ -0,0 +1,8 @@ +use crate::errors::CliError; + + + +pub fn install() -> Result<(), CliError> { + + Ok(()) +}
\ No newline at end of file diff --git a/src/cli/mod.rs b/src/cli/mod.rs new file mode 100644 index 0000000..04f7bac --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1,2 @@ +pub mod updates; +pub mod install;
\ No newline at end of file diff --git a/src/cli/updates.rs b/src/cli/updates.rs new file mode 100644 index 0000000..3e0bc5d --- /dev/null +++ b/src/cli/updates.rs @@ -0,0 +1,39 @@ +use thirtyfour::{DesiredCapabilities, WebDriver}; +use tracing::{info, error}; +use std::{path::Path, process::exit}; + +use toml::Value; +use crate::{errors::CliError, util::{PackConfig, read_pack_lock, self}}; + +pub async fn check_updates(cfg: PackConfig) -> Result<(), CliError> { + info!("Checking for pack updates for {} ({})", cfg.pack.name,cfg.pack.game_version); + let mut old_urls: Vec<String> = Vec::new(); + if Path::new("./pack.lock").exists() { + let lock = read_pack_lock()?; + + for url in lock.mod_versions.keys() { + old_urls.push((&lock.mod_versions.get(url).unwrap_or(&Value::String("none".to_string()))).to_string()); + } + } else { + error!("No pack.lock found, can't check for updates to something that doesn't exist!"); + exit(-1); + } + + let mut num_updates: u32 = 0; + + let caps = DesiredCapabilities::firefox(); + let driver = WebDriver::new("http://localhost:9515", caps).await; + + for url in old_urls { + info!("Checking for updates to {}", url); + } + Ok(()) +} + + +pub async fn apply_updates(cfg: PackConfig) -> Result<(), CliError> { + info!("Fetching new mod versions and updating pack.lock..."); + let mod_urls = util::find_updated_urls(cfg.mods.values(), cfg.pack.game_version).await?; + + Ok(()) +}
\ No newline at end of file diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..54b6f88 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,18 @@ +use std::num::ParseIntError; + +use thirtyfour::prelude::WebDriverError; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum CliError { + #[error("Command Error: {0}")] + Cli(String), + #[error("I/O Error: {0}")] + IO(#[from] std::io::Error), + #[error("TOML Error: {0}")] + TomlDe(#[from] toml::de::Error), + #[error("WebDriver Error: {0}")] + WebDriver(#[from] WebDriverError), + #[error("Integer Parsing Error: {0}")] + ParseInt(#[from] ParseIntError) +}
\ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0898daf --- /dev/null +++ b/src/main.rs @@ -0,0 +1,83 @@ +/*! + * Thinking about how best to design this... + * + * I want to do something different from the original modpackman, mostly in the realm of "i want to make it easier and more portable" (hence rust). + * + * ## Interface + * I want this to be a tad easier to use. There should be one binary to run that does both dependency fixing for modpacks and installation. That binary should have the following subcommands + * - apply_updates + * - check_updates + * - install + * + * All of these commands should assume that the CWD contains a valid pack.toml and that the pack is valid. + * + * I'm not sure how packaging this is going to work but it should be fun! + */ + + + +use std::process::exit; + +use clap::{Parser, Subcommand}; +mod errors; +mod cli; +mod util; + +use cli::{updates::check_updates, install}; +use errors::CliError; +use util::{PackConfig, read_pack_config}; +use tracing_subscriber::prelude::*; + +#[derive(Parser)] +#[command(author, version, about)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand, Clone, Copy)] +enum Commands { + ApplyUpdates { }, + CheckUpdates { }, + /// Installs CWD's pack definition into the local .minecraft folder + Install { } +} + +#[tokio::main] +async fn main() { + color_eyre::install().unwrap(); + tracing_subscriber::registry() + .with(tracing_subscriber::EnvFilter::new( + std::env::var("RUST_LOG").unwrap_or_else(|_| "modpackman_ng=debug,hyper=debug".into()), + )) + .with(tracing_subscriber::fmt::layer()) + .init(); + let cli = Cli::parse(); + + let cfg: PackConfig = match read_pack_config() { + Ok(c) => c, + Err(e) => { + println!("Error reading config: {:?}", e); + exit(-1); + } + }; + + let res: Result<(), CliError> = match &cli.command { + Commands::ApplyUpdates { } => { + cli::updates::apply_updates(cfg).await + }, + Commands::CheckUpdates { } => { + check_updates(cfg).await + }, + Commands::Install { } => { + install::install() + } + }; + + match res { + Err(e) => { + println!("{:?}", e); + }, + Ok(()) => { }, + } +}
\ No newline at end of file diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..4e9f5a0 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,137 @@ +use std::fs; + +use serde::{Deserialize, Serialize}; +use thirtyfour::{DesiredCapabilities, WebDriver, By}; +use toml::Table; +use tracing::{info, debug}; + +use crate::errors::CliError; + + +#[derive(Deserialize, Serialize)] +pub struct PackConfig { + pub pack: Pack, + pub mods: Table, +} + +#[derive(Deserialize, Serialize)] +pub struct Pack { + pub name: String, + pub pack_base_url: String, + pub forge_url: String, + pub game_version: String, + pub java_args: String, +} + +#[derive(Deserialize, Serialize)] +pub struct PackLock { + pub global: LockGlobal, + pub mod_versions: Table, +} + +#[derive(Deserialize, Serialize)] +pub struct LockGlobal { + pub pack_version: u16, +} + +pub fn read_pack_config() -> Result<PackConfig, CliError> { + let contents = fs::read_to_string("./pack.toml")?; + + let res: PackConfig = toml::from_str(&contents)?; + + Ok(res) +} + +pub fn read_pack_lock() -> Result<PackLock, CliError> { + let contents = fs::read_to_string("./pack.lock")?; + + let res = toml::from_str(&contents)?; + + Ok(res) +} + +pub async fn find_updated_urls(urls: toml::map::Values<'_>, game_version: String) -> Result<Vec<String>, CliError> { + let res: Vec<String> = Vec::new(); + debug!("Attempting to find updated URLs"); + debug!("Attempting to open webdriver"); + let caps = DesiredCapabilities::firefox(); + let driver = WebDriver::new("http://localhost:9515", caps).await?; + driver.close_window().await?; + debug!("Closed test window"); + + for url in urls { + find_url(url.to_string(), &game_version).await?; + } + + Ok(res) +} + +async fn find_url(homepage_url: String, game_version: &String) -> Result<String, CliError> { + if homepage_url.contains("curseforge") { + let caps = DesiredCapabilities::firefox(); + let driver = WebDriver::new("http://localhost:9515", caps).await?; + + find_cdn(&driver, homepage_url, game_version).await?; + driver.close_window().await?; + } else { + + } + + Ok(String::new()) +} + + +#[derive(Debug)] +struct RowInfo { + pub release_type: String, + pub filename: String, + pub cdn_id: String, + pub game_version: (u16, u16, u16) +} + +/** + * Ugh... + */ +async fn find_cdn(driver: &WebDriver, homepage_url: String, game_version: &String) -> Result<String, CliError> { + let mut page_index = 0; + let best_row: RowInfo; + loop { + let mut url = homepage_url.clone(); + url.push_str(&format!("/files/all?page={}", page_index)); + driver.goto(&url).await?; + + let mod_vers = driver.find(By::ClassName("listing")).await?.find_all(By::XPath("tbody/tr")).await?; + + let mut rows: Vec<RowInfo> = Vec::new(); + for entry in mod_vers { + let entry_cells = entry.find_all(By::Tag("td")).await?; + let release_type = entry_cells[0].text().await?; + let tmp0 = &entry_cells[1].find_all(By::Tag("a")).await?[0].text().await?; + let initial_filename = urlencoding::encode(tmp0); + + let tmp = entry_cells[4].find(By::ClassName("mr-2")).await?.text().await?; + let tmp2: Vec<&str> = tmp.split(".").collect(); + let version = (tmp2[0].clone().parse::<u16>()?, tmp2[1].clone().parse::<u16>()?, tmp2[2].parse::<u16>()?); + + let tmp3 = entry_cells[1].find(By::Tag("a")).await?.prop("href").await?.unwrap(); + let tmp4: Vec<&str> = tmp3.split("/").collect(); + let cdn_id = *tmp4.last().unwrap(); + + if !(initial_filename.to_lowercase().contains("fabric")) || (initial_filename.to_lowercase().contains("forge")) { + rows.push(RowInfo { + release_type, + cdn_id: cdn_id.to_string(), + game_version: version, + filename: initial_filename.to_string() + }) + } + } + rows.sort_by_key(|k| k.game_version); + + let tuple_game_version_tmp: Vec<&str> = game_version.split(".").collect(); + let tuple_game_version = (tuple_game_version_tmp[0].parse::<u16>()?, tuple_game_version_tmp[1].parse::<u16>()?, tuple_game_version_tmp[2].parse::<u16>()?); + let best_rows: Vec<&RowInfo> = rows.iter().filter(|x| x.game_version > tuple_game_version).collect(); + + info!("{:?}", best_rows); + } +}
\ No newline at end of file |