mxl_player_components/ui/playlist/
model.rs1use gst_pbutils::DiscovererInfo;
2use log::*;
3use mxl_relm4_components::relm4::{
4 Sender, adw::prelude::*, factory::FactoryVecDeque, gtk::gdk::DragAction, prelude::*,
5};
6use std::path::PathBuf;
7
8use crate::ui::playlist::messages::{
9 PlaylistChange, PlaylistCommandOutput, PlaylistComponentInput, PlaylistComponentOutput, PlaylistState, RepeatMode,
10 SortOrder,
11};
12use crate::uri_helpers::uri_from_pathbuf;
13
14use super::factory::PlaylistEntryInit;
15pub use super::factory::PlaylistEntryModel;
16
17#[derive(Debug, Clone)]
18pub struct PlaylistEntry {
19 pub uri: PathBuf,
20 pub media_info: Option<DiscovererInfo>,
21}
22
23impl From<PathBuf> for PlaylistEntry {
24 fn from(uri: PathBuf) -> Self {
25 PlaylistEntry {
26 uri: uri.clone(),
27 media_info: None,
28 }
29 }
30}
31
32#[derive(Debug)]
33pub struct PlaylistComponentInit {
34 pub uris: Vec<PlaylistEntry>,
35 pub mark_index_as_playing: Option<usize>,
36 pub repeat: RepeatMode,
37 pub is_user_mutable: bool,
38 pub show_file_index: bool,
39}
40
41impl Default for PlaylistComponentInit {
42 fn default() -> Self {
43 Self {
44 uris: vec![],
45 mark_index_as_playing: None,
46 repeat: RepeatMode::default(),
47 is_user_mutable: true,
48 show_file_index: false,
49 }
50 }
51}
52
53pub struct PlaylistComponentModel {
54 pub uris: FactoryVecDeque<PlaylistEntryModel>,
55 pub index: Option<DynamicIndex>,
56 pub state: PlaylistState,
57 pub show_file_index: bool,
58 pub show_placeholder: bool,
59 pub repeat: RepeatMode,
60 pub thread_pool: Option<rusty_pool::ThreadPool>,
61 pub is_user_mutable: bool,
62}
63
64#[allow(dead_code)]
65pub(super) enum InsertMode {
66 Front,
67 AtIndex(DynamicIndex),
68 Back,
69}
70
71impl PlaylistComponentModel {
72 pub(super) fn init_thread_pool() -> rusty_pool::ThreadPool {
73 const DEFAULT_NUMBER_THREADS: usize = 3;
74 let thread_count = std::thread::available_parallelism()
75 .map_or(DEFAULT_NUMBER_THREADS, |v| v.get().min(DEFAULT_NUMBER_THREADS));
76 debug!("Create thread pool with {thread_count} number of threads");
77 rusty_pool::Builder::new()
78 .name("mxl_playlist_pool".to_owned())
79 .max_size(thread_count)
80 .build()
81 }
82
83 pub fn dynamic_index(&self) -> Option<&DynamicIndex> {
84 self.index.as_ref()
85 }
86
87 pub fn new_drop_target(sender: Sender<PlaylistComponentInput>) -> gtk::DropTarget {
88 let formats = gtk::gdk::ContentFormatsBuilder::new()
89 .add_type(gtk::gdk::FileList::static_type())
90 .add_type(gtk::gio::File::static_type())
91 .build();
92 let drop_target = gtk::DropTarget::builder()
93 .actions(DragAction::COPY)
94 .formats(&formats)
95 .build();
96
97 drop_target.set_types(&[gtk::gdk::FileList::static_type(), gtk::gio::File::static_type()]);
98
99 drop_target.connect_drop(move |_, value, _, _| {
100 if let Ok(files) = value.get::<gtk::gdk::FileList>() {
101 let files: Vec<_> = files.files().iter().filter_map(|file| file.path()).collect();
102
103 sender.emit(PlaylistComponentInput::Add(files));
104 return true;
105 } else if let Ok(file) = value.get::<gtk::gio::File>()
106 && let Some(file) = file.path()
107 {
108 sender.emit(PlaylistComponentInput::Add(vec![file]));
109 return true;
110 }
111 false
112 });
113
114 drop_target
115 }
116
117 pub(super) fn add_uris(
118 &mut self,
119 sender: &ComponentSender<Self>,
120 insert_mode: InsertMode,
121 uris: &Vec<PlaylistEntry>,
122 ) {
123 macro_rules! insert {
124 ($edit:expr, $insert_mode:expr, $entry:expr) => {
125 match &$insert_mode {
126 InsertMode::Front => {
127 $edit.push_front($entry);
128 }
129 InsertMode::AtIndex(index) => {
130 $edit.insert(index.current_index(), $entry);
131 }
132 InsertMode::Back => {
133 $edit.push_back($entry);
134 }
135 }
136 };
137 }
138
139 let mut edit = self.uris.guard();
140 for entry in uris {
141 match uri_from_pathbuf(&entry.uri) {
142 Ok(file) => {
143 let file_name = entry
144 .uri
145 .file_name()
146 .map(|x| x.to_str().unwrap_or_default().to_string());
147
148 insert!(
149 edit,
150 insert_mode,
151 PlaylistEntryInit {
152 uri: file,
153 short_uri: file_name,
154 media_info: entry.media_info.clone(),
155 error: None,
156 show_index: self.show_file_index,
157 movable: self.is_user_mutable,
158 removable: self.is_user_mutable,
159 drop_files_to_add: self.is_user_mutable,
160 }
161 );
162 }
163 Err(error) => {
164 let file_name = entry
165 .uri
166 .file_name()
167 .map(|x| x.to_str().unwrap_or_default().to_string());
168 let file = entry.uri.to_str().unwrap_or_default().to_string();
169
170 insert!(
171 edit,
172 insert_mode,
173 PlaylistEntryInit {
174 uri: file,
175 short_uri: file_name,
176 media_info: entry.media_info.clone(),
177 error: Some(error),
178 show_index: self.show_file_index,
179 movable: self.is_user_mutable,
180 removable: self.is_user_mutable,
181 drop_files_to_add: self.is_user_mutable,
182 }
183 );
184 }
185 }
186 }
187 sender
188 .command_sender()
189 .emit(PlaylistCommandOutput::ShowPlaceholder(edit.is_empty()));
190 sender
191 .output_sender()
192 .emit(PlaylistComponentOutput::PlaylistChanged(PlaylistChange::Added));
193 drop(edit);
194 }
195
196 pub(super) fn sort_factory(&mut self, order: &SortOrder) {
197 macro_rules! sort_factory {
198 ($guard:expr, $key:ident) => {{
199 let length = $guard.len();
200 for from_pos in 1..length {
201 let mut j = from_pos;
202 while j > 0 && $guard.get(j).unwrap().$key < $guard.get(j - 1).unwrap().$key {
203 trace!(
204 "Swap item {}[{:?}] with item {}[{:?}]",
205 j,
206 $guard.get(j).unwrap().$key,
207 j - 1,
208 $guard.get(j - 1).unwrap().$key
209 );
210 $guard.swap(j, j - 1);
211 j -= 1;
212 }
213 }
214 }};
215 }
216
217 let mut guard = self.uris.guard();
218 if !guard.is_empty() {
219 match order {
220 SortOrder::StartTime => {
221 sort_factory!(guard, date_time);
222 }
223 SortOrder::ShortUri => {
224 sort_factory!(guard, short_uri);
225 }
226 }
227 }
228 }
229
230 pub(super) fn previous(&mut self, sender: &ComponentSender<Self>) {
231 if let Some(index) = self.index.as_ref() {
232 if let Some(i) = index.current_index().checked_sub(1)
233 && let Some(entry) = self.uris.guard().get(i)
234 {
235 debug!("Playlist previous -> switch to index {i}");
237 sender.input(PlaylistComponentInput::Switch(entry.index.clone()));
238 return;
239 }
240 if let Some(entry) = self.uris.guard().get(index.current_index()) {
241 debug!("Playlist previous -> switch to index {}", index.current_index());
243 sender.input(PlaylistComponentInput::Switch(entry.index.clone()));
244 }
245 }
246 }
247
248 pub(super) fn next(&mut self, sender: &ComponentSender<Self>) {
249 if let Some(current_index) = self.index.as_ref() {
250 if let Some(i) = current_index.current_index().checked_add(1)
251 && let Some(entry) = self.uris.guard().get(i)
252 {
253 debug!("Playlist next -> switch to index {i}");
255 sender.input(PlaylistComponentInput::Switch(entry.index.clone()));
256 return;
257 }
258 match self.repeat {
259 RepeatMode::Off => (),
260 RepeatMode::All => {
261 if let Some(entry) = self.uris.guard().get(0) {
262 debug!("Playlist repeat all -> switch to index 0");
263 sender.input(PlaylistComponentInput::Switch(entry.index.clone()));
264 return;
265 }
266 }
267 }
268 sender.input(PlaylistComponentInput::EndOfPlaylist(current_index.clone()));
269 }
270 }
271}