mirror of
https://github.com/WarrenHood/MCModpackManager.git
synced 2025-04-29 22:44:59 +01:00
Add initial WIP modpack lock file
This commit is contained in:
parent
b360901285
commit
3a91def282
1175
Cargo.lock
generated
1175
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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"
|
||||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -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
86
src/mod_meta.rs
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
98
src/resolver.rs
Normal 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()?)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue