legacympt-rs/mpt/src/commands/downloadmods.rs
2021-08-29 15:14:10 +02:00

202 lines
6.3 KiB
Rust

use crate::{config::Config, util::mvn_artifact_to_url};
use addonscript::manifest::{
installer::Installer,
link::Link,
File,
FileOpt,
Manifest,
Relation,
Repository,
RepositoryType,
};
use anyhow::{bail, Context};
use crossterm::style::{Attribute, Color, Stylize};
use futures::stream::{self, StreamExt};
use indicatif::ProgressBar;
use reqwest::{Client, StatusCode};
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use tokio::{fs::File as TokioFile, io::AsyncWriteExt};
use url::Url;
pub async fn run(
(config, manifest): (Config, Manifest),
dir: PathBuf,
all: bool,
) -> anyhow::Result<()> {
let http = Arc::new(Client::new());
let Manifest {
mut versions,
repositories,
..
} = manifest;
let repositories = Arc::new(repositories);
let futs = versions
.pop()
.context("Manifest has no versions!")?
.relations
.into_iter()
.filter(|rel| {
rel.options.contains(&FileOpt::Client)
&& rel
.file
.as_ref()
.map(|f| {
matches!(f.installer(), Installer::Dir(dir) if all || dir == Path::new("mods"))
})
.unwrap_or(false)
})
.map(|rel| download_file(Arc::clone(&http), Arc::clone(&repositories), dir.clone(), all, rel))
.collect::<Vec<_>>();
let pb = ProgressBar::new(futs.len() as u64);
let mut futs = stream::iter(futs).buffer_unordered(config.downloads.max_threads as usize);
while let Some(res) = futs.next().await {
match res {
Ok(i) => match i {
DownloadInfo::Local { from, to } => pb.println(format!(
"{} {} {} {}",
"Copied local file".with(Color::Green),
from.with(Color::Cyan).attribute(Attribute::Bold),
"to".with(Color::Green),
to.with(Color::Cyan).attribute(Attribute::Bold),
)),
DownloadInfo::Http { status, from, to } => pb.println(format!(
"{} {} {} {} {} {}",
"Downloaded file".with(Color::Green),
from.with(Color::Cyan).attribute(Attribute::Bold),
"to".with(Color::Green),
to.with(Color::Cyan).attribute(Attribute::Bold),
"with status".with(Color::Green),
status
.to_string()
.with(Color::Cyan)
.attribute(Attribute::Bold),
)),
},
Err(e) => {
pb.println(format!(
"{} {:?}",
"Error downloading file:"
.with(Color::Red)
.attribute(Attribute::Bold),
e,
));
},
}
pb.inc(1);
}
Ok(())
}
enum DownloadInfo {
Local {
from: String,
to: String,
},
Http {
status: StatusCode,
from: String,
to: String,
},
}
async fn download_file(
http: Arc<Client>,
repos: Arc<Vec<Repository>>,
mut target_dir: PathBuf,
all: bool,
rel: Relation,
) -> anyhow::Result<DownloadInfo> {
let link;
let link = match &rel.file {
Some(File::Link { link, .. }) => link,
Some(File::Maven {
artifact,
repository,
..
}) => {
if let Some(repo) = repos.iter().find(|r| &r.id == repository) {
match repo.repo_type {
RepositoryType::Maven => {
link = Link::Http(mvn_artifact_to_url(&artifact, &repo)?);
&link
},
RepositoryType::Curseforge => {
let mut splits = artifact.split(':').skip(1);
let p_id = splits
.next()
.context("Couldn't parse curseforge artifact!")?;
let f_id = splits
.next()
.context("Couldn't parse curseforge artifact!")?;
let url = format!(
"https://addons-ecs.forgesvc.net/api/v2/addon/{}/file/{}/download-url",
p_id, f_id
);
link = Link::Http(
Url::parse(http.get(url).send().await?.text().await?.trim())
.context("failed to parse curseforge URL")?,
);
&link
},
}
} else {
bail!("Rel {:?} references non-existant repository!")
}
},
None => bail!("Rel {:?} has no file!", rel),
};
if let (true, Installer::Dir(d)) = (all, rel.file.as_ref().unwrap().installer()) {
target_dir.push(d);
}
tokio::fs::create_dir_all(&target_dir).await?;
match link {
Link::File(f) => {
let to = target_dir.join(f.file_name().context("File to copy is not a file!")?);
tokio::fs::copy(
f,
target_dir.join(f.file_name().context("File to copy is not a file!")?),
)
.await
.context("Failed to install file with file link.")?;
Ok(DownloadInfo::Local {
from: f.to_string_lossy().to_string(),
to: to.to_string_lossy().to_string(),
})
},
Link::Http(l) => {
let res = http.get(l.clone()).send().await?;
let p = target_dir.join(
Path::new(l.path())
.file_name()
.context("HTTP File has no file name!")?,
);
let mut file = TokioFile::create(&p).await?;
let status = res.status();
let mut stream = res.bytes_stream();
while let Some(b) = stream.next().await {
file.write_all_buf(&mut b?).await?;
}
Ok(DownloadInfo::Http {
status,
from: l.to_string(),
to: p.to_string_lossy().to_string(),
})
},
}
}