use std::{
fs::{self, File, OpenOptions},
io,
path::Path,
};
use log::debug;
use zip::ZipArchive;
use crate::api::model::Mod;
use anyhow::{anyhow, Context, Result};
use super::{actions, config, utils, Ctx};
pub(crate) async fn init_northstar(ctx: &mut Ctx, game_path: &Path) -> Result<()> {
let version = install_northstar(ctx, game_path).await?;
ctx.config.game_path = game_path.to_path_buf();
ctx.config.nstar_version = Some(version);
ctx.config
.set_dir(game_path.join("R2Northstar").join("mods").to_str().unwrap());
println!("Set mod directory to {}", ctx.config.mod_dir().display());
config::save_config(ctx.dirs.config_dir(), &ctx.config)?;
Ok(())
}
#[cfg(feature = "launcher")]
pub fn start_northstar(ctx: &Ctx) -> Result<(), String> {
let game = ctx.config.game_path.join("NorthstarLauncher.exe");
std::process::Command::new(game)
.spawn()
.expect("Unable to start game");
Ok(())
}
pub async fn update_northstar(ctx: &mut Ctx) -> Result<()> {
if let Some(current) = &ctx.config.nstar_version {
let index = utils::update_index(ctx.config.mod_dir(), &ctx.global_target).await;
let nmod = index
.iter()
.find(|f| f.name.to_lowercase() == "northstar")
.ok_or_else(|| anyhow!("Couldn't find Northstar on thunderstore???"))?;
if nmod.version == *current {
println!("Northstar is already up to date ({})", current);
return Ok(());
}
if let Ok(s) = ctx.rl.readline(&format!(
"Update Northstar to version {}? [Y/n]",
nmod.version
)) {
if &s.to_lowercase() == "n" {
return Ok(());
}
}
do_install(ctx, nmod, &ctx.config.game_path).await?;
ctx.config.nstar_version = Some(nmod.version.clone());
config::save_config(ctx.dirs.config_dir(), &ctx.config)?;
Ok(())
} else {
println!(
"Only Northstar installations done with `papa northstar init` can be updated this way"
);
Ok(())
}
}
pub async fn install_northstar(ctx: &Ctx, game_path: &Path) -> Result<String> {
let index = utils::update_index(ctx.config.mod_dir(), &ctx.global_target).await;
let nmod = index
.iter()
.find(|f| f.name.to_lowercase() == "northstar")
.ok_or_else(|| anyhow!("Couldn't find Northstar on thunderstore???"))?;
do_install(ctx, nmod, game_path).await?;
Ok(nmod.version.clone())
}
async fn do_install(ctx: &Ctx, nmod: &Mod, game_path: &Path) -> Result<()> {
let filename = format!("northstar-{}.zip", nmod.version);
let nfile = if let Some(f) = utils::check_cache(&ctx.dirs.cache_dir().join(&filename)) {
println!("Using cached verision of Northstar@{}...", nmod.version);
f
} else {
actions::download_file(&nmod.url, ctx.dirs.cache_dir().join(&filename)).await?
};
println!("Extracting Northstar...");
extract(ctx, nfile, game_path)?;
println!("Done!");
Ok(())
}
fn extract(ctx: &Ctx, zip_file: File, target: &Path) -> Result<()> {
let mut archive = ZipArchive::new(&zip_file).context("Unable to open zip archive")?;
for i in 0..archive.len() {
let mut f = archive.by_index(i).unwrap();
if let Some(n) = f.enclosed_name() {
if ctx.config.exclude.iter().any(|e| Path::new(e) == n) {
continue;
}
} else {
return Err(anyhow!(
"Unable to read name of compressed file {}",
f.name()
));
}
if f.enclosed_name().unwrap().starts_with("Northstar") {
let out = target.join(
f.enclosed_name()
.unwrap()
.strip_prefix("Northstar")
.unwrap(),
);
if (*f.name()).ends_with('/') {
debug!("Create directory {}", f.name());
fs::create_dir_all(target.join(f.name())).context("Unable to create directory")?;
continue;
} else if let Some(p) = out.parent() {
fs::create_dir_all(&p).context("Unable to create directory")?;
}
let mut outfile = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&out)?;
debug!("Write file {}", out.display());
io::copy(&mut f, &mut outfile).context("Unable to write to file")?;
}
}
Ok(())
}