Profile mod management
This commit is contained in:
parent
8512b45e2b
commit
59b835d374
1465
Cargo.lock
generated
1465
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
103
flake.lock
generated
103
flake.lock
generated
@ -1,103 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"fenix": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"rust-analyzer-src": "rust-analyzer-src"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1655706580,
|
|
||||||
"narHash": "sha256-7DshIT1Ya5W9NAW7UdnYCHsGmXfOXJZCEHbbB/cCX7g=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "fenix",
|
|
||||||
"rev": "d895003d8e03ac2fc8ffe2aa898299cbef1a7048",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "fenix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"naersk": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1655042882,
|
|
||||||
"narHash": "sha256-9BX8Fuez5YJlN7cdPO63InoyBy7dm3VlJkkmTt6fS1A=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "naersk",
|
|
||||||
"rev": "cddffb5aa211f50c4b8750adbec0bbbdfb26bb9f",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "naersk",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1655624069,
|
|
||||||
"narHash": "sha256-7g1zwTdp35GMTERnSzZMWJ7PG3QdDE8VOX3WsnOkAtM=",
|
|
||||||
"owner": "nixos",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "0d68d7c857fe301d49cdcd56130e0beea4ecd5aa",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nixos",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"fenix": "fenix",
|
|
||||||
"naersk": "naersk",
|
|
||||||
"nixpkgs": "nixpkgs",
|
|
||||||
"utils": "utils"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rust-analyzer-src": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1655654433,
|
|
||||||
"narHash": "sha256-auHQ0XPCiaTPSn+R3Yu4J7oZ5Zq/FS5/Da1ivvdYb/Y=",
|
|
||||||
"owner": "rust-lang",
|
|
||||||
"repo": "rust-analyzer",
|
|
||||||
"rev": "427061da19723f2206fe4dcb175c9c43b9a6193d",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "rust-lang",
|
|
||||||
"ref": "nightly",
|
|
||||||
"repo": "rust-analyzer",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"utils": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1653893745,
|
|
||||||
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
||||||
72
flake.nix
72
flake.nix
@ -1,72 +0,0 @@
|
|||||||
{
|
|
||||||
description = "The official Modrinth launcher";
|
|
||||||
|
|
||||||
inputs = {
|
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
|
||||||
utils.url = "github:numtide/flake-utils";
|
|
||||||
naersk = {
|
|
||||||
url = "github:nix-community/naersk";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
fenix = {
|
|
||||||
url = "github:nix-community/fenix";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs = inputs@{self, ...}:
|
|
||||||
inputs.utils.lib.eachDefaultSystem (system: let
|
|
||||||
pkgs = import inputs.nixpkgs { inherit system; };
|
|
||||||
fenix = inputs.fenix.packages.${system};
|
|
||||||
utils = inputs.utils.lib;
|
|
||||||
|
|
||||||
toolchain = with fenix;
|
|
||||||
combine [
|
|
||||||
minimal.rustc minimal.cargo
|
|
||||||
];
|
|
||||||
|
|
||||||
naersk = inputs.naersk.lib.${system}.override {
|
|
||||||
rustc = toolchain;
|
|
||||||
cargo = toolchain;
|
|
||||||
};
|
|
||||||
|
|
||||||
deps = with pkgs; {
|
|
||||||
global = [
|
|
||||||
openssl pkg-config gcc
|
|
||||||
];
|
|
||||||
gui = [
|
|
||||||
gtk4 gdk-pixbuf atk webkitgtk dbus
|
|
||||||
];
|
|
||||||
shell = [
|
|
||||||
(with fenix; combine [toolchain default.clippy complete.rust-src rust-analyzer])
|
|
||||||
git
|
|
||||||
jdk17 jdk8
|
|
||||||
];
|
|
||||||
};
|
|
||||||
in {
|
|
||||||
packages = {
|
|
||||||
theseus-cli = naersk.buildPackage {
|
|
||||||
pname = "theseus_cli";
|
|
||||||
src = ./.;
|
|
||||||
buildInputs = deps.global;
|
|
||||||
cargoBuildOptions = x: x ++ ["-p" "theseus_cli"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
apps = {
|
|
||||||
cli = utils.mkApp {
|
|
||||||
drv = self.packages.${system}.theseus-cli;
|
|
||||||
};
|
|
||||||
cli-dev = utils.mkApp {
|
|
||||||
drv = self.packages.${system}.theseus-cli.overrideAttrs (old: old // {
|
|
||||||
release = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
devShell = pkgs.mkShell {
|
|
||||||
buildInputs = with deps;
|
|
||||||
global ++ gui ++ shell;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -11,7 +11,9 @@ bytes = "1"
|
|||||||
bincode = { version = "2.0.0-rc.1", features = ["serde"] }
|
bincode = { version = "2.0.0-rc.1", features = ["serde"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
sha1 = { version = "0.6.0", features = ["std"]}
|
toml = "0.7.3"
|
||||||
|
sha1 = { version = "0.6.1", features = ["std"]}
|
||||||
|
sha2 = "0.9.9"
|
||||||
sled = { version = "0.34.7", features = ["compression"] }
|
sled = { version = "0.34.7", features = ["compression"] }
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
uuid = { version = "1.1", features = ["serde", "v4"] }
|
uuid = { version = "1.1", features = ["serde", "v4"] }
|
||||||
@ -29,7 +31,7 @@ tracing = "0.1"
|
|||||||
tracing-error = "0.2"
|
tracing-error = "0.2"
|
||||||
|
|
||||||
|
|
||||||
async-tungstenite = { version = "0.17", features = ["tokio-runtime", "tokio-native-tls"] }
|
async-tungstenite = { version = "0.20.0", features = ["tokio-runtime", "tokio-native-tls"] }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
once_cell = "1.9.0"
|
once_cell = "1.9.0"
|
||||||
reqwest = { version = "0.11", features = ["json"] }
|
reqwest = { version = "0.11", features = ["json"] }
|
||||||
|
|||||||
@ -17,6 +17,8 @@ pub static REQWEST_CLIENT: Lazy<reqwest::Client> = Lazy::new(|| {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pub const MODRINTH_API_URL: &str = "https://api.modrinth.com/v2/";
|
||||||
|
|
||||||
pub fn sled_config() -> sled::Config {
|
pub fn sled_config() -> sled::Config {
|
||||||
sled::Config::default().use_compression(true)
|
sled::Config::default().use_compression(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -122,6 +122,12 @@ impl DirectoryInfo {
|
|||||||
self.config_dir.join("settings.json")
|
self.config_dir.join("settings.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the cache directory for Theseus
|
||||||
|
#[inline]
|
||||||
|
pub fn caches_dir(&self) -> PathBuf {
|
||||||
|
self.config_dir.join("caches")
|
||||||
|
}
|
||||||
|
|
||||||
/// Get path from environment variable
|
/// Get path from environment variable
|
||||||
#[inline]
|
#[inline]
|
||||||
fn env_path(name: &str) -> Option<PathBuf> {
|
fn env_path(name: &str) -> Option<PathBuf> {
|
||||||
|
|||||||
@ -16,6 +16,9 @@ pub use self::profiles::*;
|
|||||||
mod settings;
|
mod settings;
|
||||||
pub use self::settings::*;
|
pub use self::settings::*;
|
||||||
|
|
||||||
|
mod projects;
|
||||||
|
pub use self::projects::*;
|
||||||
|
|
||||||
mod users;
|
mod users;
|
||||||
pub use self::users::*;
|
pub use self::users::*;
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
use super::settings::{Hooks, MemorySettings, WindowSize};
|
use super::settings::{Hooks, MemorySettings, WindowSize};
|
||||||
use crate::config::BINCODE_CONFIG;
|
use crate::config::BINCODE_CONFIG;
|
||||||
|
use crate::data::DirectoryInfo;
|
||||||
|
use crate::state::projects::Project;
|
||||||
use daedalus::modded::LoaderVersion;
|
use daedalus::modded::LoaderVersion;
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -27,6 +29,7 @@ pub struct Profile {
|
|||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub metadata: ProfileMetadata,
|
pub metadata: ProfileMetadata,
|
||||||
|
pub projects: HashMap<PathBuf, Project>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub java: Option<JavaSettings>,
|
pub java: Option<JavaSettings>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@ -107,6 +110,7 @@ impl Profile {
|
|||||||
loader_version: None,
|
loader_version: None,
|
||||||
format_version: CURRENT_FORMAT_VERSION,
|
format_version: CURRENT_FORMAT_VERSION,
|
||||||
},
|
},
|
||||||
|
projects: HashMap::new(),
|
||||||
java: None,
|
java: None,
|
||||||
memory: None,
|
memory: None,
|
||||||
resolution: None,
|
resolution: None,
|
||||||
@ -200,7 +204,10 @@ impl Profile {
|
|||||||
|
|
||||||
impl Profiles {
|
impl Profiles {
|
||||||
#[tracing::instrument(skip(db))]
|
#[tracing::instrument(skip(db))]
|
||||||
pub async fn init(db: &sled::Db) -> crate::Result<Self> {
|
pub async fn init(
|
||||||
|
db: &sled::Db,
|
||||||
|
dirs: &DirectoryInfo,
|
||||||
|
) -> crate::Result<Self> {
|
||||||
let profile_db = db.get(PROFILE_SUBTREE)?.map_or(
|
let profile_db = db.get(PROFILE_SUBTREE)?.map_or(
|
||||||
Ok(Default::default()),
|
Ok(Default::default()),
|
||||||
|bytes| {
|
|bytes| {
|
||||||
@ -212,7 +219,7 @@ impl Profiles {
|
|||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let profiles = stream::iter(profile_db.iter())
|
let mut profiles = stream::iter(profile_db.iter())
|
||||||
.then(|it| async move {
|
.then(|it| async move {
|
||||||
let path = PathBuf::from(it);
|
let path = PathBuf::from(it);
|
||||||
let prof = match Self::read_profile_from_dir(&path).await {
|
let prof = match Self::read_profile_from_dir(&path).await {
|
||||||
@ -227,6 +234,15 @@ impl Profiles {
|
|||||||
.collect::<HashMap<PathBuf, Option<Profile>>>()
|
.collect::<HashMap<PathBuf, Option<Profile>>>()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
// {
|
||||||
|
// for (path, profile_opt) in profiles.iter_mut() {
|
||||||
|
// if let Some(profile) = profile_opt {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// dirs.caches_dir()
|
||||||
|
|
||||||
Ok(Self(profiles))
|
Ok(Self(profiles))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,63 +312,3 @@ impl Profiles {
|
|||||||
Ok(profile)
|
Ok(profile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn profile_test() -> Result<(), serde_json::Error> {
|
|
||||||
let profile = Profile {
|
|
||||||
path: PathBuf::new(),
|
|
||||||
metadata: ProfileMetadata {
|
|
||||||
name: String::from("Example Pack"),
|
|
||||||
icon: None,
|
|
||||||
game_version: String::from("1.18.2"),
|
|
||||||
loader: ModLoader::Vanilla,
|
|
||||||
loader_version: None,
|
|
||||||
format_version: CURRENT_FORMAT_VERSION,
|
|
||||||
},
|
|
||||||
java: Some(JavaSettings {
|
|
||||||
install: Some(PathBuf::from("/usr/bin/java")),
|
|
||||||
extra_arguments: Some(Vec::new()),
|
|
||||||
}),
|
|
||||||
memory: Some(MemorySettings {
|
|
||||||
minimum: None,
|
|
||||||
maximum: 8192,
|
|
||||||
}),
|
|
||||||
resolution: Some(WindowSize(1920, 1080)),
|
|
||||||
hooks: Some(Hooks {
|
|
||||||
pre_launch: HashSet::new(),
|
|
||||||
wrapper: None,
|
|
||||||
post_exit: HashSet::new(),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
let json = serde_json::json!({
|
|
||||||
"metadata": {
|
|
||||||
"name": "Example Pack",
|
|
||||||
"game_version": "1.18.2",
|
|
||||||
"format_version": 1u32,
|
|
||||||
"loader": "vanilla",
|
|
||||||
},
|
|
||||||
"java": {
|
|
||||||
"extra_arguments": [],
|
|
||||||
"install": "/usr/bin/java",
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"maximum": 8192u32,
|
|
||||||
},
|
|
||||||
"resolution": (1920u16, 1080u16),
|
|
||||||
"hooks": {},
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(serde_json::to_value(profile.clone())?, json.clone());
|
|
||||||
assert_str_eq!(
|
|
||||||
format!("{:?}", serde_json::from_value::<Profile>(json)?),
|
|
||||||
format!("{:?}", profile),
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
424
theseus/src/state/projects.rs
Normal file
424
theseus/src/state/projects.rs
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
//! Project management + inference
|
||||||
|
|
||||||
|
use crate::config::{MODRINTH_API_URL, REQWEST_CLIENT};
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::json;
|
||||||
|
use sha2::Digest;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
use zip::ZipArchive;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct Project {
|
||||||
|
pub sha512: String,
|
||||||
|
pub disabled: bool,
|
||||||
|
pub metadata: ProjectMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct ModrinthProject {
|
||||||
|
pub id: String,
|
||||||
|
pub slug: Option<String>,
|
||||||
|
pub project_type: String,
|
||||||
|
pub team: String,
|
||||||
|
pub title: String,
|
||||||
|
pub description: String,
|
||||||
|
pub body: String,
|
||||||
|
|
||||||
|
pub published: DateTime<Utc>,
|
||||||
|
pub updated: DateTime<Utc>,
|
||||||
|
|
||||||
|
pub client_side: String,
|
||||||
|
pub server_side: String,
|
||||||
|
|
||||||
|
pub downloads: u32,
|
||||||
|
pub followers: u32,
|
||||||
|
|
||||||
|
pub categories: Vec<String>,
|
||||||
|
pub additional_categories: Vec<String>,
|
||||||
|
pub game_versions: Vec<String>,
|
||||||
|
pub loaders: Vec<String>,
|
||||||
|
|
||||||
|
pub versions: Vec<String>,
|
||||||
|
|
||||||
|
pub icon_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
#[serde(tag = "type", rename_all = "snake_case")]
|
||||||
|
pub enum ProjectMetadata {
|
||||||
|
Modrinth(ModrinthProject),
|
||||||
|
Inferred {
|
||||||
|
title: Option<String>,
|
||||||
|
description: Option<String>,
|
||||||
|
authors: Vec<String>,
|
||||||
|
version: Option<String>,
|
||||||
|
icon: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn infer_data_from_files(
|
||||||
|
paths: Vec<PathBuf>,
|
||||||
|
cache_dir: PathBuf,
|
||||||
|
) -> crate::Result<HashMap<PathBuf, Project>> {
|
||||||
|
let mut file_path_hashes = HashMap::new();
|
||||||
|
|
||||||
|
// TODO: Make this concurrent and use progressive hashing to avoid loading each JAR in memory
|
||||||
|
for path in paths.clone() {
|
||||||
|
let mut file = tokio::fs::File::open(path.clone()).await?;
|
||||||
|
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
file.read_to_end(&mut buffer).await?;
|
||||||
|
|
||||||
|
let hash = format!("{:x}", sha2::Sha512::digest(&buffer));
|
||||||
|
file_path_hashes.insert(hash, path.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add disabled mods
|
||||||
|
// TODO: add retrying
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ModrinthVersion {
|
||||||
|
pub project_id: String,
|
||||||
|
}
|
||||||
|
let files: HashMap<String, ModrinthVersion> = REQWEST_CLIENT
|
||||||
|
.post(format!("{}version_files", MODRINTH_API_URL))
|
||||||
|
.json(&json!({
|
||||||
|
"hashes": file_path_hashes.keys().collect::<Vec<_>>(),
|
||||||
|
"algorithm": "sha512",
|
||||||
|
}))
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.json()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let projects: Vec<ModrinthProject> = REQWEST_CLIENT
|
||||||
|
.get(format!(
|
||||||
|
"{}projects?ids={}",
|
||||||
|
MODRINTH_API_URL,
|
||||||
|
serde_json::to_string(
|
||||||
|
&files
|
||||||
|
.values()
|
||||||
|
.map(|x| x.project_id.clone())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
)?
|
||||||
|
))
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.json()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut return_projects = HashMap::new();
|
||||||
|
let mut further_analyze_projects: Vec<(String, PathBuf)> = Vec::new();
|
||||||
|
|
||||||
|
for (hash, path) in file_path_hashes {
|
||||||
|
if let Some(file) = files.get(&hash) {
|
||||||
|
if let Some(project) =
|
||||||
|
projects.iter().find(|x| file.project_id == x.id)
|
||||||
|
{
|
||||||
|
return_projects.insert(
|
||||||
|
path,
|
||||||
|
Project {
|
||||||
|
sha512: hash,
|
||||||
|
disabled: false,
|
||||||
|
metadata: ProjectMetadata::Modrinth(project.clone()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
further_analyze_projects.push((hash, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (hash, path) in further_analyze_projects {
|
||||||
|
let file = File::open(path.clone())?;
|
||||||
|
|
||||||
|
// TODO: get rid of below unwrap
|
||||||
|
let mut zip = ZipArchive::new(file).unwrap();
|
||||||
|
|
||||||
|
let read_icon_from_file =
|
||||||
|
|icon_path: Option<String>| -> crate::Result<Option<PathBuf>> {
|
||||||
|
if let Some(icon_path) = icon_path {
|
||||||
|
// we have to repoen the zip twice here :(
|
||||||
|
let zip_file = File::open(path.clone())?;
|
||||||
|
if let Ok(mut zip) = ZipArchive::new(zip_file) {
|
||||||
|
if let Ok(mut file) = zip.by_name(&icon_path) {
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
if file.read_to_end(&mut bytes).is_ok() {
|
||||||
|
let extension = Path::new(&icon_path)
|
||||||
|
.extension()
|
||||||
|
.and_then(OsStr::to_str);
|
||||||
|
let hash = sha1::Sha1::from(&bytes).hexdigest();
|
||||||
|
let path = cache_dir.join("icons").join(
|
||||||
|
if let Some(ext) = extension {
|
||||||
|
format!("{hash}.{ext}")
|
||||||
|
} else {
|
||||||
|
hash
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if !path.exists() {
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file = File::create(path.clone())?;
|
||||||
|
file.write_all(&bytes)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Some(path));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(mut file) = zip.by_name("META-INF/mods.toml") {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ForgeModInfo {
|
||||||
|
pub mods: Vec<ForgeMod>,
|
||||||
|
}
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ForgeMod {
|
||||||
|
mod_id: String,
|
||||||
|
version: Option<String>,
|
||||||
|
display_name: Option<String>,
|
||||||
|
description: Option<String>,
|
||||||
|
logo_file: Option<String>,
|
||||||
|
authors: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file_str = String::new();
|
||||||
|
if file.read_to_string(&mut file_str).is_ok() {
|
||||||
|
if let Ok(pack) = serde_json::from_str::<ForgeMod>(&file_str) {
|
||||||
|
let icon = read_icon_from_file(pack.logo_file)?;
|
||||||
|
|
||||||
|
return_projects.insert(
|
||||||
|
path.clone(),
|
||||||
|
Project {
|
||||||
|
sha512: hash,
|
||||||
|
disabled: false,
|
||||||
|
metadata: ProjectMetadata::Inferred {
|
||||||
|
title: Some(
|
||||||
|
pack.display_name.unwrap_or(pack.mod_id),
|
||||||
|
),
|
||||||
|
description: pack.description,
|
||||||
|
authors: pack
|
||||||
|
.authors
|
||||||
|
.map(|x| vec![x])
|
||||||
|
.unwrap_or_default(),
|
||||||
|
version: pack.version,
|
||||||
|
icon,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(mut file) = zip.by_name("mcmod.info") {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ForgeMod {
|
||||||
|
modid: String,
|
||||||
|
name: String,
|
||||||
|
description: Option<String>,
|
||||||
|
version: Option<String>,
|
||||||
|
author_list: Option<Vec<String>>,
|
||||||
|
logo_file: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file_str = String::new();
|
||||||
|
if file.read_to_string(&mut file_str).is_ok() {
|
||||||
|
if let Ok(pack) = serde_json::from_str::<ForgeMod>(&file_str) {
|
||||||
|
let icon = read_icon_from_file(pack.logo_file)?;
|
||||||
|
|
||||||
|
return_projects.insert(
|
||||||
|
path.clone(),
|
||||||
|
Project {
|
||||||
|
sha512: hash,
|
||||||
|
disabled: false,
|
||||||
|
metadata: ProjectMetadata::Inferred {
|
||||||
|
title: Some(if pack.name.is_empty() {
|
||||||
|
pack.modid
|
||||||
|
} else {
|
||||||
|
pack.name
|
||||||
|
}),
|
||||||
|
description: pack.description,
|
||||||
|
authors: pack.author_list.unwrap_or_default(),
|
||||||
|
version: pack.version,
|
||||||
|
icon,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(mut file) = zip.by_name("fabric.mod.json") {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum FabricAuthor {
|
||||||
|
String(String),
|
||||||
|
Object { name: String },
|
||||||
|
}
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct FabricMod {
|
||||||
|
id: String,
|
||||||
|
version: String,
|
||||||
|
name: Option<String>,
|
||||||
|
description: Option<String>,
|
||||||
|
authors: Vec<FabricAuthor>,
|
||||||
|
icon: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file_str = String::new();
|
||||||
|
if file.read_to_string(&mut file_str).is_ok() {
|
||||||
|
if let Ok(pack) = serde_json::from_str::<FabricMod>(&file_str) {
|
||||||
|
let icon = read_icon_from_file(pack.icon)?;
|
||||||
|
|
||||||
|
return_projects.insert(
|
||||||
|
path.clone(),
|
||||||
|
Project {
|
||||||
|
sha512: hash,
|
||||||
|
disabled: false,
|
||||||
|
metadata: ProjectMetadata::Inferred {
|
||||||
|
title: Some(pack.name.unwrap_or(pack.id)),
|
||||||
|
description: pack.description,
|
||||||
|
authors: pack
|
||||||
|
.authors
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| match x {
|
||||||
|
FabricAuthor::String(name) => name,
|
||||||
|
FabricAuthor::Object { name } => name,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
version: Some(pack.version),
|
||||||
|
icon,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(mut file) = zip.by_name("quilt.mod.json") {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct QuiltMetadata {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub contributors: Option<HashMap<String, String>>,
|
||||||
|
pub icon: Option<String>,
|
||||||
|
}
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct QuiltMod {
|
||||||
|
id: String,
|
||||||
|
version: String,
|
||||||
|
metadata: Option<QuiltMetadata>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file_str = String::new();
|
||||||
|
if file.read_to_string(&mut file_str).is_ok() {
|
||||||
|
if let Ok(pack) = serde_json::from_str::<QuiltMod>(&file_str) {
|
||||||
|
let icon = read_icon_from_file(
|
||||||
|
pack.metadata
|
||||||
|
.as_ref()
|
||||||
|
.map(|x| x.icon.clone())
|
||||||
|
.flatten(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
return_projects.insert(
|
||||||
|
path.clone(),
|
||||||
|
Project {
|
||||||
|
sha512: hash,
|
||||||
|
disabled: false,
|
||||||
|
metadata: ProjectMetadata::Inferred {
|
||||||
|
title: Some(
|
||||||
|
pack.metadata
|
||||||
|
.as_ref()
|
||||||
|
.map(|x| x.name.clone())
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or(pack.id),
|
||||||
|
),
|
||||||
|
description: pack
|
||||||
|
.metadata
|
||||||
|
.as_ref()
|
||||||
|
.map(|x| x.description.clone())
|
||||||
|
.flatten(),
|
||||||
|
authors: pack
|
||||||
|
.metadata
|
||||||
|
.map(|x| {
|
||||||
|
x.contributors
|
||||||
|
.unwrap_or_default()
|
||||||
|
.keys()
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
version: Some(pack.version),
|
||||||
|
icon,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(mut file) = zip.by_name("pack.mcmeta") {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Pack {
|
||||||
|
description: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file_str = String::new();
|
||||||
|
if file.read_to_string(&mut file_str).is_ok() {
|
||||||
|
if let Ok(pack) = serde_json::from_str::<Pack>(&file_str) {
|
||||||
|
let icon =
|
||||||
|
read_icon_from_file(Some("pack.png".to_string()))?;
|
||||||
|
|
||||||
|
return_projects.insert(
|
||||||
|
path.clone(),
|
||||||
|
Project {
|
||||||
|
sha512: hash,
|
||||||
|
disabled: false,
|
||||||
|
metadata: ProjectMetadata::Inferred {
|
||||||
|
title: None,
|
||||||
|
description: pack.description,
|
||||||
|
authors: Vec::new(),
|
||||||
|
version: None,
|
||||||
|
icon,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return_projects.insert(
|
||||||
|
path,
|
||||||
|
Project {
|
||||||
|
sha512: hash,
|
||||||
|
disabled: false,
|
||||||
|
metadata: ProjectMetadata::Unknown,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(return_projects)
|
||||||
|
}
|
||||||
@ -1,3 +1,3 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
tauri_build::build()
|
tauri_build::build()
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user