use crate::{STYLE_BYTE, TICK};
use anyhow::{anyhow, bail, Error, Result};
use colored::Colorize;
use fs_extra::{
dir::{copy as copy_dir, CopyOptions as DirCopyOptions},
file::{move_file, CopyOptions as FileCopyOptions},
};
use indicatif::ProgressBar;
use itertools::Itertools;
use libium::{mutex_ext::MutexExt, upgrade::Downloadable};
use reqwest::Client;
use size::Size;
use std::{
ffi::OsString,
fs::read_dir,
path::{Path, PathBuf},
sync::{Arc, Mutex},
time::Duration,
};
use tokio::{
fs::{copy, create_dir_all, remove_file},
sync::Semaphore,
task::JoinSet,
};
pub async fn clean(
directory: &Path,
to_download: &mut Vec<Downloadable>,
to_install: &mut Vec<(OsString, PathBuf)>,
) -> Result<()> {
let dupes = find_dupes_by_key(to_download, Downloadable::filename);
if !dupes.is_empty() {
println!(
"{}",
format!(
"Warning: {} duplicate files were found {}. Remove the mod it belongs to",
dupes.len(),
dupes
.into_iter()
.map(|i| to_download.swap_remove(i).filename())
.format(", ")
)
.yellow()
.bold()
);
}
create_dir_all(directory.join(".old")).await?;
for file in read_dir(directory)? {
let file = file?;
if file.file_type()?.is_file() {
let filename = file.file_name();
let filename = filename.to_string_lossy();
let filename = filename.as_ref();
if let Some(index) = to_download
.iter()
.position(|thing| filename == thing.filename())
{
to_download.swap_remove(index);
} else if let Some(index) = to_install.iter().position(|thing| filename == thing.0) {
to_install.swap_remove(index);
} else if filename.ends_with("part")
|| move_file(
file.path(),
directory.join(".old").join(filename),
&FileCopyOptions::new(),
)
.is_err()
{
remove_file(file.path()).await?;
}
}
}
Ok(())
}
pub fn read_overrides(directory: &Path) -> Result<Vec<(OsString, PathBuf)>> {
let mut to_install = Vec::new();
for file in read_dir(directory)? {
let file = file?;
to_install.push((file.file_name(), file.path()));
}
Ok(to_install)
}
pub async fn download(
output_dir: PathBuf,
to_download: Vec<Downloadable>,
to_install: Vec<(OsString, PathBuf)>,
) -> Result<()> {
create_dir_all(&*output_dir).await?;
let progress_bar = Arc::new(Mutex::new(
ProgressBar::new(
to_download
.iter()
.map(|downloadable| downloadable.length)
.sum(),
)
.with_style(STYLE_BYTE.clone()),
));
progress_bar
.force_lock()
.enable_steady_tick(Duration::from_millis(100));
let mut tasks = JoinSet::new();
let semaphore = Arc::new(Semaphore::new(75));
let client = Arc::new(Client::new());
let output_dir = Arc::new(output_dir);
for downloadable in to_download {
let permit = semaphore.clone().acquire_owned().await?;
let progress_bar = progress_bar.clone();
let output_dir = output_dir.clone();
let client = client.clone();
tasks.spawn(async move {
let _permit = permit;
let (length, filename) = downloadable
.download(&client, &output_dir, |additional| {
progress_bar.force_lock().inc(additional as u64);
})
.await?;
progress_bar.force_lock().println(format!(
"{} Downloaded {:7} {}",
&*TICK,
Size::from_bytes(length)
.format()
.with_base(size::Base::Base10)
.to_string(),
filename.dimmed(),
));
Ok::<(), Error>(())
});
}
while let Some(res) = tasks.join_next().await {
res??;
}
Arc::try_unwrap(progress_bar)
.map_err(|_| anyhow!("Failed to run threads to completion"))?
.into_inner()?
.finish_and_clear();
for installable in to_install {
if installable.1.is_file() {
copy(installable.1, output_dir.join(&installable.0)).await?;
} else if installable.1.is_dir() {
let mut copy_options = DirCopyOptions::new();
copy_options.overwrite = true;
copy_dir(installable.1, &*output_dir, ©_options)?;
} else {
bail!("Could not determine whether installable is file/folder")
}
println!(
"{} Installed {}",
&*TICK,
installable.0.to_string_lossy().dimmed()
);
}
Ok(())
}
fn find_dupes_by_key<T, V, F>(slice: &mut [T], key: F) -> Vec<usize>
where
V: Eq + Ord,
F: Fn(&T) -> V,
{
let mut indices = Vec::new();
if slice.len() < 2 {
return indices;
}
slice.sort_unstable_by_key(&key);
for i in 0..(slice.len() - 1) {
if key(&slice[i]) == key(&slice[i + 1]) {
indices.push(i);
}
}
indices.reverse();
indices
}