A2PPR5WVZ2J45NIQCKDB37QTR2TAORVC2B2WKZQALKDW6IUNQEKAC
e.installed = installed
.mods
.iter()
.any(|f| f.package_name == e.name && f.version == e.version);
if let Ok(installed) = &installed {
e.installed = installed
.mods
.iter()
.any(|(n, f)| n == &e.name && f.version == e.version);
}
if let Ok(glob) = &glob {
e.global = glob
.mods
.iter()
.any(|(n, f)| n == &e.name && f.version == e.version);
}
pub fn get_installed(path: &Path) -> Result<LocalIndex> {
let path = path.join(".papa.ron");
if path.exists() {
let raw = fs::read_to_string(path).context("Unable to read installed packages")?;
Ok(ron::from_str(&raw)?)
} else {
if let Some(p) = path.parent() {
if !p.exists() {
fs::create_dir_all(p)?;
}
}
File::create(path)
.context("Unable to create installed package index")?
.write_all(ron::to_string(&LocalIndex::new()).unwrap().as_bytes())?;
Ok(LocalIndex::new())
}
}
#[inline]
pub fn save_installed(path: &Path, installed: &LocalIndex) -> Result<()> {
let path = path.join(".papa.ron");
save_file(&path, ron::to_string(installed).unwrap())?;
Ok(())
}
cluster: Cluster::find().unwrap_or(None),
}
cluster: Cluster::find().unwrap_or(None), //don't use `?` here so we don't crash everything if there's no cluster
// local_installed: l_mods,
// global_installed: g_mods,
})
let mut installed = if let ManageMode::Client = ctx.config.mode {
utils::get_installed(ctx.config.mod_dir())
} else if let Some(c) = ctx.cluster.as_ref() {
let mut chain = LocalIndex::new();
for e in c.members.values() {
chain.mods.extend(utils::get_installed(e)?.mods);
}
debug!("Chained clustered mods: {:#?}", chain);
Ok(chain)
let mut installed = LocalIndex::load(ctx.config.mod_dir()).ok();
let mut global = LocalIndex::load_or_create(ctx.dirs.data_local_dir());
let outdated: Vec<&model::Mod> = if let Some(installed) = &installed {
index
.iter()
.filter(|e| {
installed
.mods
.iter()
.any(|(n, i)| n.trim() == e.name.trim() && i.version.trim() != e.version.trim())
})
.collect()
Err(anyhow!("Failed to get clustered mods"))
}?;
let mut global = utils::get_installed(ctx.dirs.data_local_dir())?;
let outdated: Vec<&model::Mod> = index
.iter()
.filter(|e| {
installed.mods.iter().any(|i| {
i.package_name.trim() == e.name.trim() && i.version.trim() != e.version.trim()
})
})
.collect();
vec![]
};
//check if any link mods are being updated
let relink = installed
.linked
.clone()
.into_iter()
.filter(|e| glob_outdated.iter().any(|f| e.package_name == f.name));
if let Some(installed) = installed.as_mut() {
//check if any link mods are being updated
let relink = installed
.linked
.clone()
.into_iter()
.filter(|(e, _)| glob_outdated.iter().any(|f| e == &f.name));
for r in relink {
debug!("Relinking mod {}", r.package_name);
//Update the submod links
for p in r.mods.iter() {
//delete the current link first
let target = ctx.local_target.join(&p.name);
if target.exists() {
fs::remove_dir_all(&target)?;
for (name, r) in relink {
debug!("Relinking mod {}", name);
//Update the submod links
for p in r.mods.iter() {
//delete the current link first
let target = ctx.local_target.join(&p.name);
if target.exists() {
fs::remove_dir_all(&target)?;
}
link_dir(&p.path, &target)?;
//replace the linked mod with the new mod info
let n = global
.mods
.iter()
.find(|e| e.package_name == r.package_name)
.ok_or_else(|| anyhow!("Unable to find linked mod in global index"))?;
if !installed.linked.remove(&r) {
debug!("Didn't find old linked mod to remove");
//replace the linked mod with the new mod info
let (n, m) = global
.mods
.iter()
.find(|(e, _)| *e == &r.package_name)
.ok_or_else(|| anyhow!("Unable to find linked mod in global index"))?;
//Insert or update the mod in the linked set
installed
.linked
.entry(n.to_owned())
.and_modify(|v| *v = m.clone())
.or_insert_with(|| m.clone());
}
}
}
Ok(())
}
async fn cluster_update(ctx: &mut Ctx, yes: bool) -> Result<()> {
if let Some(c) = ctx.cluster.clone() {
println!(
"Updating server cluster{}...",
if c.name.is_some() {
format!(" {}", c.name.as_ref().unwrap())
} else {
"".to_string()
}
);
let index = utils::update_index(&ctx.local_target, &ctx.global_target).await;
//update global mods first
let mut to_relink = vec![];
let mut global_installed = LocalIndex::load(&ctx.global_target)?;
for g in index.iter().filter(|m| m.global) {
if let Some(_o) = global_installed
.mods
.iter()
.find(|(n, e)| **n == g.name && e.version != g.version)
{
to_relink.push((&g.name, g));
if !to_relink.is_empty() {
let size: i64 = to_relink.iter().map(|f| f.1.file_size).sum::<i64>();
println!("Updating global mod(s): \n");
print!("\t");
to_relink.iter().enumerate().for_each(|(i, f)| {
//Only display 5 mods per row
if i > 0 && i % 5 == 0 {
println!("\n");
print!("\t");
}
print!(" \x1b[36m{}@{}\x1b[0m ", f.0, f.1.version);
});
println!("\n");
if !yes {
if let Ok(line) = ctx.rl.readline(&format!(
"Will download ~{:.2} MB (compressed), okay? (This will overwrite any changes made to mod files) [Y/n]: ",
size as f64 / 1_048_576f64
)) {
if line.to_lowercase() == "n" {
return Ok(());
}
} else {
return Ok(());
}
}
do_update(
ctx,
&to_relink.iter().map(|(_, m)| *m).collect(),
&mut global_installed,
&ctx.global_target.clone(),
)
.await?;
}
for s in c.members.iter() {
let _name = s.0;
let path = s.1;
let _installed = LocalIndex::load(path);
}
let mut installed = utils::get_installed(ctx.config.mod_dir())?;
let valid: Vec<InstalledMod> = mod_names
let mod_names = mod_names
.into_iter()
.map(|n| n.to_lowercase())
.collect::<String>();
let installed = LocalIndex::load(ctx.config.mod_dir())?;
let valid: Vec<InstalledMod> = installed
.mods
.filter_map(|f| {
installed
.mods
.clone()
.iter()
.find(|e| e.package_name.trim().to_lowercase() == f.trim().to_lowercase())
.filter(|e| installed.mods.remove(e))
.cloned()
.filter_map(|(n, v)| {
if mod_names.contains(&n.to_lowercase()) {
Some(v.clone())
} else {
None
}
use anyhow::{anyhow, Result};
use clap::Subcommand;
use crate::{api::model::Profile, core::Ctx};
#[derive(Subcommand)]
pub enum ProfCommands {
///Create a new mod profile
Create { name: String },
///Add a mod to a profile
Add {
name: String,
///Profile to modify to. Defaults to the current profile
#[clap(long, short)]
profile: Option<String>,
},
///Remove a mod from the a profile
Remove {
name: String,
///Profile to modify. Defaults to the current profile
#[clap(long, short)]
profile: Option<String>,
},
}
pub fn profile(ctx: &mut Ctx, command: ProfCommands) -> Result<()> {
match command {
ProfCommands::Create { name } => {
Profile::get(ctx.dirs.config_dir(), &name)?;
println!("Created profile \"{}\"", name);
}
ProfCommands::Add { name, profile } => add_mod(ctx, name, profile)?,
_ => {}
}
Ok(())
}
//Add a mod to the target profile
fn add_mod(ctx: &mut Ctx, name: String, pname: Option<String>) -> Result<()> {
let mut target = if let Some(p) = pname {
Profile::get(ctx.dirs.config_dir(), &p)?
} else {
Profile::get(ctx.dirs.config_dir(), &ctx.config.profile)?
};
if let Some(m) = ctx
.local_installed
.mods
.iter()
.find(|e| e.package_name.to_lowercase() == name.to_lowercase())
{
target.mods.insert(m.clone());
} else if let Some(m) = ctx
.global_installed
.mods
.iter()
.find(|e| e.package_name.to_lowercase() == name.to_lowercase())
{
target.mods.insert(m.clone());
} else {
println!("Unable to find mod '{}'", name);
return Ok(());
}
println!("Added mod '{}' to profile {}", name, target.name);
Ok(())
}
let index = utils::update_index(target).await;
let mut installed = utils::get_installed(target)?;
//Create the target dir if it doesn't exist
if !target.exists() {
log::trace!("Creating dir {}", target.display());
fs::create_dir_all(target)?;
}
let index = utils::update_index(target, &ctx.global_target).await;
let mut installed = LocalIndex::load_or_create(target);
for i in installed.mods.clone().iter() {
installed.mods.remove(i);
let mut i = i.clone();
if i.package_name.to_lowercase() == m {
for sub in i.mods.iter_mut() {
for (_i, p) in installed.mods.iter_mut() {
if p.package_name.to_lowercase() == m {
for sub in p.mods.iter_mut() {
use crate::core::Ctx;
use anyhow::Result;
pub(crate) fn remove(ctx: &mut Ctx, name: String) -> Result<()> {
if let Some(c) = ctx.cluster.as_mut() {
if !c.members.contains_key(&name) {
println!("Couldn't find member with name '{}'", name);
return Ok(());
}
c.members.remove(&name);
println!("Removed '{}' from cluster", name);
} else {
println!("There is no cluster set up!");
}
Ok(())
}
use anyhow::Result;
use crate::core::Ctx;
pub(crate) fn list(ctx: &Ctx) -> Result<()> {
if let Some(c) = ctx.cluster.as_ref() {
c.members.iter().for_each(|(k, v)| {
println!("{} at {}", k, v.display());
});
}
Ok(())
}
for p in mods
.iter()
.map(|p| (&p.path, mods_dir.join(p.path.file_name().unwrap())))
{
if p.1.exists() {
fs::remove_dir_all(&p.1)?;
// move the mod files from the temp dir to the real dir
for p in mods.iter() {
let temp = temp_dir.join(&p.path);
let perm = mods_dir.join(&p.path);
if perm.exists() {
fs::remove_dir_all(&perm)?;
pub fn new() -> Self {
Self {
mods: HashSet::new(),
linked: HashSet::new(),
pub fn load(path: &Path) -> Result<Self> {
if path.join(".papa.ron").exists() {
let raw = fs::read_to_string(path.join(".papa.ron"))?;
let mut parsed = ron::from_str::<Self>(&raw)?;
parsed.path = Some(path.join(".papa.ron"));
Ok(parsed)
} else {
Err(anyhow::anyhow!("No such file"))
}
}
pub fn load_or_create(path: &Path) -> Self {
match Self::load(path) {
Ok(s) => s,
Err(_) => Self::create(path),
}
}
pub fn create(path: &Path) -> Self {
let mut ind = Self::default();
ind.path = Some(path.join(".papa.ron"));
ind
}
pub fn save(&self) -> Result<()> {
if let Some(p) = &self.path {
let parsed = ron::to_string(self)?;
if let Some(p) = p.parent() {
fs::create_dir_all(p)?;
}
fs::write(&p, &parsed).context("Unable to write index")
} else {
Err(anyhow::anyhow!(
"Tried to save local index but the path wasn't set"
))
}
}
}
impl Drop for LocalIndex {
fn drop(&mut self) {
if self.path.is_some() {
self.save().expect("Failed to write index to disk");
Ok(())
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Profile {
#[serde(skip)]
path: Option<PathBuf>,
pub name: String,
pub mods: HashSet<InstalledMod>,
}
#[allow(dead_code)]
impl Profile {
pub fn get(dir: &Path, name: &str) -> Result<Self> {
let fname = format!("{}.ron", name);
let path = dir.join(&fname);
let raw = if path.exists() {
fs::read_to_string(&path)?
} else {
String::new()
};
let mut p: Self = if raw.is_empty() {
Profile {
path: None,
name: name.to_owned(),
mods: HashSet::new(),
}
} else {
ron::from_str(&raw).with_context(|| format!("Failed to parse profile {}", fname))?
};
p.path = Some(path);
Ok(p)
}
@draft t:
@cargo build --release
@cargo deb
@gh release create {{t}} --notes "# Papa {{t}}" -d --title "Papa {{t}}"
@fish -c "gh release upload {{t}} target/debian/papa_(string replace 'v' '' {{t}})_amd64.deb"
@gh release upload {{t}} target/release/papa
build:
cargo build
@test *args='':
cargo run --features="cluster" -- $@