Skip to main content

rgc_chart/models/osu/
metadata.rs

1use std::str::FromStr;
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, PartialEq)]
5pub struct Metadata {
6    pub title: String,
7    
8    pub title_unicode: String,
9    
10    pub artist: String,
11    
12    pub artist_unicode: String,
13    
14    pub creator: String,
15    
16    pub version: String,
17    
18    pub source: String,
19    
20    pub tags: Vec<String>,
21    
22    pub beatmap_id: i32,
23    
24    pub beatmap_set_id: i32,
25}
26
27impl Default for Metadata {
28    fn default() -> Self {
29        Self {
30            title: String::new(),
31            title_unicode: String::new(),
32            artist: String::new(),
33            artist_unicode: String::new(),
34            creator: String::new(),
35            version: String::new(),
36            source: String::new(),
37            tags: Vec::new(),
38            beatmap_id: 0,
39            beatmap_set_id: 0,
40        }
41    }
42}
43
44impl FromStr for Metadata {
45    type Err = String;
46    
47    fn from_str(s: &str) -> Result<Self, Self::Err> {
48        let mut metadata = Metadata::default();
49        
50        let mut key_values = HashMap::new();
51        for line in s.lines() {
52            let line = line.trim();
53            if line.is_empty() || line.starts_with("//") {
54                continue;
55            }
56            
57            if let Some((key, value)) = line.split_once(':') {
58                let key = key.trim();
59                let value = value.trim();
60                key_values.insert(key.to_string(), value.to_string());
61            }
62        }
63        
64        if let Some(value) = key_values.get("Title") {
65            metadata.title = value.clone();
66        }
67        
68        if let Some(value) = key_values.get("TitleUnicode") {
69            metadata.title_unicode = value.clone();
70        }
71        
72        if let Some(value) = key_values.get("Artist") {
73            metadata.artist = value.clone();
74        }
75        
76        if let Some(value) = key_values.get("ArtistUnicode") {
77            metadata.artist_unicode = value.clone();
78        }
79        
80        if let Some(value) = key_values.get("Creator") {
81            metadata.creator = value.clone();
82        }
83        
84        if let Some(value) = key_values.get("Version") {
85            metadata.version = value.clone();
86        }
87        
88        if let Some(value) = key_values.get("Source") {
89            metadata.source = value.clone();
90        }
91        
92        if let Some(value) = key_values.get("Tags") {
93            metadata.tags = value
94                .split_whitespace()
95                .map(|tag| tag.to_string())
96                .collect();
97        }
98        
99        if let Some(value) = key_values.get("BeatmapID") {
100            metadata.beatmap_id = value.parse::<i32>()
101                .map_err(|_| format!("Invalid BeatmapID value: {}", value))?;
102        }
103        
104        if let Some(value) = key_values.get("BeatmapSetID") {
105            metadata.beatmap_set_id = value.parse::<i32>()
106                .map_err(|_| format!("Invalid BeatmapSetID value: {}", value))?;
107        }
108        
109        Ok(metadata)
110    }
111}
112
113impl Metadata {
114    pub fn new(
115        title: String,
116        title_unicode: String,
117        artist: String,
118        artist_unicode: String,
119        creator: String,
120        version: String,
121        source: String,
122        tags: Vec<String>,
123        beatmap_id: i32,
124        beatmap_set_id: i32,
125    ) -> Self {
126        Self {
127            title,
128            title_unicode,
129            artist,
130            artist_unicode,
131            creator,
132            version,
133            source,
134            tags,
135            beatmap_id,
136            beatmap_set_id,
137        }
138    }
139    
140    pub fn display_title(&self) -> &str {
141        if !self.title_unicode.is_empty() {
142            &self.title_unicode
143        } else {
144            &self.title
145        }
146    }
147    
148    pub fn display_artist(&self) -> &str {
149        if !self.artist_unicode.is_empty() {
150            &self.artist_unicode
151        } else {
152            &self.artist
153        }
154    }
155    
156    pub fn add_tag(&mut self, tag: String) {
157        if !self.tags.contains(&tag) {
158            self.tags.push(tag);
159        }
160    }
161    
162    pub fn remove_tag(&mut self, tag: &str) {
163        self.tags.retain(|t| t != tag);
164    }
165    
166    pub fn has_tag(&self, tag: &str) -> bool {
167        self.tags.iter().any(|t| t.eq_ignore_ascii_case(tag))
168    }
169    
170    pub fn tags_string(&self) -> String {
171        self.tags.join(" ")
172    }
173    
174    pub fn set_tags_from_string(&mut self, tags_str: &str) {
175        self.tags = tags_str
176            .split_whitespace()
177            .map(|tag| tag.to_string())
178            .collect();
179    }
180    
181    pub fn is_submitted(&self) -> bool {
182        self.beatmap_id > 0 && self.beatmap_set_id > 0
183    }
184    
185    pub fn has_unicode(&self) -> bool {
186        !self.title_unicode.is_empty() || !self.artist_unicode.is_empty()
187    }
188    
189    pub fn to_osu_format(&self) -> String {
190        let mut lines = Vec::new();
191        
192        lines.push(format!("Title: {}", self.title));
193        lines.push(format!("TitleUnicode: {}", self.title_unicode));
194        lines.push(format!("Artist: {}", self.artist));
195        lines.push(format!("ArtistUnicode: {}", self.artist_unicode));
196        lines.push(format!("Creator: {}", self.creator));
197        lines.push(format!("Version: {}", self.version));
198        lines.push(format!("Source: {}", self.source));
199        lines.push(format!("Tags: {}", self.tags_string()));
200        lines.push(format!("BeatmapID: {}", self.beatmap_id));
201        lines.push(format!("BeatmapSetID: {}", self.beatmap_set_id));
202        
203        lines.join("\n")
204    }
205}