1use crate::{Error, Result};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum DlcOwnership {
11 Owned,
12 Available,
13 Required, }
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18pub enum DlcInstallationStatus {
19 NotInstalled,
20 Installed,
21 UpdateAvailable,
22 PartiallyInstalled,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct DlcInfo {
28 pub id: String,
29 pub title: String,
30 pub description: Option<String>,
31 pub app_name: String, pub dlc_app_name: String,
33 pub namespace: String,
34 pub catalog_item_id: String,
35 pub ownership: DlcOwnership,
36 pub installation_status: DlcInstallationStatus,
37 pub size_bytes: u64,
38 pub version: String,
39 pub release_date: Option<chrono::DateTime<chrono::Utc>>,
40 pub install_date: Option<chrono::DateTime<chrono::Utc>>,
41 pub install_path: Option<std::path::PathBuf>,
42}
43
44impl DlcInfo {
45 pub fn new(
46 id: String,
47 title: String,
48 app_name: String,
49 dlc_app_name: String,
50 namespace: String,
51 catalog_item_id: String,
52 ) -> Self {
53 Self {
54 id,
55 title,
56 description: None,
57 app_name,
58 dlc_app_name,
59 namespace,
60 catalog_item_id,
61 ownership: DlcOwnership::Available,
62 installation_status: DlcInstallationStatus::NotInstalled,
63 size_bytes: 0,
64 version: "1.0".to_string(),
65 release_date: None,
66 install_date: None,
67 install_path: None,
68 }
69 }
70
71 pub fn is_owned(&self) -> bool {
72 self.ownership == DlcOwnership::Owned
73 }
74
75 pub fn is_installed(&self) -> bool {
76 matches!(
77 self.installation_status,
78 DlcInstallationStatus::Installed | DlcInstallationStatus::UpdateAvailable
79 )
80 }
81
82 pub fn needs_update(&self) -> bool {
83 self.installation_status == DlcInstallationStatus::UpdateAvailable
84 }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct DlcBundle {
90 pub id: String,
91 pub name: String,
92 pub description: Option<String>,
93 pub dlc_ids: Vec<String>,
94 pub price: Option<f32>,
95 pub discount_percent: Option<u32>,
96}
97
98impl DlcBundle {
99 pub fn new(id: String, name: String) -> Self {
100 Self {
101 id,
102 name,
103 description: None,
104 dlc_ids: Vec::new(),
105 price: None,
106 discount_percent: None,
107 }
108 }
109
110 pub fn add_dlc(&mut self, dlc_id: String) {
111 if !self.dlc_ids.contains(&dlc_id) {
112 self.dlc_ids.push(dlc_id);
113 }
114 }
115}
116
117pub struct DlcManager {
119 dlcs: HashMap<String, DlcInfo>,
120 bundles: HashMap<String, DlcBundle>,
121 game_dlcs: HashMap<String, Vec<String>>,
123}
124
125impl Default for DlcManager {
126 fn default() -> Self {
127 Self::new()
128 }
129}
130
131impl DlcManager {
132 pub fn new() -> Self {
133 Self {
134 dlcs: HashMap::new(),
135 bundles: HashMap::new(),
136 game_dlcs: HashMap::new(),
137 }
138 }
139
140 pub fn register_dlc(&mut self, dlc: DlcInfo) {
142 let dlc_id = dlc.id.clone();
143 let game_app_name = dlc.app_name.clone();
144
145 self.dlcs.insert(dlc_id.clone(), dlc);
146 self.game_dlcs
147 .entry(game_app_name)
148 .or_insert_with(Vec::new)
149 .push(dlc_id);
150 }
151
152 pub fn unregister_dlc(&mut self, dlc_id: &str) -> Result<()> {
154 if let Some(dlc) = self.dlcs.remove(dlc_id) {
155 if let Some(dlcs) = self.game_dlcs.get_mut(&dlc.app_name) {
156 dlcs.retain(|id| id != dlc_id);
157 }
158 Ok(())
159 } else {
160 Err(Error::Other(format!("DLC not found: {}", dlc_id)))
161 }
162 }
163
164 pub fn get_dlc(&self, dlc_id: &str) -> Option<&DlcInfo> {
166 self.dlcs.get(dlc_id)
167 }
168
169 pub fn get_dlc_mut(&mut self, dlc_id: &str) -> Option<&mut DlcInfo> {
171 self.dlcs.get_mut(dlc_id)
172 }
173
174 pub fn get_game_dlcs(&self, app_name: &str) -> Vec<&DlcInfo> {
176 self.game_dlcs
177 .get(app_name)
178 .map(|dlc_ids| {
179 dlc_ids
180 .iter()
181 .filter_map(|id| self.dlcs.get(id))
182 .collect()
183 })
184 .unwrap_or_default()
185 }
186
187 pub fn get_owned_dlcs(&self, app_name: &str) -> Vec<&DlcInfo> {
189 self.get_game_dlcs(app_name)
190 .into_iter()
191 .filter(|dlc| dlc.is_owned())
192 .collect()
193 }
194
195 pub fn get_available_dlcs(&self, app_name: &str) -> Vec<&DlcInfo> {
197 self.get_game_dlcs(app_name)
198 .into_iter()
199 .filter(|dlc| !dlc.is_owned())
200 .collect()
201 }
202
203 pub fn get_dlcs_with_updates(&self, app_name: &str) -> Vec<&DlcInfo> {
205 self.get_game_dlcs(app_name)
206 .into_iter()
207 .filter(|dlc| dlc.needs_update())
208 .collect()
209 }
210
211 pub fn mark_installed(&mut self, dlc_id: &str) -> Result<()> {
213 if let Some(dlc) = self.dlcs.get_mut(dlc_id) {
214 dlc.installation_status = DlcInstallationStatus::Installed;
215 dlc.install_date = Some(chrono::Utc::now());
216 Ok(())
217 } else {
218 Err(Error::Other(format!("DLC not found: {}", dlc_id)))
219 }
220 }
221
222 pub fn mark_uninstalled(&mut self, dlc_id: &str) -> Result<()> {
224 if let Some(dlc) = self.dlcs.get_mut(dlc_id) {
225 dlc.installation_status = DlcInstallationStatus::NotInstalled;
226 dlc.install_date = None;
227 dlc.install_path = None;
228 Ok(())
229 } else {
230 Err(Error::Other(format!("DLC not found: {}", dlc_id)))
231 }
232 }
233
234 pub fn mark_update_available(&mut self, dlc_id: &str) -> Result<()> {
236 if let Some(dlc) = self.dlcs.get_mut(dlc_id) {
237 if dlc.is_installed() {
238 dlc.installation_status = DlcInstallationStatus::UpdateAvailable;
239 }
240 Ok(())
241 } else {
242 Err(Error::Other(format!("DLC not found: {}", dlc_id)))
243 }
244 }
245
246 pub fn set_ownership(&mut self, dlc_id: &str, ownership: DlcOwnership) -> Result<()> {
248 if let Some(dlc) = self.dlcs.get_mut(dlc_id) {
249 dlc.ownership = ownership;
250 Ok(())
251 } else {
252 Err(Error::Other(format!("DLC not found: {}", dlc_id)))
253 }
254 }
255
256 pub fn set_install_path(&mut self, dlc_id: &str, path: std::path::PathBuf) -> Result<()> {
258 if let Some(dlc) = self.dlcs.get_mut(dlc_id) {
259 dlc.install_path = Some(path);
260 Ok(())
261 } else {
262 Err(Error::Other(format!("DLC not found: {}", dlc_id)))
263 }
264 }
265
266 pub fn register_bundle(&mut self, bundle: DlcBundle) {
268 self.bundles.insert(bundle.id.clone(), bundle);
269 }
270
271 pub fn get_bundle(&self, bundle_id: &str) -> Option<&DlcBundle> {
273 self.bundles.get(bundle_id)
274 }
275
276 pub fn get_bundle_mut(&mut self, bundle_id: &str) -> Option<&mut DlcBundle> {
278 self.bundles.get_mut(bundle_id)
279 }
280
281 pub fn list_bundles(&self) -> Vec<&DlcBundle> {
283 self.bundles.values().collect()
284 }
285
286 pub fn get_bundle_contents(&self, bundle_id: &str) -> Vec<&DlcInfo> {
288 if let Some(bundle) = self.bundles.get(bundle_id) {
289 bundle
290 .dlc_ids
291 .iter()
292 .filter_map(|id| self.dlcs.get(id))
293 .collect()
294 } else {
295 Vec::new()
296 }
297 }
298
299 pub fn check_compatibility(&self, dlc_id: &str, _owned_dlc_ids: &[String]) -> bool {
301 if let Some(dlc) = self.dlcs.get(dlc_id) {
302 if dlc.ownership == DlcOwnership::Required {
303 true } else {
306 true
307 }
308 } else {
309 false
310 }
311 }
312
313 pub fn get_owned_dlcs_size(&self, app_name: &str) -> u64 {
315 self.get_owned_dlcs(app_name)
316 .iter()
317 .map(|dlc| dlc.size_bytes)
318 .sum()
319 }
320
321 pub fn get_installed_dlcs_size(&self, app_name: &str) -> u64 {
323 self.get_game_dlcs(app_name)
324 .iter()
325 .filter(|dlc| dlc.is_installed())
326 .map(|dlc| dlc.size_bytes)
327 .sum()
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn test_register_dlc() {
337 let mut manager = DlcManager::new();
338 let dlc = DlcInfo::new(
339 "dlc1".to_string(),
340 "Expansion Pack".to_string(),
341 "game1".to_string(),
342 "game1_dlc1".to_string(),
343 "namespace".to_string(),
344 "catalog_id".to_string(),
345 );
346
347 manager.register_dlc(dlc);
348 assert!(manager.get_dlc("dlc1").is_some());
349 }
350
351 #[test]
352 fn test_get_game_dlcs() {
353 let mut manager = DlcManager::new();
354 let dlc1 = DlcInfo::new(
355 "dlc1".to_string(),
356 "DLC 1".to_string(),
357 "game1".to_string(),
358 "game1_dlc1".to_string(),
359 "ns".to_string(),
360 "cat".to_string(),
361 );
362 let dlc2 = DlcInfo::new(
363 "dlc2".to_string(),
364 "DLC 2".to_string(),
365 "game1".to_string(),
366 "game1_dlc2".to_string(),
367 "ns".to_string(),
368 "cat".to_string(),
369 );
370
371 manager.register_dlc(dlc1);
372 manager.register_dlc(dlc2);
373
374 let game_dlcs = manager.get_game_dlcs("game1");
375 assert_eq!(game_dlcs.len(), 2);
376 }
377
378 #[test]
379 fn test_dlc_ownership() {
380 let mut manager = DlcManager::new();
381 let dlc = DlcInfo::new(
382 "dlc1".to_string(),
383 "DLC".to_string(),
384 "game1".to_string(),
385 "game1_dlc1".to_string(),
386 "ns".to_string(),
387 "cat".to_string(),
388 );
389
390 manager.register_dlc(dlc);
391 manager.set_ownership("dlc1", DlcOwnership::Owned).unwrap();
392
393 let dlc = manager.get_dlc("dlc1").unwrap();
394 assert!(dlc.is_owned());
395 }
396
397 #[test]
398 fn test_bundle_management() {
399 let mut manager = DlcManager::new();
400 let mut bundle = DlcBundle::new("bundle1".to_string(), "Complete Edition".to_string());
401
402 bundle.add_dlc("dlc1".to_string());
403 bundle.add_dlc("dlc2".to_string());
404
405 manager.register_bundle(bundle);
406 let retrieved = manager.get_bundle("bundle1").unwrap();
407 assert_eq!(retrieved.dlc_ids.len(), 2);
408 }
409}