1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
use crate::error::{Result, LateJavaCoreError};
use crate::utils::get_file_hash;
use std::path::Path;
/// Item de bundle
#[derive(Debug, Clone)]
pub struct BundleItem {
pub r#type: Option<String>,
pub path: String,
pub folder: Option<String>,
pub content: Option<String>,
pub sha1: Option<String>,
pub size: Option<u64>,
pub url: Option<String>,
}
/// Opciones de MinecraftBundle
#[derive(Debug, Clone)]
pub struct MinecraftBundleOptions {
pub path: String,
pub instance: Option<String>,
pub ignored: Vec<String>,
}
/// Clase para manejar bundles de Minecraft
pub struct MinecraftBundle {
options: MinecraftBundleOptions,
}
impl MinecraftBundle {
pub fn new(options: MinecraftBundleOptions) -> Self {
Self { options }
}
/// Verificar cada item en el bundle para ver si necesita ser descargado o actualizado
pub async fn check_bundle(&self, bundle: &mut [BundleItem]) -> Result<Vec<BundleItem>> {
let mut to_download = Vec::new();
for file in bundle.iter_mut() {
if file.path.is_empty() {
continue;
}
// Convertir ruta a absoluta, formato consistente
file.path = Path::new(&self.options.path)
.join(&file.path)
.to_string_lossy()
.replace('\\', "/");
file.folder = file.path.split('/')
.take_while(|part| *part != "")
.collect::<Vec<_>>()
.join("/");
// Si es un archivo de contenido directo (CFILE), crear/escribir el contenido inmediatamente
if file.r#type.as_ref().map(|t| t == "CFILE").unwrap_or(false) {
if let Some(folder) = &file.folder {
std::fs::create_dir_all(folder)?;
}
std::fs::write(&file.path, file.content.as_deref().unwrap_or(""))?;
continue;
}
// Si el archivo se supone que tiene un cierto hash, verificarlo
if Path::new(&file.path).exists() {
// Construir el prefijo de ruta de instancia para ignorar verificaciones
let replace_name = if let Some(instance) = &self.options.instance {
format!("{}/instances/{}/", self.options.path, instance)
} else {
format!("{}/", self.options.path)
};
// Si el archivo está en la lista "ignored", omitir verificaciones
let relative_path = file.path.replace(&replace_name, "");
if self.options.ignored.contains(&relative_path) {
continue;
}
// Si el archivo tiene un hash y no coincide, marcarlo para descarga
if let Some(expected_hash) = &file.sha1 {
let local_hash = get_file_hash(&file.path).await?;
if local_hash != *expected_hash {
to_download.push(file.clone());
}
}
} else {
// El archivo no existe en absoluto, marcarlo para descarga
to_download.push(file.clone());
}
}
Ok(to_download)
}
/// Calcular el tamaño total de descarga de todos los archivos en el bundle
pub async fn get_total_size(&self, bundle: &[BundleItem]) -> Result<u64> {
let mut total_size = 0;
for file in bundle {
if let Some(size) = file.size {
total_size += size;
}
}
Ok(total_size)
}
/// Remover archivos o directorios que no deberían estar presentes
pub async fn check_files(&self, bundle: &[BundleItem]) -> Result<()> {
// Si se usan instancias, asegurar que el directorio 'instances' existe
let instance_path = if let Some(instance) = &self.options.instance {
let instances_dir = format!("{}/instances", self.options.path);
if !Path::new(&instances_dir).exists() {
std::fs::create_dir_all(&instances_dir)?;
}
format!("/instances/{}", instance)
} else {
String::new()
};
// Recopilar todos los archivos existentes en el directorio relevante
let all_files = if self.options.instance.is_some() {
self.get_files(&format!("{}{}", self.options.path, instance_path))
} else {
self.get_files(&self.options.path)
};
// También recopilar archivos de directorios "loader" y "runtime" para ignorar
let mut ignored_files = Vec::new();
ignored_files.extend(self.get_files(&format!("{}/loader", self.options.path)));
ignored_files.extend(self.get_files(&format!("{}/runtime", self.options.path)));
// Convertir rutas ignoradas personalizadas a rutas de archivo reales
for ignored_path in &self.options.ignored {
let full_ignored_path = format!("{}{}/{}", self.options.path, instance_path, ignored_path);
if Path::new(&full_ignored_path).exists() {
if Path::new(&full_ignored_path).is_dir() {
// Si es un directorio, agregar todos los archivos dentro de él
ignored_files.extend(self.get_files(&full_ignored_path));
} else {
// Si es un archivo único, solo agregar ese archivo
ignored_files.push(full_ignored_path);
}
}
}
// Marcar rutas de bundle como ignoradas (para que no las eliminemos)
for file in bundle {
ignored_files.push(file.path.clone());
}
// Filtrar todos los archivos ignorados de la lista principal de archivos
let files_to_delete: Vec<String> = all_files.into_iter()
.filter(|file| !ignored_files.contains(file))
.collect();
// Remover cada archivo o directorio
for file_path in files_to_delete {
if let Ok(metadata) = std::fs::metadata(&file_path) {
if metadata.is_dir() {
std::fs::remove_dir_all(&file_path)?;
} else {
std::fs::remove_file(&file_path)?;
// Limpiar carpetas vacías subiendo hasta que lleguemos a la ruta principal
let mut current_dir = Path::new(&file_path).parent();
while let Some(dir) = current_dir {
if dir.to_string_lossy() == self.options.path {
break;
}
if let Ok(entries) = std::fs::read_dir(dir) {
if entries.count() == 0 {
std::fs::remove_dir(dir)?;
}
}
current_dir = dir.parent();
}
}
}
}
Ok(())
}
/// Recursivamente recopilar todos los archivos en una ruta de directorio dada
fn get_files(&self, dir_path: &str) -> Vec<String> {
let mut collected_files = Vec::new();
self.get_files_recursive(dir_path, &mut collected_files);
collected_files
}
fn get_files_recursive(&self, dir_path: &str, collected_files: &mut Vec<String>) {
if Path::new(dir_path).exists() {
if let Ok(entries) = std::fs::read_dir(dir_path) {
let entries: Vec<_> = entries.collect();
// Si el directorio está vacío, almacenarlo como "archivo" para que pueda ser procesado
if entries.is_empty() {
collected_files.push(dir_path.to_string());
}
// Explorar cada entrada hija
for entry in entries {
if let Ok(entry) = entry {
let full_path = entry.path().to_string_lossy().to_string();
if entry.metadata().map(|m| m.is_dir()).unwrap_or(false) {
self.get_files_recursive(&full_path, collected_files);
} else {
collected_files.push(full_path);
}
}
}
}
}
}
}