2024-08-24 13:03:27 +01:00
|
|
|
use anyhow::Result;
|
2024-09-01 11:50:23 +01:00
|
|
|
use reqwest::{header::CONTENT_DISPOSITION, Url};
|
2024-08-16 00:31:02 +01:00
|
|
|
use serde::{Deserialize, Serialize};
|
2024-09-01 11:50:23 +01:00
|
|
|
use sha1::Sha1;
|
2024-08-18 22:43:24 +01:00
|
|
|
use sha2::{Digest, Sha512};
|
2024-08-17 16:11:04 +01:00
|
|
|
use std::{
|
2024-09-01 10:53:38 +01:00
|
|
|
collections::{BTreeMap, HashSet},
|
2024-08-20 00:32:32 +01:00
|
|
|
ffi::{OsStr, OsString},
|
2024-08-20 01:20:17 +01:00
|
|
|
path::{Path, PathBuf},
|
2024-08-17 16:11:04 +01:00
|
|
|
};
|
2024-08-16 00:31:02 +01:00
|
|
|
|
2024-08-17 16:11:04 +01:00
|
|
|
use crate::{
|
|
|
|
mod_meta::{ModMeta, ModProvider},
|
|
|
|
modpack::ModpackMeta,
|
2024-09-01 11:50:23 +01:00
|
|
|
providers::{modrinth::Modrinth, DownloadSide, FileSource, PinnedMod},
|
2024-08-17 16:11:04 +01:00
|
|
|
};
|
2024-08-16 00:31:02 +01:00
|
|
|
|
|
|
|
const MODPACK_LOCK_FILENAME: &str = "modpack.lock";
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct PinnedPackMeta {
|
2024-09-01 10:53:38 +01:00
|
|
|
mods: BTreeMap<String, PinnedMod>,
|
2024-08-17 16:11:04 +01:00
|
|
|
#[serde(skip_serializing, skip_deserializing)]
|
|
|
|
modrinth: Modrinth,
|
2024-08-16 00:31:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl PinnedPackMeta {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
mods: Default::default(),
|
2024-08-17 16:11:04 +01:00
|
|
|
modrinth: Modrinth::new(),
|
2024-08-16 00:31:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-18 22:25:01 +01:00
|
|
|
/// Clears out anything not in the mods list, and then downloads anything in the mods list not present
|
2024-08-18 23:32:23 +01:00
|
|
|
pub async fn download_mods(
|
|
|
|
&self,
|
|
|
|
mods_dir: &PathBuf,
|
|
|
|
download_side: DownloadSide,
|
2024-08-24 13:03:27 +01:00
|
|
|
) -> Result<()> {
|
2024-08-18 22:25:01 +01:00
|
|
|
let files = std::fs::read_dir(mods_dir)?;
|
2024-08-20 00:32:32 +01:00
|
|
|
let mut pinned_files_cache = HashSet::new();
|
2024-08-18 22:25:01 +01:00
|
|
|
for file in files.into_iter() {
|
|
|
|
let file = file?;
|
|
|
|
if file.file_type()?.is_file() {
|
|
|
|
let filename = file.file_name();
|
2024-08-20 00:32:32 +01:00
|
|
|
if !self.file_is_pinned(&filename, download_side, &mut pinned_files_cache) {
|
2024-08-18 22:25:01 +01:00
|
|
|
println!(
|
|
|
|
"Deleting file {:#?} as it is not in the pinned mods",
|
|
|
|
filename
|
|
|
|
);
|
|
|
|
tokio::fs::remove_file(file.path()).await?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-18 23:32:23 +01:00
|
|
|
for (_, pinned_mod) in self.mods.iter().filter(|m| {
|
|
|
|
download_side == DownloadSide::Both
|
|
|
|
|| download_side == DownloadSide::Client && m.1.client_side
|
|
|
|
|| download_side == DownloadSide::Server && m.1.server_side
|
|
|
|
}) {
|
2024-08-20 00:14:45 +01:00
|
|
|
for filesource in pinned_mod.source.iter() {
|
2024-08-18 22:25:01 +01:00
|
|
|
match filesource {
|
|
|
|
crate::providers::FileSource::Download {
|
|
|
|
url,
|
2024-08-20 01:20:17 +01:00
|
|
|
sha1: _,
|
2024-08-18 22:25:01 +01:00
|
|
|
sha512,
|
|
|
|
filename,
|
|
|
|
} => {
|
|
|
|
if mods_dir.join(PathBuf::from(filename)).exists() {
|
|
|
|
println!("Found existing mod {}", filename);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
println!("Downloading {} from {}", filename, url);
|
|
|
|
let file_contents = reqwest::get(url).await?.bytes().await?;
|
2024-08-18 22:43:24 +01:00
|
|
|
let mut hasher = Sha512::new();
|
|
|
|
hasher.update(&file_contents);
|
|
|
|
let sha512_hash = format!("{:X}", hasher.finalize()).to_ascii_lowercase();
|
|
|
|
let sha512 = sha512.to_ascii_lowercase();
|
|
|
|
if sha512_hash != *sha512 {
|
|
|
|
eprintln!(
|
|
|
|
"Sha512 hash mismatch for file {}\nExpected:\n{}\nGot:\n{}",
|
|
|
|
filename, sha512, sha512_hash
|
|
|
|
);
|
2024-08-24 13:03:27 +01:00
|
|
|
anyhow::bail!(
|
2024-08-18 22:43:24 +01:00
|
|
|
"Sha512 hash mismatch for file {}\nExpected:\n{}\nGot:\n{}",
|
2024-08-24 13:03:27 +01:00
|
|
|
filename,
|
|
|
|
sha512,
|
|
|
|
sha512_hash
|
2024-08-18 22:43:24 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-08-18 22:25:01 +01:00
|
|
|
tokio::fs::write(mods_dir.join(filename), file_contents).await?;
|
2024-08-18 22:43:24 +01:00
|
|
|
}
|
2024-08-18 22:25:01 +01:00
|
|
|
crate::providers::FileSource::Local {
|
2024-08-20 01:20:17 +01:00
|
|
|
path: _,
|
|
|
|
sha1: _,
|
|
|
|
sha512: _,
|
|
|
|
filename: _,
|
2024-08-18 22:25:01 +01:00
|
|
|
} => unimplemented!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-08-20 01:20:17 +01:00
|
|
|
pub fn file_is_pinned(
|
|
|
|
&self,
|
|
|
|
file_name: &OsStr,
|
|
|
|
mod_side: DownloadSide,
|
|
|
|
cache: &mut HashSet<OsString>,
|
|
|
|
) -> bool {
|
2024-08-20 00:32:32 +01:00
|
|
|
if cache.contains(file_name) {
|
|
|
|
return true;
|
|
|
|
}
|
2024-08-18 23:32:23 +01:00
|
|
|
for (_, pinned_mod) in self.mods.iter().filter(|m| {
|
|
|
|
mod_side == DownloadSide::Both
|
|
|
|
|| mod_side == DownloadSide::Client && m.1.client_side
|
|
|
|
|| mod_side == DownloadSide::Server && m.1.server_side
|
|
|
|
}) {
|
2024-08-18 22:25:01 +01:00
|
|
|
for filesource in pinned_mod.source.iter() {
|
|
|
|
match filesource {
|
|
|
|
crate::providers::FileSource::Download {
|
2024-08-20 01:20:17 +01:00
|
|
|
url: _,
|
|
|
|
sha1: _,
|
|
|
|
sha512: _,
|
2024-08-18 22:25:01 +01:00
|
|
|
filename,
|
|
|
|
} => {
|
2024-08-20 00:32:32 +01:00
|
|
|
let pinned_filename = OsStr::new(filename);
|
|
|
|
cache.insert(pinned_filename.into());
|
|
|
|
if pinned_filename == file_name {
|
2024-08-18 22:43:24 +01:00
|
|
|
return true;
|
2024-08-18 22:25:01 +01:00
|
|
|
}
|
2024-08-18 22:43:24 +01:00
|
|
|
}
|
2024-08-18 22:25:01 +01:00
|
|
|
crate::providers::FileSource::Local {
|
2024-08-20 01:20:17 +01:00
|
|
|
path: _,
|
|
|
|
sha1: _,
|
|
|
|
sha512: _,
|
2024-08-18 22:25:01 +01:00
|
|
|
filename,
|
|
|
|
} => {
|
2024-08-20 00:32:32 +01:00
|
|
|
let pinned_filename = OsStr::new(filename);
|
|
|
|
cache.insert(pinned_filename.into());
|
|
|
|
if pinned_filename == file_name {
|
2024-08-18 22:43:24 +01:00
|
|
|
return true;
|
2024-08-18 22:25:01 +01:00
|
|
|
}
|
2024-08-18 22:43:24 +01:00
|
|
|
}
|
2024-08-18 22:25:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-18 22:43:24 +01:00
|
|
|
return false;
|
2024-08-18 22:25:01 +01:00
|
|
|
}
|
|
|
|
|
2024-08-17 21:06:31 +01:00
|
|
|
pub async fn pin_mod_and_deps(
|
2024-08-17 16:11:04 +01:00
|
|
|
&mut self,
|
|
|
|
mod_metadata: &ModMeta,
|
|
|
|
pack_metadata: &ModpackMeta,
|
2024-08-18 00:45:14 +01:00
|
|
|
ignore_transitive_versions: bool,
|
2024-08-24 13:03:27 +01:00
|
|
|
) -> Result<()> {
|
2024-08-17 21:06:31 +01:00
|
|
|
if let Some(mod_meta) = self.mods.get(&mod_metadata.name) {
|
2024-08-18 00:45:14 +01:00
|
|
|
if mod_metadata.version != "*" && mod_metadata.version == mod_meta.version {
|
2024-08-17 21:06:31 +01:00
|
|
|
// Skip already pinned mods
|
|
|
|
// TODO: Replace * with the current mod version in the modpack meta so this doesn't get called twice for the first mod created
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let mut deps =
|
|
|
|
HashSet::from_iter(self.pin_mod(mod_metadata, pack_metadata).await?.into_iter());
|
|
|
|
|
2024-08-18 00:45:14 +01:00
|
|
|
if ignore_transitive_versions {
|
|
|
|
// Ignore transitive dep versions
|
|
|
|
deps = deps.iter().map(|d| d.clone().version("*")).collect();
|
|
|
|
}
|
|
|
|
|
2024-08-17 22:34:06 +01:00
|
|
|
let pinned_version = self
|
|
|
|
.mods
|
|
|
|
.get(&mod_metadata.name)
|
|
|
|
.expect("should be in pinned mods")
|
|
|
|
.version
|
|
|
|
.clone();
|
|
|
|
|
2024-08-17 21:06:31 +01:00
|
|
|
while !deps.is_empty() {
|
|
|
|
let mut next_deps = HashSet::new();
|
|
|
|
for dep in deps.iter() {
|
|
|
|
println!(
|
|
|
|
"Adding mod {}@{} (dependency of {}@{})",
|
2024-08-17 22:34:06 +01:00
|
|
|
dep.name, dep.version, mod_metadata.name, pinned_version
|
2024-08-17 21:06:31 +01:00
|
|
|
);
|
|
|
|
next_deps.extend(self.pin_mod(dep, &pack_metadata).await?);
|
|
|
|
}
|
|
|
|
deps = next_deps;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Pin a mod version
|
|
|
|
///
|
|
|
|
/// A list of dependencies to pin is included
|
|
|
|
pub async fn pin_mod(
|
|
|
|
&mut self,
|
|
|
|
mod_metadata: &ModMeta,
|
|
|
|
pack_metadata: &ModpackMeta,
|
2024-08-24 13:03:27 +01:00
|
|
|
) -> Result<Vec<ModMeta>> {
|
2024-08-19 00:50:38 +01:00
|
|
|
if pack_metadata.forbidden_mods.contains(&mod_metadata.name) {
|
|
|
|
println!("Skipping adding forbidden mod {}...", mod_metadata.name);
|
|
|
|
return Ok(vec![]);
|
|
|
|
}
|
|
|
|
|
2024-08-17 16:11:04 +01:00
|
|
|
let mod_providers = if let Some(mod_providers) = &mod_metadata.providers {
|
|
|
|
mod_providers
|
|
|
|
} else {
|
|
|
|
&vec![]
|
|
|
|
};
|
|
|
|
let mut checked_providers: HashSet<ModProvider> = HashSet::new();
|
|
|
|
for mod_provider in mod_providers
|
|
|
|
.iter()
|
|
|
|
.chain(pack_metadata.default_providers.iter())
|
|
|
|
{
|
|
|
|
if checked_providers.contains(&mod_provider) {
|
|
|
|
// No need to repeat a check for a provider if we have already checked it
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
checked_providers.insert(mod_provider.clone());
|
|
|
|
match mod_provider {
|
|
|
|
crate::mod_meta::ModProvider::CurseForge => unimplemented!(),
|
|
|
|
crate::mod_meta::ModProvider::Modrinth => {
|
2024-08-17 21:06:31 +01:00
|
|
|
let pinned_mod = self.modrinth.resolve(&mod_metadata, pack_metadata).await;
|
|
|
|
if let Ok(pinned_mod) = pinned_mod {
|
|
|
|
self.mods
|
|
|
|
.insert(mod_metadata.name.clone(), pinned_mod.clone());
|
|
|
|
println!("Pinned {}@{}", mod_metadata.name, pinned_mod.version);
|
|
|
|
if let Some(deps) = &pinned_mod.deps {
|
|
|
|
return Ok(deps
|
|
|
|
.iter()
|
|
|
|
.filter(|d| !self.mods.contains_key(&d.name))
|
|
|
|
.cloned()
|
|
|
|
.collect());
|
|
|
|
}
|
|
|
|
return Ok(vec![]);
|
2024-08-17 21:17:59 +01:00
|
|
|
} else if let Err(e) = pinned_mod {
|
|
|
|
eprintln!(
|
|
|
|
"Failed to resolve {}@{} with provider {:#?}: {}",
|
|
|
|
mod_metadata.name, mod_metadata.version, mod_provider, e
|
|
|
|
);
|
2024-08-17 16:11:04 +01:00
|
|
|
}
|
|
|
|
}
|
2024-09-01 11:50:23 +01:00
|
|
|
crate::mod_meta::ModProvider::Raw => {
|
|
|
|
let url = mod_metadata
|
|
|
|
.download_url
|
|
|
|
.clone()
|
|
|
|
.ok_or(anyhow::format_err!(
|
|
|
|
"A download url is required to pin {}",
|
|
|
|
mod_metadata.name
|
|
|
|
))?;
|
|
|
|
let file_response = reqwest::get(&url).await?;
|
|
|
|
|
|
|
|
// TODO: Get filename from content disposition
|
|
|
|
let _content_disposition = file_response.headers().get(CONTENT_DISPOSITION);
|
|
|
|
let url_parsed = Url::parse(&url)?;
|
|
|
|
let filename = url_parsed
|
|
|
|
.path_segments()
|
|
|
|
.ok_or(anyhow::format_err!(
|
|
|
|
"Cannot get path segments from url {}",
|
|
|
|
url
|
|
|
|
))?
|
|
|
|
.last()
|
|
|
|
.ok_or(anyhow::format_err!("Cannot get filename from url {}", url))?;
|
|
|
|
|
|
|
|
let file_contents = file_response.bytes().await?;
|
|
|
|
let mut sha1_hasher = Sha1::new();
|
|
|
|
let mut sha512_hasher = Sha512::new();
|
|
|
|
sha1_hasher.update(&file_contents);
|
|
|
|
sha512_hasher.update(&file_contents);
|
|
|
|
let sha1_hash = format!("{:X}", sha1_hasher.finalize()).to_ascii_lowercase();
|
|
|
|
let sha512_hash =
|
|
|
|
format!("{:X}", sha512_hasher.finalize()).to_ascii_lowercase();
|
|
|
|
|
|
|
|
let pinned_mod = PinnedMod {
|
|
|
|
source: vec![FileSource::Download {
|
|
|
|
url: url.into(),
|
|
|
|
sha1: sha1_hash,
|
|
|
|
sha512: sha512_hash,
|
|
|
|
filename: filename.into(),
|
|
|
|
}],
|
|
|
|
version: "Unknown".into(),
|
|
|
|
deps: None,
|
|
|
|
server_side: mod_metadata.server_side.unwrap_or(true),
|
|
|
|
client_side: mod_metadata.client_side.unwrap_or(true),
|
|
|
|
};
|
|
|
|
self.mods
|
|
|
|
.insert(mod_metadata.name.clone(), pinned_mod.clone());
|
|
|
|
println!("Pinned {}@{}", mod_metadata.name, pinned_mod.version);
|
|
|
|
return Ok(vec![]);
|
|
|
|
}
|
2024-08-17 16:11:04 +01:00
|
|
|
};
|
|
|
|
}
|
2024-08-17 21:06:31 +01:00
|
|
|
|
2024-08-24 13:03:27 +01:00
|
|
|
anyhow::bail!(
|
2024-08-17 21:06:31 +01:00
|
|
|
"Failed to pin mod '{}' (providers={:#?}) with constraint {} and all its deps",
|
2024-08-24 13:03:27 +01:00
|
|
|
mod_metadata.name,
|
|
|
|
mod_metadata.providers,
|
|
|
|
mod_metadata.version
|
2024-08-17 21:06:31 +01:00
|
|
|
)
|
2024-08-16 00:31:02 +01:00
|
|
|
}
|
|
|
|
|
2024-08-17 23:38:40 +01:00
|
|
|
fn get_dependent_mods(&self, mod_name: &str) -> HashSet<String> {
|
|
|
|
let mut dependent_mods = HashSet::new();
|
|
|
|
|
|
|
|
for (pinned_mod_name, pinned_mod) in self.mods.iter() {
|
|
|
|
if let Some(deps) = &pinned_mod.deps {
|
|
|
|
for dep in deps.iter() {
|
|
|
|
if dep.name == mod_name {
|
|
|
|
dependent_mods.insert(pinned_mod_name.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dependent_mods
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove_mod(
|
|
|
|
&mut self,
|
|
|
|
mod_name: &str,
|
|
|
|
pack_metadata: &ModpackMeta,
|
2024-08-19 00:24:36 +01:00
|
|
|
force: bool,
|
2024-08-24 13:03:27 +01:00
|
|
|
) -> Result<()> {
|
2024-08-19 00:29:49 +01:00
|
|
|
if !self.mods.contains_key(mod_name) {
|
2024-08-19 00:50:38 +01:00
|
|
|
eprintln!(
|
|
|
|
"Skipping removing non-existent mod {} from modpack",
|
|
|
|
mod_name
|
|
|
|
);
|
2024-08-19 00:29:49 +01:00
|
|
|
return Ok(());
|
|
|
|
}
|
2024-08-17 23:38:40 +01:00
|
|
|
let dependent_mods = self.get_dependent_mods(mod_name);
|
|
|
|
|
|
|
|
if dependent_mods.len() > 0 {
|
2024-08-19 00:24:36 +01:00
|
|
|
if force {
|
|
|
|
println!("Forcefully removing mod {} even though it is depended on by the following mods:\n{:#?}", mod_name, dependent_mods);
|
|
|
|
} else {
|
2024-08-24 13:03:27 +01:00
|
|
|
anyhow::bail!(
|
2024-08-19 00:24:36 +01:00
|
|
|
"Cannot remove mod {}.The following mods depend on it:\n{:#?}",
|
2024-08-24 13:03:27 +01:00
|
|
|
mod_name,
|
|
|
|
dependent_mods
|
2024-08-19 00:24:36 +01:00
|
|
|
)
|
|
|
|
}
|
2024-08-17 23:38:40 +01:00
|
|
|
}
|
|
|
|
let removed_mod = self.mods.remove(mod_name);
|
|
|
|
if let Some(removed_mod) = removed_mod {
|
|
|
|
println!("Removed mod {}@{}", mod_name, removed_mod.version);
|
|
|
|
}
|
|
|
|
self.prune_mods(pack_metadata)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Remove all mods from lockfile that aren't in the pack metadata or depended on by another mod
|
2024-08-24 13:03:27 +01:00
|
|
|
fn prune_mods(&mut self, pack_metadata: &ModpackMeta) -> Result<()> {
|
2024-08-17 23:38:40 +01:00
|
|
|
let mods_to_remove: HashSet<String> = self
|
|
|
|
.mods
|
|
|
|
.keys()
|
|
|
|
.filter(|mod_name| {
|
|
|
|
!pack_metadata.mods.contains_key(*mod_name)
|
|
|
|
&& self.get_dependent_mods(mod_name).len() == 0
|
|
|
|
})
|
|
|
|
.map(|mod_name| mod_name.into())
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
for mod_name in mods_to_remove {
|
|
|
|
let removed_mod = self.mods.remove(&mod_name);
|
|
|
|
if let Some(removed_mod) = removed_mod {
|
|
|
|
println!("Pruned mod {}@{}", mod_name, removed_mod.version);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-08-18 00:45:14 +01:00
|
|
|
pub async fn init(
|
|
|
|
&mut self,
|
|
|
|
modpack_meta: &ModpackMeta,
|
|
|
|
ignore_transitive_versions: bool,
|
2024-08-24 13:03:27 +01:00
|
|
|
) -> Result<()> {
|
2024-08-17 16:11:04 +01:00
|
|
|
for mod_meta in modpack_meta.iter_mods() {
|
2024-08-18 00:45:14 +01:00
|
|
|
self.pin_mod_and_deps(mod_meta, modpack_meta, ignore_transitive_versions)
|
|
|
|
.await?;
|
2024-08-17 16:11:04 +01:00
|
|
|
}
|
|
|
|
Ok(())
|
2024-08-16 00:31:02 +01:00
|
|
|
}
|
|
|
|
|
2024-08-24 13:03:27 +01:00
|
|
|
pub fn save_to_file(&self, path: &PathBuf) -> Result<()> {
|
2024-08-16 00:31:02 +01:00
|
|
|
std::fs::write(
|
|
|
|
path,
|
|
|
|
toml::to_string(self).expect("Pinned pack meta should be serializable"),
|
|
|
|
)?;
|
2024-08-17 22:34:06 +01:00
|
|
|
// println!("Saved modpack.lock to {}", path.display());
|
2024-08-16 00:31:02 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-08-24 13:03:27 +01:00
|
|
|
pub fn save_current_dir_lock(&self) -> Result<()> {
|
2024-08-17 21:06:31 +01:00
|
|
|
self.save_to_dir(&std::env::current_dir()?)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-08-24 13:03:27 +01:00
|
|
|
pub fn save_to_dir(&self, dir: &PathBuf) -> Result<()> {
|
2024-08-17 21:06:31 +01:00
|
|
|
let modpack_lock_file_path = dir.join(PathBuf::from(MODPACK_LOCK_FILENAME));
|
2024-08-16 00:31:02 +01:00
|
|
|
self.save_to_file(&modpack_lock_file_path)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-08-18 00:45:14 +01:00
|
|
|
pub async fn load_from_directory(
|
2024-08-20 01:20:17 +01:00
|
|
|
directory: &Path,
|
2024-08-18 00:45:14 +01:00
|
|
|
ignore_transitive_versions: bool,
|
2024-08-24 13:03:27 +01:00
|
|
|
) -> Result<Self> {
|
2024-08-20 01:20:17 +01:00
|
|
|
let modpack_lock_file_path = directory.join(PathBuf::from(MODPACK_LOCK_FILENAME));
|
2024-08-16 00:31:02 +01:00
|
|
|
if !modpack_lock_file_path.exists() {
|
|
|
|
let mut new_modpack_lock = Self::new();
|
2024-08-17 16:11:04 +01:00
|
|
|
new_modpack_lock
|
2024-08-18 00:45:14 +01:00
|
|
|
.init(
|
|
|
|
&ModpackMeta::load_from_directory(directory)?,
|
|
|
|
ignore_transitive_versions,
|
|
|
|
)
|
2024-08-17 16:11:04 +01:00
|
|
|
.await?;
|
2024-08-16 00:31:02 +01:00
|
|
|
return Ok(new_modpack_lock);
|
|
|
|
};
|
|
|
|
let modpack_lock_contents = std::fs::read_to_string(modpack_lock_file_path)?;
|
|
|
|
Ok(toml::from_str(&modpack_lock_contents)?)
|
|
|
|
}
|
|
|
|
|
2024-09-01 11:50:23 +01:00
|
|
|
pub async fn load_from_current_directory(ignore_transitive_versions: bool) -> Result<Self> {
|
2024-08-18 00:45:14 +01:00
|
|
|
Self::load_from_directory(&std::env::current_dir()?, ignore_transitive_versions).await
|
2024-08-16 00:31:02 +01:00
|
|
|
}
|
2024-08-20 01:20:17 +01:00
|
|
|
|
2024-08-20 01:34:22 +01:00
|
|
|
/// Load a pack from a git repo cloned to a temporary directory
|
2024-08-20 01:20:17 +01:00
|
|
|
pub async fn load_from_git_repo(
|
|
|
|
git_url: &str,
|
|
|
|
ignore_transitive_versions: bool,
|
2024-08-24 13:03:27 +01:00
|
|
|
) -> Result<(Self, tempfile::TempDir)> {
|
2024-08-20 01:20:17 +01:00
|
|
|
let pack_dir = tempfile::tempdir()?;
|
|
|
|
println!(
|
|
|
|
"Cloning modpack from git repo {} to {:#?}...",
|
|
|
|
git_url,
|
|
|
|
pack_dir.path()
|
|
|
|
);
|
|
|
|
let _repo = git2::Repository::clone(git_url, pack_dir.path())?;
|
|
|
|
|
|
|
|
let modpack_meta = ModpackMeta::load_from_directory(pack_dir.path())?;
|
|
|
|
let pinned_pack_meta =
|
|
|
|
PinnedPackMeta::load_from_directory(pack_dir.path(), ignore_transitive_versions)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
println!(
|
|
|
|
"Loaded modpack '{}' (MC {} - {}) from git",
|
|
|
|
modpack_meta.pack_name,
|
|
|
|
modpack_meta.mc_version,
|
|
|
|
modpack_meta.modloader.to_string()
|
|
|
|
);
|
|
|
|
|
2024-08-20 01:34:22 +01:00
|
|
|
Ok((pinned_pack_meta, pack_dir))
|
2024-08-20 01:20:17 +01:00
|
|
|
}
|
2024-08-16 00:31:02 +01:00
|
|
|
}
|