2025-06-24 01:06:25 +02:00
use crate ::model ::{ ConfigSortChannel , ConfigSortGroup , ConfigTarget };
2025-05-04 16:07:26 +02:00
use deunicode ::deunicode ;
use std ::cmp ::Ordering ;
2025-06-24 01:06:25 +02:00
use shared ::foundation ::filter ::get_field_value ;
use shared ::model ::{ PlaylistGroup , PlaylistItem , SortOrder };
2025-05-04 10:59:08 +02:00
fn playlist_comparator (
sequence : Option <& Vec < regex ::Regex >> ,
2025-05-26 13:57:29 +02:00
order : SortOrder ,
2025-05-04 10:59:08 +02:00
value_a : & str ,
value_b : & str ,
) -> Ordering {
if let Some ( regex_list ) = sequence {
let mut match_a = None ;
let mut match_b = None ;
for ( i , regex ) in regex_list . iter (). enumerate () {
if match_a . is_none () {
if let Some ( caps ) = regex . captures ( value_a ) {
match_a = Some (( i , caps ));
}
}
if match_b . is_none () {
if let Some ( caps ) = regex . captures ( value_b ) {
match_b = Some (( i , caps ));
}
}
// If both matches found → break
if match_a . is_some () && match_b . is_some () {
break ;
}
}
match ( match_a , match_b ) {
( Some (( idx_a , caps_a )), Some (( idx_b , caps_b ))) => {
// Different regex indices → sort by their sequence order.
if idx_a != idx_b {
return match order {
SortOrder ::Asc => idx_a . cmp ( & idx_b ),
SortOrder ::Desc => idx_b . cmp ( & idx_a ),
};
}
// Same regex → sort by captures (c1, c2, …)
let mut named : Vec < _ > = regex_list [ idx_a ]
. capture_names ()
. flatten ()
. filter ( | name | name . starts_with ( 'c' ))
. collect ();
named . sort_by_key ( | name | name [ 1 .. ]. parse ::< u32 > (). unwrap_or ( 0 ));
for name in named {
let va = caps_a . name ( name ). map ( | m | m . as_str ());
let vb = caps_b . name ( name ). map ( | m | m . as_str ());
if let ( Some ( va ), Some ( vb )) = ( va , vb ) {
let o = va . cmp ( vb );
if o != Ordering ::Equal {
return match order {
SortOrder ::Asc => o ,
SortOrder ::Desc => o . reverse (),
};
}
}
}
Ordering ::Equal
}
( Some ( _ ), None ) => match order {
SortOrder ::Asc => Ordering ::Less ,
SortOrder ::Desc => Ordering ::Greater ,
},
( None , Some ( _ )) => match order {
SortOrder ::Asc => Ordering ::Greater ,
SortOrder ::Desc => Ordering ::Less ,
},
( None , None ) => {
// NP match → fallback
let o = value_a . cmp ( value_b );
match order {
SortOrder ::Asc => o ,
SortOrder ::Desc => o . reverse (),
}
}
}
} else {
// No Regex-Sequence defined → fallback
let o = value_a . cmp ( value_b );
match order {
SortOrder ::Asc => o ,
SortOrder ::Desc => o . reverse (),
}
}
}
fn playlistgroup_comparator ( a : & PlaylistGroup , b : & PlaylistGroup , group_sort : & ConfigSortGroup , match_as_ascii : bool ) -> Ordering {
let value_a = if match_as_ascii { deunicode ( & a . title ) } else { a . title . to_string () };
let value_b = if match_as_ascii { deunicode ( & b . title ) } else { b . title . to_string () };
2025-06-24 01:06:25 +02:00
playlist_comparator ( group_sort . sequence . as_ref (), group_sort . order , & value_a , & value_b )
2025-05-04 10:59:08 +02:00
}
fn playlistitem_comparator (
a : & PlaylistItem ,
b : & PlaylistItem ,
channel_sort : & ConfigSortChannel ,
match_as_ascii : bool ,
) -> Ordering {
2025-05-26 13:57:29 +02:00
let raw_value_a = get_field_value ( a , channel_sort . field );
let raw_value_b = get_field_value ( b , channel_sort . field );
2025-05-04 10:59:08 +02:00
let value_a = if match_as_ascii { deunicode ( & raw_value_a ) } else { raw_value_a };
let value_b = if match_as_ascii { deunicode ( & raw_value_b ) } else { raw_value_b };
2025-06-24 01:06:25 +02:00
playlist_comparator ( channel_sort . sequence . as_ref (), channel_sort . order , & value_a , & value_b )
2025-05-04 10:59:08 +02:00
}
pub ( in crate ::processing ::processor ) fn sort_playlist ( target : & ConfigTarget , new_playlist : & mut [ PlaylistGroup ]) {
if let Some ( sort ) = & target . sort {
let match_as_ascii = sort . match_as_ascii ;
if let Some ( group_sort ) = & sort . groups {
new_playlist . sort_by ( | a , b | playlistgroup_comparator ( a , b , group_sort , match_as_ascii ));
}
if let Some ( channel_sorts ) = & sort . channels {
for channel_sort in channel_sorts {
2025-06-24 01:06:25 +02:00
let regexp = & channel_sort . group_pattern ;
2025-05-04 10:59:08 +02:00
for group in new_playlist . iter_mut () {
let group_title = if match_as_ascii { deunicode ( & group . title ) } else { group . title . to_string () };
if regexp . is_match ( group_title . as_str ()) {
group . channels . sort_by ( | chan1 , chan2 | playlistitem_comparator ( chan1 , chan2 , channel_sort , match_as_ascii ));
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use crate ::processing ::processor ::sort ::playlistitem_comparator ;
2025-05-04 16:07:26 +02:00
use regex ::Regex ;
2025-06-24 01:06:25 +02:00
use shared ::model ::{ ItemField , PlaylistItem , PlaylistItemHeader , SortOrder };
use crate ::model ::ConfigSortChannel ;
2025-05-04 10:59:08 +02:00
#[test]
fn test_sort () {
let mut channels : Vec < PlaylistItem > = vec! [
( "D" , "HD" ), ( "A" , "FHD" ), ( "Z" , "HD" ), ( "K" , "HD" ), ( "B" , "HD" ), ( "A" , "HD" ),
( "K" , "UHD" ), ( "C" , "HD" ), ( "L" , "FHD" ), ( "R" , "UHD" ), ( "T" , "SD" ), ( "A" , "FHD" ),
2025-05-16 14:11:33 +02:00
]. into_iter (). map ( | ( name , quality ) | PlaylistItem { header : PlaylistItemHeader { title : format ! ( "Chanel {name} [{quality}]" ), .. Default ::default () } }). collect ::< Vec < PlaylistItem >> ();
2025-05-04 10:59:08 +02:00
let channel_sort = ConfigSortChannel {
field : ItemField ::Caption ,
order : SortOrder ::Asc ,
2025-06-24 01:06:25 +02:00
sequence : Some ( vec! [
2025-05-04 10:59:08 +02:00
Regex ::new ( r "(?P<c1>.*?)\bUHD\b" ). unwrap (),
Regex ::new ( r "(?P<c1>.*?)\bFHD\b" ). unwrap (),
Regex ::new ( r "(?P<c1>.*?)\bHD\b" ). unwrap (),
]),
2025-06-24 01:06:25 +02:00
group_pattern : Regex ::new ( ".*" ). unwrap (),
2025-05-04 10:59:08 +02:00
};
channels . sort_by ( | chan1 , chan2 | playlistitem_comparator ( chan1 , chan2 , & channel_sort , true ));
let expected = vec! [ "Chanel K [UHD]" , "Chanel R [UHD]" , "Chanel A [FHD]" , "Chanel A [FHD]" , "Chanel L [FHD]" , "Chanel A [HD]" , "Chanel B [HD]" , "Chanel C [HD]" , "Chanel D [HD]" , "Chanel K [HD]" , "Chanel Z [HD]" , "Chanel T [SD]" ];
let sorted = channels . into_iter (). map ( | pli | pli . header . title . clone ()). collect ::< Vec < String >> ();
assert_eq! ( expected , sorted );
}
2025-05-04 16:07:26 +02:00
#[test]
fn test_sort2 () {
let mut channels : Vec < PlaylistItem > = vec! [
"US| EAST [FHD] abc" ,
"US| EAST [FHD] def" ,
"US| EAST [FHD] ghi" ,
"US| EAST [HD] jkl" ,
"US| EAST [HD] mno" ,
"US| EAST [HD] pqrs" ,
"US| EAST [HD] tuv" ,
"US| EAST [HD] wxy" ,
"US| EAST [HD] z" ,
"US| EAST [SD] a" ,
"US| EAST [FHD] bc" ,
"US| EAST [FHD] de" ,
"US| EAST [HD] f" ,
"US| EAST [HD] h" ,
"US| EAST [SD] ijk" ,
"US| EAST [SD] l" ,
"US| EAST [UHD] m" ,
"US| WEST [FHD] no" ,
"US| WEST [HD] qrst" ,
"US| WEST [HD] uvw" ,
"US| (West) xv" ,
"US| East d" ,
"US| West e" ,
"US| West f" ,
2025-05-16 14:11:33 +02:00
]. into_iter (). map ( | name | PlaylistItem { header : PlaylistItemHeader { title : name . to_string (), .. Default ::default () } }). collect ::< Vec < PlaylistItem >> ();
2025-05-04 16:07:26 +02:00
let channel_sort = ConfigSortChannel {
field : ItemField ::Caption ,
order : SortOrder ::Asc ,
2025-06-24 01:06:25 +02:00
sequence : Some ( vec! [
2025-05-04 16:07:26 +02:00
Regex ::new ( r "^US\| EAST.*?\[\bUHD\b\](?P<c1>.*)" ). unwrap (),
Regex ::new ( r "^US\| EAST.*?\[\bFHD\b\](?P<c1>.*)" ). unwrap (),
Regex ::new ( r "^US\| EAST.*?\[\bHD\b\](?P<c1>.*)" ). unwrap (),
Regex ::new ( r "^US\| EAST.*?\[\bSD\b\](?P<c1>.*)" ). unwrap (),
Regex ::new ( r "^US\| WEST.*?\[\bUHD\b\](?P<c1>.*)" ). unwrap (),
Regex ::new ( r "^US\| WEST.*?\[\bFHD\b\](?P<c1>.*)" ). unwrap (),
Regex ::new ( r "^US\| WEST.*?\[\bHD\b\](?P<c1>.*)" ). unwrap (),
Regex ::new ( r "^US\| WEST.*?\[\bSD\b\](?P<c1>.*)" ). unwrap (),
]),
2025-06-24 01:06:25 +02:00
group_pattern : Regex ::new ( ".*" ). unwrap (),
2025-05-04 16:07:26 +02:00
};
channels . sort_by ( | chan1 , chan2 | playlistitem_comparator ( chan1 , chan2 , & channel_sort , true ));
let sorted = channels . into_iter (). map ( | pli | pli . header . title . clone ()). collect ::< Vec < String >> ();
let expected = vec! [ "US| EAST [UHD] m" , "US| EAST [FHD] abc" , "US| EAST [FHD] bc" , "US| EAST [FHD] de" , "US| EAST [FHD] def" , "US| EAST [FHD] ghi" , "US| EAST [HD] f" , "US| EAST [HD] h" , "US| EAST [HD] jkl" , "US| EAST [HD] mno" , "US| EAST [HD] pqrs" , "US| EAST [HD] tuv" , "US| EAST [HD] wxy" , "US| EAST [HD] z" , "US| EAST [SD] a" , "US| EAST [SD] ijk" , "US| EAST [SD] l" , "US| WEST [FHD] no" , "US| WEST [HD] qrst" , "US| WEST [HD] uvw" , "US| (West) xv" , "US| East d" , "US| West e" , "US| West f" ];
assert_eq! ( expected , sorted );
}
2025-05-04 10:59:08 +02:00
}