1use crate::resource::partition_manager::PartitionState;
2use crate::resource::pdefs::PartitionInfo;
3use crate::resource::resource_info::ResourceInfo;
4use crate::{utils, GlacierResource, GlacierResourceError, WoaVersion};
5use lazy_regex::regex::Regex;
6use std::cmp::Ordering;
7use std::fmt::Debug;
8use std::{collections::HashMap, path::Path};
9use std::{fmt, io};
10use thiserror::Error;
11
12use crate::resource::resource_package::{ResourcePackage, ResourcePackageError};
13
14use super::runtime_resource_id::RuntimeResourceID;
15
16#[derive(Debug, Error)]
17pub enum ResourcePartitionError {
18 #[error("Failed to open file: {0}")]
19 IoError(#[from] io::Error),
20
21 #[error("Error while reading ResourcePackage({1}): {0}")]
22 ReadResourcePackageError(ResourcePackageError, String),
23
24 #[error("Failed to parse patch index as u16: {0}")]
25 ParsePatchIndexError(#[from] std::num::ParseIntError),
26
27 #[error("Base package not found: {0}")]
28 BasePackageNotFound(String),
29
30 #[error("Failed to read package: {0}")]
31 ReadPackageError(String),
32
33 #[error("No partition mounted")]
34 NotMounted,
35
36 #[error("Resource not available")]
37 ResourceNotAvailable,
38
39 #[error("Interal resource error: {0}")]
40 ResourceError(#[from] GlacierResourceError),
41}
42
43#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
44pub enum PatchId {
45 Base,
46 Patch(usize),
47}
48
49impl PatchId {
50 pub fn is_base(&self) -> bool {
51 match self {
52 PatchId::Base => true,
53 PatchId::Patch(_) => false,
54 }
55 }
56
57 pub fn is_patch(&self) -> bool {
58 !self.is_base()
59 }
60}
61
62impl Ord for PatchId {
63 fn cmp(&self, other: &Self) -> Ordering {
64 match (self, other) {
65 (PatchId::Base, PatchId::Base) => Ordering::Equal,
66 (PatchId::Base, PatchId::Patch(_)) => Ordering::Less,
67 (PatchId::Patch(_), PatchId::Base) => Ordering::Greater,
68 (PatchId::Patch(a), PatchId::Patch(b)) => a.cmp(b),
69 }
70 }
71}
72
73impl PartialOrd for PatchId {
74 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
75 Some(self.cmp(other))
76 }
77}
78
79pub struct ResourcePartition {
80 info: PartitionInfo,
81 pub packages: HashMap<PatchId, ResourcePackage>,
82 pub(crate) resources: HashMap<RuntimeResourceID, PatchId>,
83}
84
85impl ResourcePartition {
86 pub fn new(info: PartitionInfo) -> Self {
87 Self {
88 info,
89 packages: Default::default(),
90 resources: Default::default(),
91 }
92 }
93
94 fn read_patch_indices(
97 &self,
98 package_dir: &Path,
99 ) -> Result<Vec<PatchId>, ResourcePartitionError> {
100 let mut patch_indices = vec![];
101
102 let filename = self.info.filename(PatchId::Base);
103 if !package_dir.join(&filename).exists() {
104 return Err(ResourcePartitionError::BasePackageNotFound(filename));
105 }
106
107 let regex_str = format!(r"^(?:{}patch([0-9]+).rpkg)$", self.info.id);
108 let patch_package_re = Regex::new(regex_str.as_str()).unwrap();
109
110 for file_name in utils::read_file_names(package_dir)
111 .iter()
112 .flat_map(|file_name| file_name.to_str())
113 {
114 if let Some(cap) = patch_package_re.captures(file_name) {
115 let patch_level = cap[1].parse::<usize>()?;
116 if patch_level <= self.info.patch_level {
117 patch_indices.push(PatchId::Patch(patch_level));
118 }
119 }
120 }
121
122 patch_indices.sort();
123 Ok(patch_indices)
124 }
125
126 pub fn mount_resource_packages_in_partition(
132 &mut self,
133 runtime_path: &Path,
134 ) -> Result<(), ResourcePartitionError> {
135 self.mount_resource_packages_in_partition_with_callback(runtime_path, |_| {})
136 }
137
138 pub fn mount_resource_packages_in_partition_with_callback<F>(
144 &mut self,
145 runtime_path: &Path,
146 mut progress_callback: F,
147 ) -> Result<(), ResourcePartitionError>
148 where
149 F: FnMut(&PartitionState),
150 {
151 let mut state = PartitionState {
152 installing: true,
153 mounted: false,
154 install_progress: 0.0,
155 };
156
157 let patch_idx_result = self.read_patch_indices(runtime_path);
160 if patch_idx_result.is_err() {
161 state.installing = false;
162 progress_callback(&state);
163 return Ok(());
164 }
165
166 let patch_indices = patch_idx_result?;
167
168 let base_package_path = runtime_path.join(self.info.filename(PatchId::Base));
169 self.mount_package(base_package_path.as_path(), PatchId::Base)?;
170
171 for (index, patch_id) in patch_indices.clone().into_iter().enumerate() {
172 let patch_package_path = runtime_path.join(self.info.filename(patch_id));
173 self.mount_package(patch_package_path.as_path(), patch_id)?;
174
175 state.install_progress = index as f32 / patch_indices.len() as f32;
176 progress_callback(&state);
177 }
178 state.install_progress = 1.0;
179 state.installing = false;
180 state.mounted = true;
181 progress_callback(&state);
182
183 Ok(())
184 }
185
186 fn mount_package(
187 &mut self,
188 package_path: &Path,
189 patch_index: PatchId,
190 ) -> Result<(), ResourcePartitionError> {
191 let rpkg = ResourcePackage::from_file(package_path).map_err(|e| {
192 ResourcePartitionError::ReadResourcePackageError(
193 e,
194 package_path
195 .file_name()
196 .unwrap_or_default()
197 .to_string_lossy()
198 .into_owned(),
199 )
200 })?;
201
202 for deletion in rpkg.unneeded_resource_ids() {
204 if self.resources.contains_key(deletion) {
205 self.resources.remove_entry(deletion);
206 }
207 }
208
209 for rrid in rpkg.resources.keys() {
210 self.resources.insert(*rrid, patch_index);
211 }
212
213 self.packages.insert(patch_index, rpkg);
214 Ok(())
215 }
216
217 pub fn contains(&self, rrid: &RuntimeResourceID) -> bool {
218 self.resources.contains_key(rrid)
219 }
220
221 pub fn num_patches(&self) -> usize {
222 self.packages.len().saturating_sub(1)
223 }
224
225 pub fn latest_resources(&self) -> Vec<(&ResourceInfo, PatchId)> {
230 self.resources
231 .iter()
232 .flat_map(|(rrid, idx)| {
233 if let Ok(info) = self.resource_info_from(rrid, *idx) {
234 Some((info, *idx))
235 } else {
236 None
237 }
238 })
239 .collect()
240 }
241
242 pub fn latest_resources_of_type(&self, resource_type: &str) -> Vec<(&ResourceInfo, PatchId)>{
243 self.resources
244 .iter()
245 .flat_map(|(rrid, idx)| {
246 if let Ok(info) = self.resource_info_from(rrid, *idx) {
247 Some((info, *idx))
248 } else {
249 None
250 }
251 }).filter(|(resource, _)| resource.data_type() == resource_type)
252 .collect()
253 }
254
255 pub fn latest_resources_of_glacier_type<G: GlacierResource>(&self) -> Vec<(&ResourceInfo, PatchId)>{
256 let resource_type: String = String::from_utf8_lossy(&G::resource_type()).into_owned();
257 self.latest_resources_of_type(resource_type.as_str())
258 }
259
260 pub fn removed_resources(&self) -> Vec<(&ResourceInfo, PatchId)> {
266
267 self.packages
268 .iter()
269 .flat_map(|(patch_id, package)| {
270 package
271 .unneeded_resource_ids()
272 .iter()
273 .map(|&deletion| (*patch_id, *deletion)).collect::<Vec<_>>()
274 }).filter_map(|(_, deletion_rid)| {
275 let patches = self.resource_patch_indices(&deletion_rid);
276 if patches.is_empty() { return None } if self.resources.contains_key(&deletion_rid) { return None } patches.iter().max().and_then(|&latest_patch| {
279 self.resource_info_from(&deletion_rid, latest_patch)
280 .ok().map(|resource_info| (resource_info, latest_patch))
281 })
282 }).collect()
283 }
284
285 pub fn removed_resources_of_type(&self, resource_type: &str) -> Vec<(&ResourceInfo, PatchId)> {
286 self.removed_resources().iter().filter(|(res_info, _)| res_info.data_type() == resource_type).cloned().collect()
287 }
288
289 pub fn removed_resources_of_glacier_type<G: GlacierResource>(&self) -> Vec<(&ResourceInfo, PatchId)>{
290 let resource_type: String = String::from_utf8_lossy(&G::resource_type()).into_owned();
291 self.removed_resources_of_type(resource_type.as_str())
292 }
293
294 pub fn read_resource(
295 &self,
296 rrid: &RuntimeResourceID,
297 ) -> Result<Vec<u8>, ResourcePartitionError> {
298 let package_index = *self
299 .resources
300 .get(rrid)
301 .ok_or(ResourcePartitionError::ResourceNotAvailable)?;
302
303 let rpkg = self
304 .packages
305 .get(&package_index)
306 .ok_or(ResourcePartitionError::NotMounted)?;
307
308 rpkg.read_resource(rrid).map_err(|e| {
309 ResourcePartitionError::ReadResourcePackageError(e, self.info.filename(package_index))
310 })
311 }
312
313 pub fn read_glacier_resource<T>(
314 &self,
315 woa_version: WoaVersion,
316 rrid: &RuntimeResourceID,
317 ) -> Result<T::Output, ResourcePartitionError>
318 where
319 T: GlacierResource,
320 {
321 let package_index = *self
322 .resources
323 .get(rrid)
324 .ok_or(ResourcePartitionError::ResourceNotAvailable)?;
325
326 let rpkg = self
327 .packages
328 .get(&package_index)
329 .ok_or(ResourcePartitionError::NotMounted)?;
330
331 let bytes = rpkg.read_resource(rrid).map_err(|e| {
332 ResourcePartitionError::ReadResourcePackageError(e, self.info.filename(package_index))
333 })?;
334
335 T::process_data(woa_version, bytes).map_err(ResourcePartitionError::ResourceError)
336 }
337
338 pub fn read_resource_from(
339 &self,
340 rrid: &RuntimeResourceID,
341 patch_id: PatchId,
342 ) -> Result<Vec<u8>, ResourcePartitionError> {
343 let rpkg = self
344 .packages
345 .get(&patch_id)
346 .ok_or(ResourcePartitionError::NotMounted)?;
347
348 rpkg.read_resource(rrid).map_err(|e| {
349 ResourcePartitionError::ReadResourcePackageError(e, self.info.filename(patch_id))
350 })
351 }
352
353 pub fn get_resource_info(
354 &self,
355 rrid: &RuntimeResourceID,
356 ) -> Result<&ResourceInfo, ResourcePartitionError> {
357 let package_index = self
358 .resources
359 .get(rrid)
360 .ok_or(ResourcePartitionError::ResourceNotAvailable)?;
361
362 let rpkg = self
363 .packages
364 .get(package_index)
365 .ok_or(ResourcePartitionError::NotMounted)?;
366
367 rpkg.resources
368 .get(rrid)
369 .ok_or(ResourcePartitionError::ResourceNotAvailable)
370 }
371
372 pub fn resource_info_from(
373 &self,
374 rrid: &RuntimeResourceID,
375 patch_id: PatchId,
376 ) -> Result<&ResourceInfo, ResourcePartitionError> {
377 let rpkg = self
378 .packages
379 .get(&patch_id)
380 .ok_or(ResourcePartitionError::NotMounted)?;
381
382 rpkg.resources
383 .get(rrid)
384 .ok_or(ResourcePartitionError::ResourceNotAvailable)
385 }
386
387 pub fn partition_info(&self) -> &PartitionInfo {
388 &self.info
389 }
390
391 pub fn resource_patch_indices(&self, rrid: &RuntimeResourceID) -> Vec<PatchId> {
392 self.packages
393 .iter()
394 .filter(|(_, package)| package.resources.contains_key(rrid))
395 .map(|(id, _)| *id)
396 .collect::<Vec<PatchId>>()
397 }
398
399 pub fn resource_removal_indices(&self, rrid: &RuntimeResourceID) -> Vec<PatchId> {
400 self.packages
401 .iter()
402 .filter(|(_, package)| package.has_unneeded_resource(rrid))
403 .map(|(id, _)| *id)
404 .collect::<Vec<PatchId>>()
405 }
406}
407
408impl Debug for ResourcePartition {
409 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
410 let total = self
411 .packages
412 .values()
413 .map(|v| v.resources.len())
414 .sum::<usize>();
415
416 write!(
417 f,
418 "{{index: {}, name: {}, edge_resources: {}, total_resources: {} }}",
419 self.info.filename(PatchId::Base),
420 self.info.name.clone().unwrap_or_default(),
421 self.resources.len(),
422 total
423 )?;
424
425 Ok(())
426 }
427}