Добавил автопоиск по modrinth api через хэш
This commit is contained in:
parent
6ef66541b1
commit
6b931bdba2
1284
Cargo.lock
generated
1284
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -12,3 +12,5 @@ md5 = "0.7"
|
|||||||
rayon = "1.10"
|
rayon = "1.10"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
|
reqwest = { version = "0.11", features = ["json", "blocking"] }
|
||||||
|
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||||
|
|||||||
102
src/main.rs
102
src/main.rs
@ -1,19 +1,24 @@
|
|||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use md5;
|
use md5;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
use reqwest::blocking::Client;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::Value;
|
||||||
use sha1::Sha1;
|
use sha1::Sha1;
|
||||||
use sha2::{Digest, Sha256, Sha512};
|
use sha2::{Digest, Sha256, Sha512};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::{self, BufReader, BufWriter, Read};
|
use std::io::{self, BufReader, BufWriter, Read};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
const MODS_DIR: &str = "mods";
|
const MODS_DIR: &str = "mods";
|
||||||
const JSON_FILE: &str = "mods-list.json";
|
const JSON_FILE: &str = "mods-list.json";
|
||||||
const BUILD_CLIENT_DIR: &str = "build_client";
|
const BUILD_CLIENT_DIR: &str = "build_client";
|
||||||
const BUILD_SERVER_DIR: &str = "build_server";
|
const BUILD_SERVER_DIR: &str = "build_server";
|
||||||
const CATEGORIES: [&str; 3] = ["client-only", "server-only", "universal"];
|
const CATEGORIES: [&str; 3] = ["client-only", "server-only", "universal"];
|
||||||
|
const MODRINTH_API: &str = "https://api.modrinth.com/v2";
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
struct ModEntry {
|
struct ModEntry {
|
||||||
@ -47,17 +52,14 @@ fn main() {
|
|||||||
let mods_dir = PathBuf::from(MODS_DIR);
|
let mods_dir = PathBuf::from(MODS_DIR);
|
||||||
let build_client_dir = PathBuf::from(BUILD_CLIENT_DIR);
|
let build_client_dir = PathBuf::from(BUILD_CLIENT_DIR);
|
||||||
let build_server_dir = PathBuf::from(BUILD_SERVER_DIR);
|
let build_server_dir = PathBuf::from(BUILD_SERVER_DIR);
|
||||||
|
|
||||||
info!("Запуск менеджера модов");
|
info!("Запуск менеджера модов");
|
||||||
println!("Выберите действие:");
|
println!("Выберите действие:");
|
||||||
println!("[1] Обновить моды");
|
println!("[1] Обновить моды");
|
||||||
println!("[2] Создать сборки");
|
println!("[2] Создать сборки");
|
||||||
println!("[3] Перевыбрать категории");
|
println!("[3] Перевыбрать категории");
|
||||||
|
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
io::stdin().read_line(&mut input).expect("Ошибка ввода");
|
io::stdin().read_line(&mut input).expect("Ошибка ввода");
|
||||||
let choice = input.trim();
|
let choice = input.trim();
|
||||||
|
|
||||||
match choice {
|
match choice {
|
||||||
"1" => update_mods(&mods_dir),
|
"1" => update_mods(&mods_dir),
|
||||||
"2" => {
|
"2" => {
|
||||||
@ -114,7 +116,6 @@ fn compute_hashes_for_file(path: &PathBuf) -> Hashes {
|
|||||||
let mut sha1 = Sha1::new();
|
let mut sha1 = Sha1::new();
|
||||||
let mut md5_ctx = md5::Context::new();
|
let mut md5_ctx = md5::Context::new();
|
||||||
let mut buffer = [0u8; 8192];
|
let mut buffer = [0u8; 8192];
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match reader.read(&mut buffer) {
|
match reader.read(&mut buffer) {
|
||||||
Ok(0) => break,
|
Ok(0) => break,
|
||||||
@ -130,7 +131,6 @@ fn compute_hashes_for_file(path: &PathBuf) -> Hashes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Hashes {
|
Hashes {
|
||||||
sha512: format!("{:x}", sha512.finalize()),
|
sha512: format!("{:x}", sha512.finalize()),
|
||||||
sha256: format!("{:x}", sha256.finalize()),
|
sha256: format!("{:x}", sha256.finalize()),
|
||||||
@ -178,6 +178,41 @@ fn get_jar_files(dir: &PathBuf) -> Vec<PathBuf> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn lookup_side(sha1: &str, sha512: &str) -> Option<&'static str> {
|
||||||
|
let client = Client::new();
|
||||||
|
for (hash, algo) in [(sha1, "sha1"), (sha512, "sha512")] {
|
||||||
|
let url = if algo == "sha512" {
|
||||||
|
format!("{}/version_file/{}?algorithm=sha512", MODRINTH_API, hash)
|
||||||
|
} else {
|
||||||
|
format!("{}/version_file/{}", MODRINTH_API, hash)
|
||||||
|
};
|
||||||
|
if let Ok(resp) = client.get(&url).send() {
|
||||||
|
if resp.status() == 200 {
|
||||||
|
if let Ok(version) = resp.json::<Value>() {
|
||||||
|
if let Some(project_id) = version["project_id"].as_str() {
|
||||||
|
if let Ok(project) = client
|
||||||
|
.get(&format!("{}/project/{}", MODRINTH_API, project_id))
|
||||||
|
.send()
|
||||||
|
.and_then(|r| r.json::<Value>())
|
||||||
|
{
|
||||||
|
let client_side = project["client_side"].as_str()?;
|
||||||
|
let server_side = project["server_side"].as_str()?;
|
||||||
|
return match (client_side, server_side) {
|
||||||
|
("required", "required") => Some("universal"),
|
||||||
|
("required", "unsupported") => Some("client-only"),
|
||||||
|
("unsupported", "required") => Some("server-only"),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thread::sleep(Duration::from_millis(200));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn process_new_mods(
|
fn process_new_mods(
|
||||||
paths: &[PathBuf],
|
paths: &[PathBuf],
|
||||||
existing_sha512: &HashSet<String>,
|
existing_sha512: &HashSet<String>,
|
||||||
@ -194,7 +229,7 @@ fn process_new_mods(
|
|||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.into_owned();
|
.into_owned();
|
||||||
info!("Новый мод: {}", name);
|
info!("Новый мод: {}", name);
|
||||||
let cat = ask_category(&name);
|
let cat = lookup_side(&hashes.sha1, &hashes.sha512).unwrap_or_else(|| ask_category(&name));
|
||||||
let entry = ModEntry {
|
let entry = ModEntry {
|
||||||
name,
|
name,
|
||||||
sha512: hashes.sha512,
|
sha512: hashes.sha512,
|
||||||
@ -246,18 +281,15 @@ fn update_mods(mods_dir: &PathBuf) {
|
|||||||
.chain(&data.universal)
|
.chain(&data.universal)
|
||||||
.map(|e| e.sha512.clone())
|
.map(|e| e.sha512.clone())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if !mods_dir.exists() {
|
if !mods_dir.exists() {
|
||||||
error!("Папка {} не найдена!", MODS_DIR);
|
error!("Папка {} не найдена!", MODS_DIR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let all_files = get_jar_files(mods_dir);
|
let all_files = get_jar_files(mods_dir);
|
||||||
let current_names: HashSet<_> = all_files
|
let current_names: HashSet<_> = all_files
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|p| p.file_name().map(|n| n.to_string_lossy().into_owned()))
|
.filter_map(|p| p.file_name().map(|n| n.to_string_lossy().into_owned()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for cat in [
|
for cat in [
|
||||||
&mut data.client_only,
|
&mut data.client_only,
|
||||||
&mut data.server_only,
|
&mut data.server_only,
|
||||||
@ -272,7 +304,6 @@ fn update_mods(mods_dir: &PathBuf) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_entries = process_new_mods(&all_files, &existing_sha512);
|
let new_entries = process_new_mods(&all_files, &existing_sha512);
|
||||||
for (entry, cat) in new_entries {
|
for (entry, cat) in new_entries {
|
||||||
match cat {
|
match cat {
|
||||||
@ -282,7 +313,6 @@ fn update_mods(mods_dir: &PathBuf) {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = save_json(&data) {
|
if let Err(e) = save_json(&data) {
|
||||||
error!("Не удалось сохранить JSON: {}", e);
|
error!("Не удалось сохранить JSON: {}", e);
|
||||||
} else {
|
} else {
|
||||||
@ -295,9 +325,7 @@ fn reassign_categories(mods_dir: &PathBuf) {
|
|||||||
error!("Папка {} не найдена!", MODS_DIR);
|
error!("Папка {} не найдена!", MODS_DIR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let all_files = get_jar_files(mods_dir);
|
let all_files = get_jar_files(mods_dir);
|
||||||
|
|
||||||
if PathBuf::from(JSON_FILE).exists() {
|
if PathBuf::from(JSON_FILE).exists() {
|
||||||
if let Err(e) = fs::remove_file(JSON_FILE) {
|
if let Err(e) = fs::remove_file(JSON_FILE) {
|
||||||
warn!("Не удалось удалить старый JSON: {}", e);
|
warn!("Не удалось удалить старый JSON: {}", e);
|
||||||
@ -305,57 +333,43 @@ fn reassign_categories(mods_dir: &PathBuf) {
|
|||||||
info!("Старый mods-list.json удалён");
|
info!("Старый mods-list.json удалён");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut data = ModList {
|
let mut data = ModList {
|
||||||
client_only: vec![],
|
client_only: vec![],
|
||||||
server_only: vec![],
|
server_only: vec![],
|
||||||
universal: vec![],
|
universal: vec![],
|
||||||
};
|
};
|
||||||
let empty_sha512: HashSet<String> = HashSet::new();
|
let empty_sha512: HashSet<String> = HashSet::new();
|
||||||
|
let file_hashes = compute_hashes(&all_files);
|
||||||
// 1. Спрашиваем категории для всех модов
|
let mut hash_map: HashMap<String, Hashes> = HashMap::new();
|
||||||
let mut temp_entries: Vec<(String, &'static str)> = vec![];
|
for (path, hashes) in &file_hashes {
|
||||||
for path in &all_files {
|
|
||||||
let name = path
|
let name = path
|
||||||
.file_name()
|
.file_name()
|
||||||
.expect("Нет имени файла")
|
.expect("Нет имени файла")
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.into_owned();
|
.into_owned();
|
||||||
let cat = ask_category(&name);
|
hash_map.insert(name, hashes.clone());
|
||||||
temp_entries.push((name, cat));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Вычисляем хэши параллельно (в конце)
|
|
||||||
let file_hashes = compute_hashes(&all_files);
|
|
||||||
let mut hash_map: HashMap<String, Hashes> = HashMap::new();
|
|
||||||
for (path, hashes) in file_hashes {
|
for (path, hashes) in file_hashes {
|
||||||
let name = path
|
let name = path
|
||||||
.file_name()
|
.file_name()
|
||||||
.expect("Нет имени файла")
|
.expect("Нет имени файла")
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.into_owned();
|
.into_owned();
|
||||||
hash_map.insert(name, hashes);
|
let cat = lookup_side(&hashes.sha1, &hashes.sha512).unwrap_or_else(|| ask_category(&name));
|
||||||
}
|
let entry = ModEntry {
|
||||||
|
name: name.clone(),
|
||||||
// 3. Собираем ModEntry
|
sha512: hashes.sha512,
|
||||||
for (name, cat) in temp_entries {
|
sha256: hashes.sha256,
|
||||||
if let Some(hashes) = hash_map.get(&name) {
|
sha1: hashes.sha1,
|
||||||
let entry = ModEntry {
|
md5: hashes.md5,
|
||||||
name: name.clone(),
|
};
|
||||||
sha512: hashes.sha512.clone(),
|
match cat {
|
||||||
sha256: hashes.sha256.clone(),
|
"client-only" => data.client_only.push(entry),
|
||||||
sha1: hashes.sha1.clone(),
|
"server-only" => data.server_only.push(entry),
|
||||||
md5: hashes.md5.clone(),
|
"universal" => data.universal.push(entry),
|
||||||
};
|
_ => {}
|
||||||
match cat {
|
|
||||||
"client-only" => data.client_only.push(entry),
|
|
||||||
"server-only" => data.server_only.push(entry),
|
|
||||||
"universal" => data.universal.push(entry),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = save_json(&data) {
|
if let Err(e) = save_json(&data) {
|
||||||
error!("Не удалось сохранить JSON: {}", e);
|
error!("Не удалось сохранить JSON: {}", e);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user