use crate::download::Item;
use crate::model::Song;
pub fn interleave<T>(lists: Vec<Vec<T>>) -> Vec<T> {
let mut iters: Vec<_> = lists.into_iter().map(Vec::into_iter).collect();
let mut out = Vec::new();
loop {
let before = out.len();
for it in &mut iters {
if let Some(x) = it.next() {
out.push(x);
}
}
if out.len() == before {
return out;
}
}
}
fn norm(s: &str) -> String {
s.chars()
.filter(|c| !c.is_whitespace())
.map(|c| match c {
'(' => '(',
')' => ')',
c => c,
})
.flat_map(char::to_lowercase)
.collect()
}
pub fn same_song(a: &Song, b: &Song) -> bool {
if a.source == b.source || norm(&a.title) != norm(&b.title) {
return false;
}
let first = |s: &Song| s.singers.first().map(|x| norm(x)).unwrap_or_default();
if first(a) != first(b) {
return false;
}
a.interval == 0 || b.interval == 0 || a.interval.abs_diff(b.interval) <= 5
}
fn better(a: &Item, b: &Item) -> bool {
(a.resolved.level, a.resolved.probe.size) > (b.resolved.level, b.resolved.probe.size)
}
pub fn dedupe_across_sources(items: Vec<Item>) -> (Vec<Item>, usize) {
let mut out: Vec<Item> = Vec::with_capacity(items.len());
let mut merged = 0;
for it in items {
match out.iter_mut().find(|o| same_song(&o.song, &it.song)) {
Some(o) => {
merged += 1;
if better(&it, o) {
*o = it;
}
}
None => out.push(it),
}
}
(out, merged)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{Level, Resolved, SourceKind};
use crate::probe::ProbeResult;
fn song(source: SourceKind, title: &str, singer: &str, interval: u64) -> Song {
Song {
source,
id: format!("{source}-{title}"),
title: title.into(),
singers: vec![singer.into()],
interval,
..Song::default()
}
}
fn item(s: Song, level: Level, size: u64) -> Item {
Item::new(
s,
Resolved {
via: "x",
quality: String::new(),
level,
probe: ProbeResult {
url: String::new(),
ext: "flac".into(),
size,
},
referer: None,
fallback: None,
},
)
}
#[test]
fn interleaves() {
assert_eq!(
interleave(vec![vec![1, 3, 5, 6], vec![2, 4]]),
[1, 2, 3, 4, 5, 6]
);
assert_eq!(interleave::<i32>(vec![vec![], vec![]]), Vec::<i32>::new());
assert_eq!(interleave(vec![vec![1, 2]]), [1, 2]);
}
#[test]
fn same_song_rules() {
use SourceKind::*;
let q = song(Qq, "刻在我心底的名字 (Live)", "蔡依林", 314);
assert!(same_song(
&q,
&song(Netease, "刻在我心底的名字(Live)", "蔡依林", 316)
));
assert!(same_song(
&q,
&song(Netease, "刻在我心底的名字 (live)", "蔡依林", 0)
));
assert!(!same_song(
&q,
&song(Qq, "刻在我心底的名字 (Live)", "蔡依林", 314)
));
assert!(!same_song(
&q,
&song(Netease, "刻在我心底的名字 (Live)", "蔡依林", 330)
));
assert!(!same_song(
&q,
&song(Netease, "刻在我心底的名字 (Live)", "田馥甄", 314)
));
assert!(!same_song(
&q,
&song(Netease, "刻在我心底的名字", "蔡依林", 314)
));
}
#[test]
fn keeps_better_in_first_position() {
use SourceKind::*;
let items = vec![
item(song(Qq, "无人知晓", "田馥甄", 288), Level::Sq, 30),
item(song(Qq, "悬日", "田馥甄", 280), Level::Master, 100),
item(song(Netease, "无人知晓", "田馥甄", 288), Level::Master, 160),
item(song(Netease, "悬日", "田馥甄", 280), Level::Master, 90),
item(song(Netease, "要去什么地方", "田馥甄", 250), Level::Hq, 9),
];
let (out, merged) = dedupe_across_sources(items);
assert_eq!(merged, 2);
let got: Vec<(SourceKind, &str)> = out
.iter()
.map(|i| (i.song.source, i.song.title.as_str()))
.collect();
assert_eq!(
got,
[
(Netease, "无人知晓"),
(Qq, "悬日"),
(Netease, "要去什么地方")
]
);
}
}