Add initial WIP modpack lock file

This commit is contained in:
Warren Hood 2024-08-16 01:31:02 +02:00
parent b360901285
commit 3a91def282
6 changed files with 1371 additions and 98 deletions

1175
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -5,5 +5,8 @@ edition = "2021"
[dependencies] [dependencies]
clap = { version = "4.5.15", features = ["derive"] } clap = { version = "4.5.15", features = ["derive"] }
ferinth = "2.11.0"
lhash = { version = "1.1.0", features = ["sha1", "sha512"] }
serde = { version = "1.0.207", features = ["derive"] } serde = { version = "1.0.207", features = ["derive"] }
tokio = "1.39.2"
toml = "0.8.19" toml = "0.8.19"

View file

@ -1,7 +1,10 @@
mod modpack; mod modpack;
mod mod_meta;
mod resolver;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use modpack::{ModLoader, ModMeta, ModProvider, ModpackMeta}; use mod_meta::{ModMeta, ModProvider};
use modpack::ModpackMeta;
use std::{error::Error, path::PathBuf}; use std::{error::Error, path::PathBuf};
/// A Minecraft Modpack Manager /// A Minecraft Modpack Manager
@ -128,8 +131,12 @@ fn main() -> Result<(), Box<dyn Error>> {
for provider in providers.into_iter() { for provider in providers.into_iter() {
mod_meta = mod_meta.provider(provider); mod_meta = mod_meta.provider(provider);
} }
modpack_meta = modpack_meta.add_mod(mod_meta); modpack_meta = modpack_meta.add_mod(&mod_meta);
modpack_meta.save_current_dir_project()?; modpack_meta.save_current_dir_project()?;
let mut modpack_lock = resolver::PinnedPackMeta::load_from_current_directory()?;
modpack_lock.pin_mod(&mod_meta);
modpack_lock.save_current_dir_lock()?;
} }
} }
}; };

86
src/mod_meta.rs Normal file
View file

@ -0,0 +1,86 @@
use std::{borrow::BorrowMut, error::Error};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub enum ModProvider {
/// Get mods from CurseForge
CurseForge,
/// Get mods from Modrinth
Modrinth,
/// Get mods from anywhere on the internet. Note: A download url is needed for this
Raw,
}
impl std::str::FromStr for ModProvider {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"curseforge" => Ok(ModProvider::CurseForge),
"modrinth" => Ok(ModProvider::Modrinth),
"raw" => Ok(ModProvider::Raw),
_ => Err(format!("Invalid mod launcher: {}", s)),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ModMeta {
pub name: String,
pub version: String,
providers: Option<Vec<ModProvider>>,
download_url: Option<String>,
}
impl ModMeta {
pub fn new(mod_name: &str) -> Result<Self, Box<dyn Error>> {
if mod_name.contains("@") {
let mod_name_and_version: Vec<&str> = mod_name.split("@").collect();
if mod_name_and_version.len() != 2 {
return Err(format!("Invalid mod with version constraint: '{}'", &mod_name).into());
}
return Ok(Self {
name: mod_name_and_version[0].into(),
version: mod_name_and_version[1].into(),
..Default::default()
});
}
Ok(Self {
name: mod_name.into(),
..Default::default()
})
}
pub fn provider(mut self, provider: ModProvider) -> Self {
if let Some(providers) = self.providers.borrow_mut() {
if !providers.contains(&provider) {
providers.push(provider)
}
} else {
self.providers = Some(vec![provider]);
}
self
}
pub fn url(mut self, download_url: &str) -> Self {
self.download_url = Some(download_url.into());
self
}
pub fn version(mut self, version_constraint: &str) -> Self {
self.version = version_constraint.into();
self
}
}
impl Default for ModMeta {
fn default() -> Self {
Self {
name: Default::default(),
version: "*".into(),
providers: None,
download_url: Default::default(),
}
}
}

View file

@ -1,92 +1,12 @@
use std::{borrow::BorrowMut, collections::HashMap, error::Error, path::PathBuf}; use std::{collections::HashMap, error::Error, path::PathBuf};
use ferinth::Ferinth;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::mod_meta::{ModMeta, ModProvider};
const MODPACK_FILENAME: &str = "modpack.toml"; const MODPACK_FILENAME: &str = "modpack.toml";
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub enum ModProvider {
/// Get mods from CurseForge
CurseForge,
/// Get mods from Modrinth
Modrinth,
/// Get mods from anywhere on the internet. Note: A download url is needed for this
Raw,
}
impl std::str::FromStr for ModProvider {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"curseforge" => Ok(ModProvider::CurseForge),
"modrinth" => Ok(ModProvider::Modrinth),
"raw" => Ok(ModProvider::Raw),
_ => Err(format!("Invalid mod launcher: {}", s)),
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct ModMeta {
name: String,
version: String,
providers: Option<Vec<ModProvider>>,
download_url: Option<String>,
}
impl ModMeta {
pub fn new(mod_name: &str) -> Result<Self, Box<dyn Error>> {
if mod_name.contains("@") {
let mod_name_and_version: Vec<&str> = mod_name.split("@").collect();
if mod_name_and_version.len() != 2 {
return Err(format!("Invalid mod with version constraint: '{}'", &mod_name).into());
}
return Ok(Self {
name: mod_name_and_version[0].into(),
version: mod_name_and_version[1].into(),
..Default::default()
});
}
Ok(Self {
name: mod_name.into(),
..Default::default()
})
}
pub fn provider(mut self, provider: ModProvider) -> Self {
if let Some(providers) = self.providers.borrow_mut() {
if !providers.contains(&provider) {
providers.push(provider)
}
} else {
self.providers = Some(vec![provider]);
}
self
}
pub fn url(mut self, download_url: &str) -> Self {
self.download_url = Some(download_url.into());
self
}
pub fn version(mut self, version_constraint: &str) -> Self {
self.version = version_constraint.into();
self
}
}
impl Default for ModMeta {
fn default() -> Self {
Self {
name: Default::default(),
version: "*".into(),
providers: None,
download_url: Default::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ModLoader { pub enum ModLoader {
Forge, Forge,
@ -134,6 +54,10 @@ impl ModpackMeta {
} }
} }
pub fn iter_mods(&self) -> std::collections::hash_map::Values<String, ModMeta> {
self.mods.values().into_iter()
}
pub fn load_from_directory(directory: &PathBuf) -> Result<Self, Box<dyn Error>> { pub fn load_from_directory(directory: &PathBuf) -> Result<Self, Box<dyn Error>> {
let modpack_meta_file_path = directory.clone().join(PathBuf::from(MODPACK_FILENAME)); let modpack_meta_file_path = directory.clone().join(PathBuf::from(MODPACK_FILENAME));
if !modpack_meta_file_path.exists() { if !modpack_meta_file_path.exists() {
@ -158,7 +82,7 @@ impl ModpackMeta {
self self
} }
pub fn add_mod(mut self, mod_meta: ModMeta) -> Self { pub fn add_mod(mut self, mod_meta: &ModMeta) -> Self {
if let Some(old_mod_meta) = self.mods.get(&mod_meta.name) { if let Some(old_mod_meta) = self.mods.get(&mod_meta.name) {
println!("Updating {} version {}->{}", mod_meta.name, old_mod_meta.version, mod_meta.version); println!("Updating {} version {}->{}", mod_meta.name, old_mod_meta.version, mod_meta.version);
} }
@ -168,7 +92,7 @@ impl ModpackMeta {
mod_meta.name, mod_meta.version, self.pack_name mod_meta.name, mod_meta.version, self.pack_name
); );
} }
self.mods.insert(mod_meta.name.to_string(), mod_meta); self.mods.insert(mod_meta.name.to_string(), mod_meta.clone());
self self
} }

98
src/resolver.rs Normal file
View file

@ -0,0 +1,98 @@
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, error::Error, path::PathBuf};
use crate::{mod_meta::ModMeta, modpack::ModpackMeta};
const MODPACK_LOCK_FILENAME: &str = "modpack.lock";
#[derive(Serialize, Deserialize)]
enum FileSource {
Download { url: String },
Local { path: PathBuf },
}
#[derive(Serialize, Deserialize)]
struct PinnedMod {
/// Source of the file
source: FileSource,
/// SHA1 Hash
sha1: String,
/// SHA512 Hash
sha512: String,
/// Version of mod
version: String,
}
impl PinnedMod {
pub fn resolve_mod(mod_metadata: &ModMeta) -> Self {
// TODO: Actually implement this
Self {
source: FileSource::Download {
url: format!("https://fake.url/mods/{}", mod_metadata.name),
},
sha1: "FakeSha1".into(),
sha512: "FakeSha512".into(),
version: if mod_metadata.version == "*" {
"1.0.0-fake-latest-version".into()
} else {
mod_metadata.version.clone()
},
}
}
}
#[derive(Serialize, Deserialize)]
pub struct PinnedPackMeta {
mods: HashMap<String, PinnedMod>,
}
impl PinnedPackMeta {
pub fn new() -> Self {
Self {
mods: Default::default(),
}
}
pub fn pin_mod(&mut self, mod_metadata: &ModMeta) -> &mut Self {
let pinned_mod = PinnedMod::resolve_mod(mod_metadata);
self.mods.insert(mod_metadata.name.clone(), pinned_mod);
self
}
pub fn init(&mut self, modpack_meta: &ModpackMeta) {
modpack_meta.iter_mods().for_each(|m| {
self.pin_mod(m);
});
}
pub fn save_to_file(&self, path: &PathBuf) -> Result<(), Box<dyn Error>> {
std::fs::write(
path,
toml::to_string(self).expect("Pinned pack meta should be serializable"),
)?;
println!("Saved modpack.lock to {}", path.display());
Ok(())
}
pub fn save_current_dir_lock(&self) -> Result<(), Box<dyn Error>> {
let modpack_lock_file_path =
std::env::current_dir()?.join(PathBuf::from(MODPACK_LOCK_FILENAME));
self.save_to_file(&modpack_lock_file_path)?;
Ok(())
}
pub fn load_from_directory(directory: &PathBuf) -> Result<Self, Box<dyn Error>> {
let modpack_lock_file_path = directory.clone().join(PathBuf::from(MODPACK_LOCK_FILENAME));
if !modpack_lock_file_path.exists() {
let mut new_modpack_lock = Self::new();
new_modpack_lock.init(&ModpackMeta::load_from_directory(directory)?);
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)?)
}
pub fn load_from_current_directory() -> Result<Self, Box<dyn Error>> {
Self::load_from_directory(&std::env::current_dir()?)
}
}