202 lines
6.3 KiB
Rust
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(),
|
|
})
|
|
},
|
|
}
|
|
}
|