epik/games/
dlc.rs

1// DLC Management Module for v1.2.0
2// Manages DLC listing, installation, uninstallation, updates, and bundle management
3
4use crate::{Error, Result};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8// DLC ownership status
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum DlcOwnership {
11    Owned,
12    Available,
13    Required, // Must own parent game
14}
15
16// DLC installation status
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18pub enum DlcInstallationStatus {
19    NotInstalled,
20    Installed,
21    UpdateAvailable,
22    PartiallyInstalled,
23}
24
25// DLC information
26#[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, // Parent game app_name
32    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// DLC bundle (multiple DLCs sold together)
88#[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
117// DLC Manager
118pub struct DlcManager {
119    dlcs: HashMap<String, DlcInfo>,
120    bundles: HashMap<String, DlcBundle>,
121    // game_app_name -> Vec<dlc_ids>
122    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    // Register a DLC
141    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    // Unregister a DLC
153    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    // Get DLC info
165    pub fn get_dlc(&self, dlc_id: &str) -> Option<&DlcInfo> {
166        self.dlcs.get(dlc_id)
167    }
168
169    // Get mutable DLC info
170    pub fn get_dlc_mut(&mut self, dlc_id: &str) -> Option<&mut DlcInfo> {
171        self.dlcs.get_mut(dlc_id)
172    }
173
174    // Get all DLCs for a game
175    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    // Get owned DLCs for a game
188    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    // Get available DLCs for a game (not owned)
196    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    // Get DLCs with updates available
204    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    // Mark DLC as installed
212    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    // Mark DLC as uninstalled
223    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    // Mark DLC as having update available
235    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    // Set DLC ownership
247    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    // Set DLC install path
257    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    // Register a bundle
267    pub fn register_bundle(&mut self, bundle: DlcBundle) {
268        self.bundles.insert(bundle.id.clone(), bundle);
269    }
270
271    // Get bundle
272    pub fn get_bundle(&self, bundle_id: &str) -> Option<&DlcBundle> {
273        self.bundles.get(bundle_id)
274    }
275
276    // Get bundle mutable
277    pub fn get_bundle_mut(&mut self, bundle_id: &str) -> Option<&mut DlcBundle> {
278        self.bundles.get_mut(bundle_id)
279    }
280
281    // List all bundles
282    pub fn list_bundles(&self) -> Vec<&DlcBundle> {
283        self.bundles.values().collect()
284    }
285
286    // Get bundle contents (resolved DLCs)
287    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    // Check DLC compatibility (e.g., required DLCs)
300    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                // Check if parent game is owned/installed (simplified check)
304                true // In real implementation, would check parent game ownership
305            } else {
306                true
307            }
308        } else {
309            false
310        }
311    }
312
313    // Get total size of owned DLCs
314    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    // Get total size of installed DLCs
322    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}