disco_quick/
master.rs

1use crate::artist_credit::{
2    get_credit_string, ArtistCredit, ArtistCreditBuilder, ArtistCreditParser,
3};
4use crate::parser::{Parser, ParserError};
5use crate::reader::XmlReader;
6use crate::shared::Image;
7use crate::util::{find_attr, maybe_text};
8use crate::video::{Video, VideoParser};
9use log::debug;
10use quick_xml::events::Event;
11use std::fmt;
12use std::mem::take;
13
14#[derive(Clone, Debug, Default, PartialEq, Eq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub struct Master {
17    pub id: u32,
18    pub title: String,
19    pub main_release: u32,
20    pub year: u16,
21    pub notes: Option<String>,
22    pub genres: Vec<String>,
23    pub styles: Vec<String>,
24    pub data_quality: String,
25    pub artists: Vec<ArtistCredit>,
26    pub images: Vec<Image>,
27    pub videos: Vec<Video>,
28}
29impl Master {
30    pub fn builder(id: u32, title: &str) -> MasterBuilder {
31        MasterBuilder {
32            inner: Master {
33                id,
34                title: title.to_string(),
35                ..Default::default()
36            },
37        }
38    }
39}
40
41impl fmt::Display for Master {
42    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43        let artist_credit = get_credit_string(&self.artists);
44        write!(f, "{} - {}", artist_credit, self.title)
45    }
46}
47
48pub struct MastersReader {
49    buf: Vec<u8>,
50    reader: XmlReader,
51    parser: MasterParser,
52}
53
54impl MastersReader {
55    pub fn new(reader: XmlReader, buf: Vec<u8>) -> Self {
56        Self {
57            buf,
58            reader,
59            parser: MasterParser::new(),
60        }
61    }
62}
63
64impl Iterator for MastersReader {
65    type Item = Master;
66    fn next(&mut self) -> Option<Self::Item> {
67        loop {
68            match self.reader.read_event_into(&mut self.buf).unwrap() {
69                Event::Eof => {
70                    return None;
71                }
72                ev => self.parser.process(&ev).unwrap(),
73            };
74            if self.parser.item_ready {
75                return Some(self.parser.take());
76            }
77            self.buf.clear();
78        }
79    }
80}
81
82#[derive(Debug, Default)]
83enum ParserState {
84    #[default]
85    Master,
86    MainRelease,
87    Artists,
88    Title,
89    DataQuality,
90    Notes,
91    Images,
92    Styles,
93    Genres,
94    Year,
95    Videos,
96}
97
98#[derive(Debug, Default)]
99pub struct MasterParser {
100    state: ParserState,
101    current_item: Master,
102    artist_parser: ArtistCreditParser,
103    videos_parser: VideoParser,
104    item_ready: bool,
105}
106
107impl Parser for MasterParser {
108    type Item = Master;
109    fn new() -> Self {
110        Self::default()
111    }
112
113    fn take(&mut self) -> Master {
114        self.item_ready = false;
115        take(&mut self.current_item)
116    }
117
118    fn process(&mut self, ev: &Event) -> Result<(), ParserError> {
119        self.state = match self.state {
120            ParserState::Master => match ev {
121                Event::Start(e) if e.local_name().as_ref() == b"master" => {
122                    self.current_item.id = find_attr(e, b"id")?.parse()?;
123                    debug!("Began parsing Master {}", self.current_item.id);
124                    ParserState::Master
125                }
126
127                Event::Start(e) => match e.local_name().as_ref() {
128                    b"main_release" => ParserState::MainRelease,
129                    b"title" => ParserState::Title,
130                    b"artists" => ParserState::Artists,
131                    b"data_quality" => ParserState::DataQuality,
132                    b"images" => ParserState::Images,
133                    b"styles" => ParserState::Styles,
134                    b"genres" => ParserState::Genres,
135                    b"notes" => ParserState::Notes,
136                    b"year" => ParserState::Year,
137                    b"videos" => ParserState::Videos,
138                    _ => ParserState::Master,
139                },
140
141                Event::End(e) if e.local_name().as_ref() == b"master" => {
142                    self.item_ready = true;
143                    ParserState::Master
144                }
145
146                _ => ParserState::Master,
147            },
148
149            ParserState::MainRelease => match ev {
150                Event::Text(e) => {
151                    self.current_item.main_release = e.unescape()?.parse()?;
152                    ParserState::MainRelease
153                }
154                _ => ParserState::Master,
155            },
156
157            ParserState::Artists => match ev {
158                Event::End(e) if e.local_name().as_ref() == b"artists" => ParserState::Master,
159
160                ev => {
161                    self.artist_parser.process(ev)?;
162                    if self.artist_parser.item_ready {
163                        self.current_item.artists.push(self.artist_parser.take());
164                    }
165                    ParserState::Artists
166                }
167            },
168
169            ParserState::Title => match ev {
170                Event::Text(e) => {
171                    self.current_item.title = e.unescape()?.to_string();
172                    ParserState::Title
173                }
174                _ => ParserState::Master,
175            },
176
177            ParserState::DataQuality => match ev {
178                Event::Text(e) => {
179                    self.current_item.data_quality = e.unescape()?.to_string();
180                    ParserState::DataQuality
181                }
182                _ => ParserState::Master,
183            },
184
185            ParserState::Images => match ev {
186                Event::Empty(e) if e.local_name().as_ref() == b"image" => {
187                    let image = Image::from_event(e)?;
188                    self.current_item.images.push(image);
189                    ParserState::Images
190                }
191                Event::End(e) if e.local_name().as_ref() == b"images" => ParserState::Master,
192
193                _ => ParserState::Images,
194            },
195
196            ParserState::Genres => match ev {
197                Event::End(e) if e.local_name().as_ref() == b"genres" => ParserState::Master,
198
199                Event::Text(e) => {
200                    self.current_item.genres.push(e.unescape()?.to_string());
201                    ParserState::Genres
202                }
203                _ => ParserState::Genres,
204            },
205
206            ParserState::Styles => match ev {
207                Event::End(e) if e.local_name().as_ref() == b"styles" => ParserState::Master,
208
209                Event::Text(e) => {
210                    self.current_item.styles.push(e.unescape()?.to_string());
211                    ParserState::Styles
212                }
213                _ => ParserState::Styles,
214            },
215
216            ParserState::Notes => match ev {
217                Event::Text(e) => {
218                    self.current_item.notes = maybe_text(e)?;
219                    ParserState::Notes
220                }
221                _ => ParserState::Master,
222            },
223
224            ParserState::Year => match ev {
225                Event::Text(e) => {
226                    self.current_item.year = e.unescape()?.parse()?;
227                    ParserState::Year
228                }
229                _ => ParserState::Master,
230            },
231
232            ParserState::Videos => match ev {
233                Event::End(e) if e.local_name().as_ref() == b"videos" => ParserState::Master,
234
235                ev => {
236                    self.videos_parser.process(ev)?;
237                    if self.videos_parser.item_ready {
238                        self.current_item.videos.push(self.videos_parser.take());
239                    }
240                    ParserState::Videos
241                }
242            },
243        };
244
245        Ok(())
246    }
247}
248
249pub struct MasterBuilder {
250    inner: Master,
251}
252
253impl MasterBuilder {
254    pub fn id(mut self, id: u32) -> Self {
255        self.inner.id = id;
256        self
257    }
258
259    pub fn title(mut self, title: &str) -> Self {
260        self.inner.title = title.to_string();
261        self
262    }
263
264    pub fn main_release(mut self, id: u32) -> Self {
265        self.inner.main_release = id;
266        self
267    }
268
269    pub fn notes(mut self, notes: &str) -> Self {
270        self.inner.notes = Some(notes.to_string());
271        self
272    }
273
274    pub fn year(mut self, year: u16) -> Self {
275        self.inner.year = year;
276        self
277    }
278
279    pub fn genre(mut self, genre: &str) -> Self {
280        self.inner.genres.push(genre.to_string());
281        self
282    }
283
284    pub fn style(mut self, style: &str) -> Self {
285        self.inner.styles.push(style.to_string());
286        self
287    }
288
289    pub fn data_quality(mut self, data_quality: &str) -> Self {
290        self.inner.data_quality = data_quality.to_string();
291        self
292    }
293
294    pub fn artist(mut self, credit: ArtistCreditBuilder) -> Self {
295        self.inner.artists.push(credit.build());
296        self
297    }
298
299    pub fn image(mut self, ty: &str, width: i16, height: i16) -> Self {
300        self.inner.images.push(Image {
301            r#type: ty.to_string(),
302            uri: None,
303            uri150: None,
304            width,
305            height,
306        });
307        self
308    }
309
310    pub fn video(mut self, src: &str, duration: u32, title: &str, description: &str) -> Self {
311        self.inner.videos.push(Video {
312            src: src.to_string(),
313            duration,
314            title: title.to_string(),
315            description: description.to_string(),
316            embed: true,
317        });
318        self
319    }
320
321    pub fn build(self) -> Master {
322        self.inner
323    }
324}
325#[cfg(test)]
326mod tests {
327    use pretty_assertions::assert_eq;
328    use std::io::{BufRead, BufReader, Cursor};
329
330    use crate::artist_credit::{ArtistCredit, ArtistCreditBuilder};
331
332    use super::{Master, MastersReader};
333
334    fn parse(xml: &'static str) -> Master {
335        let reader: Box<dyn BufRead> = Box::new(BufReader::new(Cursor::new(xml)));
336        let mut reader = quick_xml::Reader::from_reader(reader);
337        reader.config_mut().trim_text(true);
338        MastersReader::new(reader, Vec::new()).next().unwrap()
339    }
340
341    fn credit(id: u32, name: &str) -> ArtistCreditBuilder {
342        ArtistCredit::builder(id, name)
343    }
344
345    #[test]
346    fn test_master_830352_20231001() {
347        let expected = Master::builder(830352, "What Is The Time, Mr. Templar? / The Real Jazz")
348            .main_release(1671981)
349            .year(2009)
350            .genre("Electronic")
351            .style("Techno")
352            .style("Tech House")
353            .data_quality("Correct")
354            .artist(credit(239, "Jesper Dahlbäck").join("/"))
355            .artist(credit(1654, "DK").anv("Dahlbäck & Krome"))
356            .image("primary", 600, 600)
357            .image("secondary", 600, 600)
358            .video(
359                "https://www.youtube.com/watch?v=1andhkV72eo",
360                311,
361                 "JESPER DAHLBÄCK - WHAT IS THE TIME, MR. TEMPLAR? (PND02)",
362                 "JESPER DAHLBÄCK - WHAT IS THE TIME, MR. TEMPLAR? (PND02)\n\n#pvnx #thismustbethetrack #trackoftheday #russianadvisor #bvckgrnd #vinyl #vinylcommunity #vinylcollection #recordcollector #vinyladdict #records #music #musiclover #musicblog #vinyloftheday  #mus",
363            )
364            .video(
365                "https://www.youtube.com/watch?v=IWRv_Ye03cU",
366                315,
367                "J Dahlbäck - The Persuader - What Is The Time, Mr Templar?",
368                "From the LP: J. Dahlbäck - The Persuader - Label: Svek - Released: 1997\n\nProblem with the video? Please tell me and it will be removed immediately!"
369            )
370            .build();
371        let parsed = parse(
372            r#"
373<master id="830352">
374  <main_release>1671981</main_release>
375  <images>
376    <image type="primary" uri="" uri150="" width="600" height="600"/>
377    <image type="secondary" uri="" uri150="" width="600" height="600"/>
378  </images>
379  <artists>
380    <artist>
381      <id>239</id>
382      <name>Jesper Dahlbäck</name>
383      <anv>
384      </anv>
385      <join>/</join>
386      <role>
387      </role>
388      <tracks>
389      </tracks>
390    </artist>
391    <artist>
392      <id>1654</id>
393      <name>DK</name>
394      <anv>Dahlbäck &amp; Krome</anv>
395      <join>
396      </join>
397      <role>
398      </role>
399      <tracks>
400      </tracks>
401    </artist>
402  </artists>
403  <genres>
404    <genre>Electronic</genre>
405  </genres>
406  <styles>
407    <style>Techno</style>
408    <style>Tech House</style>
409  </styles>
410  <year>2009</year>
411  <title>What Is The Time, Mr. Templar? / The Real Jazz</title>
412  <data_quality>Correct</data_quality>
413  <videos>
414    <video src="https://www.youtube.com/watch?v=1andhkV72eo" duration="311" embed="true">
415      <title>JESPER DAHLBÄCK - WHAT IS THE TIME, MR. TEMPLAR? (PND02)</title>
416      <description>JESPER DAHLBÄCK - WHAT IS THE TIME, MR. TEMPLAR? (PND02)
417
418#pvnx #thismustbethetrack #trackoftheday #russianadvisor #bvckgrnd #vinyl #vinylcommunity #vinylcollection #recordcollector #vinyladdict #records #music #musiclover #musicblog #vinyloftheday  #mus</description>
419    </video>
420    <video src="https://www.youtube.com/watch?v=IWRv_Ye03cU" duration="315" embed="true">
421      <title>J Dahlbäck - The Persuader - What Is The Time, Mr Templar?</title>
422      <description>From the LP: J. Dahlbäck - The Persuader - Label: Svek - Released: 1997
423
424Problem with the video? Please tell me and it will be removed immediately!</description>
425    </video>
426  </videos>
427</master>
428        "#,
429        );
430        assert_eq!(expected, parsed);
431    }
432}