Compare commits

...

2 commits

Author SHA1 Message Date
LordMZTE 1fe7ef0f2d add debug logging
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-08 17:31:27 +02:00
LordMZTE 6d44b60182 createmodlist optimizations 2021-09-08 16:25:26 +02:00
9 changed files with 127 additions and 96 deletions

View file

@ -1,2 +1,3 @@
# 0.1.2
- optimize curseforge relation meta querying
- add debug logging

View file

@ -17,10 +17,12 @@ crossterm = "0.21.0"
futures = "0.3.16"
indicatif = "0.16.2"
json5 = "0.3.0"
log = "0.4.14"
percent-encoding = "2.1.0"
reqwest = { version = "0.11.4", features = ["stream"] }
serde = { version = "1.0.129", features = ["derive"] }
serde_json = "1.0.67"
simplelog = "0.10.0"
structopt = "0.3.22"
tera = "1.12.1"
thiserror = "1.0.28"

View file

@ -24,6 +24,7 @@ use addonscript::manifest::{
use anyhow::{bail, Context};
use async_trait::async_trait;
use indicatif::ProgressBar;
use log::{debug, info};
use reqwest::Client;
use std::{collections::HashMap, io::Write, path::Path, sync::Arc};
use tokio::io::AsyncReadExt;
@ -110,6 +111,7 @@ pub async fn run((config, mut manifest): (Config, Manifest), all: bool) -> anyho
match installer {
Installer::Dir(dir) => {
info!("copying dir installer file {}", path.to_string_lossy());
tokio::fs::copy(config.locations.src.join(path), twitch_dir.join(dir))
.await?;
},
@ -246,6 +248,7 @@ fn sort_file(
link_rels: &mut Vec<(Installer, Link)>,
cf_rels: &mut Vec<(u32, u32)>,
) -> anyhow::Result<()> {
debug!("Sorting file {:?}", &file);
match file {
File::Link {
installer, link, ..

View file

@ -3,7 +3,8 @@ use crate::{config::Config, util::CliStyle};
pub async fn run(config: Config) -> anyhow::Result<()> {
// These operations are so incredibly performance-critical that we absolutely
// must execute them in parallel!
let (res1, res2) = tokio::join!(
// we ignore the results on purpose, so nothing fails when the dirs dont exist.
let (_, _) = tokio::join!(
async {
println!(
"{}",
@ -17,8 +18,5 @@ pub async fn run(config: Config) -> anyhow::Result<()> {
}
);
res1?;
res2?;
Ok(())
}

View file

@ -9,9 +9,7 @@ use addonscript::manifest::{
RepositoryType,
};
use anyhow::Context;
use crossterm::style::Stylize;
use futures::{stream, StreamExt};
use indicatif::ProgressBar;
use log::{debug, info};
use reqwest::Client;
use serde::Serialize;
use std::{collections::HashMap, path::PathBuf, sync::Arc};
@ -21,7 +19,7 @@ use twitch::api::AddonInfoResponse;
const TEMPLATE: &str = include_str!("../../assets/modlist.html.tera");
pub async fn run(
(config, mut manifest): (Config, Manifest),
(_config, mut manifest): (Config, Manifest),
outfile: PathBuf,
) -> anyhow::Result<()> {
let version = manifest
@ -30,88 +28,71 @@ pub async fn run(
.context("Manifest has no versions!")?;
let repos = util::repo_map(manifest.repositories);
info!("instatiating tera engine");
let mut tera = Tera::default();
tera.add_raw_template("modlist", TEMPLATE)?;
let http = Arc::new(Client::new());
let len = version.relations.len();
let mut futures = stream::iter(version.relations.into_iter())
.map(|rel| get_meta(rel, &repos))
.buffer_unordered(config.downloads.max_threads);
println!("{}", "Resolving metas.".info());
let mut metas = vec![];
let pb = ProgressBar::new(len as u64)
.with_style(util::progress_style())
.with_prefix("Resolving metadata");
let mut cf_rels = vec![];
while let Some(mi) = futures.next().await {
pb.inc(1);
let mi = mi?;
for rel in version.relations {
let mi = get_meta(rel, &repos)?;
match mi {
MetaInfo::Meta(meta) => {
pb.println(format!(
"{} {}",
"Got meta for".green(),
AsRef::<str>::as_ref(&meta.name).cyan().bold()
));
metas.push(meta);
},
MetaInfo::CfId(id) => {
pb.println(format!(
"{} {}",
"Found curseforge artifact with id".green(),
id.to_string().cyan().bold()
));
cf_rels.push(id);
},
MetaInfo::Meta(meta) => metas.push(meta),
MetaInfo::CfId(id) => cf_rels.push(id),
}
}
pb.finish();
println!("{}", "Querying CF metas.".info());
if !cf_rels.is_empty() {
println!("{}", "Querying CF metas.".info());
let res = http
.post("https://addons-ecs.forgesvc.net/api/v2/addon")
.body(
serde_json::to_string(&cf_rels)
.context("Failed to serialize curseforge relation IDs")?,
)
.header("Content-Type", "application/json")
.send()
.await
.context("Failed sending CF relation request")?
.bytes()
.await
.context("Failed getting CF relation response body")?;
info!(
"Requesting addon info for {} curseforge mods",
cf_rels.len()
);
let cf_metas = serde_json::from_slice::<Vec<AddonInfoResponse>>(&res)
.context("Failed deserializing CF relation response")?;
let res = http
.post("https://addons-ecs.forgesvc.net/api/v2/addon")
.body(
serde_json::to_string(&cf_rels)
.context("Failed to serialize curseforge relation IDs")?,
)
.header("Content-Type", "application/json")
.send()
.await
.context("Failed sending CF relation request")?
.bytes()
.await
.context("Failed getting CF relation response body")?;
let cf_metas = cf_metas.into_iter().map(|m| Meta {
name: m.name,
contributors: m
.authors
.into_iter()
.map(|a| Contributor {
name: a.name,
roles: vec!["author".into()],
})
.collect(),
description: Some(m.summary),
icon_url: m
.attachments
.into_iter()
.find(|a| a.is_default)
.map(|a| a.url.to_string()),
website_url: Some(m.website_url),
});
let cf_metas = serde_json::from_slice::<Vec<AddonInfoResponse>>(&res)
.context("Failed deserializing CF relation response")?;
metas.extend(cf_metas);
info!("Converting CF metas to AS metas");
let cf_metas = cf_metas.into_iter().map(|m| Meta {
name: m.name,
contributors: m
.authors
.into_iter()
.map(|a| Contributor {
name: a.name,
roles: vec!["author".into()],
})
.collect(),
description: Some(m.summary),
icon_url: m
.attachments
.into_iter()
.find(|a| a.is_default)
.map(|a| a.url.to_string()),
website_url: Some(m.website_url),
});
metas.extend(cf_metas);
}
metas.sort_by_key(|m| m.name.to_ascii_lowercase());
@ -134,7 +115,8 @@ pub async fn run(
Ok(())
}
async fn get_meta(rel: Relation, repos: &HashMap<String, Repository>) -> anyhow::Result<MetaInfo> {
fn get_meta(rel: Relation, repos: &HashMap<String, Repository>) -> anyhow::Result<MetaInfo> {
debug!("getting meta for {:?}", &rel);
if let Some(meta) = rel.meta {
return Ok(MetaInfo::Meta(meta));
}

View file

@ -17,6 +17,7 @@ use addonscript::{
};
use anyhow::{bail, Context};
use crossterm::style::Stylize;
use log::info;
use twitch::manifest::Manifest as TwManifest;
use url::Url;
@ -38,6 +39,7 @@ pub async fn run(config: Config, infile: PathBuf) -> anyhow::Result<()> {
)
.context("Failed to parse twitch manifest")?;
info!("converting twitch mods to AS relations");
let mut relations = data
.files
.into_iter()
@ -56,6 +58,7 @@ pub async fn run(config: Config, infile: PathBuf) -> anyhow::Result<()> {
.collect::<Vec<_>>();
if let Some(ml) = data.minecraft.mod_loaders.pop() {
info!("fount modloader {:?}", &ml);
let mut splits = ml.id.split('-');
if !matches!(splits.next(), Some("forge")) {
bail!("Twitch manifest contains invalid or unknown modloader!");

View file

@ -1,6 +1,7 @@
use async_trait::async_trait;
use crossterm::style::Stylize;
use futures::{stream, StreamExt};
use log::info;
use reqwest::{Client, StatusCode};
use std::{path::PathBuf, sync::Arc};
use thiserror::Error;
@ -58,6 +59,8 @@ impl<C: Callback> Downloader<C> {
url: Url,
target: PathBuf,
) -> Result<DownloadInfo, DownloadError> {
info!("Downloading {}", &url);
if let Some(parent) = target.parent() {
tokio::fs::create_dir_all(parent).await?;
}

View file

@ -1,3 +1,6 @@
use anyhow::Context;
use log::{info, LevelFilter};
use simplelog::{ColorChoice, TermLogger, TerminalMode};
use std::path::PathBuf;
use structopt::StructOpt;
@ -9,6 +12,9 @@ mod util;
#[derive(StructOpt)]
struct Opt {
#[structopt(short, long, parse(from_occurrences), help = "enable verbose logging")]
verbose: u8,
#[structopt(subcommand)]
cmd: Command,
}
@ -69,32 +75,54 @@ enum Command {
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let Opt { cmd } = Opt::from_args();
let Opt { cmd, verbose } = Opt::from_args();
let log_level = match verbose {
0 => LevelFilter::Off,
1 => LevelFilter::Info,
2 => LevelFilter::Debug,
_ => LevelFilter::Trace,
};
TermLogger::init(
log_level,
simplelog::ConfigBuilder::new()
.set_time_to_local(true)
.set_target_level(LevelFilter::Error)
.build(),
TerminalMode::Stderr,
ColorChoice::Auto,
)
.context("Failed to init logger")?;
/// runs a given command, if the first arg is config or manifest, the
/// manifest or config file is passed to the comand respectively.
macro_rules! run_cmd {
(config: $cmd:ident $($args:expr),* $(,)?) => {
run_cmd!($cmd util::parse_config().await?, $($args),*)
};
(manifest: $cmd:ident $($args:expr),* $(,)?) => {
run_cmd!($cmd util::parse_config_and_manifest().await?, $($args),*)
};
($cmd:ident $($args:expr),* $(,)?) => {{
info!("Running command {}", stringify!($cmd));
commands::$cmd::run($($args),*).await?;
}}
}
match cmd {
Command::Init {
modpack_name,
name,
mcversion,
} => commands::init::run(modpack_name, name, mcversion).await?,
Command::DownloadMods { dir, all } => {
commands::downloadmods::run(util::parse_config_and_manifest().await?, dir, all).await?
},
Command::BuildTwitch { all } => {
commands::buildtwitch::run(util::parse_config_and_manifest().await?, all).await?
},
Command::Clean => commands::clean::run(util::parse_config().await?).await?,
Command::CreateModList { outfile } => {
commands::createmodlist::run(util::parse_config_and_manifest().await?, outfile).await?
},
Command::Import { infile } => {
commands::import::run(util::parse_config().await?, infile).await?
},
} => run_cmd!(init modpack_name, name, mcversion),
Command::DownloadMods { dir, all } => run_cmd!(manifest: downloadmods dir, all),
Command::BuildTwitch { all } => run_cmd!(manifest: buildtwitch all),
Command::Clean => run_cmd!(config: clean),
Command::CreateModList { outfile } => run_cmd!(manifest: createmodlist outfile),
Command::Import { infile } => run_cmd!(config: import infile),
}
Ok(())

View file

@ -2,6 +2,7 @@ use addonscript::manifest::{link::Link, Manifest, Repository};
use anyhow::Context;
use crossterm::style::{Attribute, Attributes, Color, ContentStyle, Stylize};
use indicatif::ProgressStyle;
use log::info;
use percent_encoding::percent_decode;
use std::{
collections::HashMap,
@ -17,6 +18,7 @@ use crate::config::Config;
/// reads and parses the config from the current working directory
pub async fn parse_config() -> anyhow::Result<Config> {
info!("reading config");
let conf = tokio::fs::read("modpacktoolsconfig.toml")
.await
.context("Failed to read config")?;
@ -36,6 +38,8 @@ pub async fn parse_config_and_manifest() -> anyhow::Result<(Config, Manifest)> {
src.join("modpack.json")
};
info!("reading manifest");
let data = tokio::fs::read(path)
.await
.context("Failed to read manifest")?;
@ -109,6 +113,7 @@ pub fn progress_style() -> ProgressStyle {
/// creates the modpacktools temporary directory (set in the config)
pub async fn make_tmp_dir(config: &Config) -> anyhow::Result<()> {
info!("creating temporary directory");
tokio::fs::create_dir_all(&config.locations.temp_dir)
.await
.context("Failed to create temporary directory")?;
@ -181,6 +186,12 @@ pub fn link_file_name(link: &Link) -> Result<String, LinkFileNameError> {
/// Copies a directory inclding all files
pub async fn copy_dir(from: PathBuf, to: PathBuf) -> io::Result<()> {
info!(
"Copying directory {} to {}",
from.to_string_lossy(),
to.to_string_lossy()
);
for file in WalkDir::new(&from) {
let file = file?;