diff --git a/src-tauri/src/commands/media.rs b/src-tauri/src/commands/media.rs new file mode 100644 index 000000000..442301859 --- /dev/null +++ b/src-tauri/src/commands/media.rs @@ -0,0 +1,79 @@ +use log::info; +use std::path::Path; +use std::process::Command; + +#[tauri::command] +pub async fn open_in_mpv(url: String, path: String) -> Result<(), String> { + info!("Custom MVP path: {}", path); + let mpv_paths = if cfg!(target_os = "windows") { + vec![ + r"C:\Program Files\mpv\mpv.exe", + r"C:\Program Files (x86)\mpv\mpv.exe", + ] + } else if cfg!(target_os = "linux") { + vec!["/usr/bin/mpv", "/usr/local/bin/mpv", "/snap/bin/mpv"] + } else { + vec![ + "/Applications/mpv.app/Contents/MacOS/mpv", + "/opt/homebrew/bin/mpv", + "/usr/local/bin/mpv", + ] + }; + + let mpv_path = if !path.is_empty() && Path::new(&path).exists() { + path + } else { + mpv_paths + .iter() + .find(|&path| Path::new(path).exists()) + .map(|s| s.to_string()) + .unwrap_or_else(|| "mpv".to_string()) + }; + + info!("Using the following MPV player path: {}", mpv_path); + + Command::new(mpv_path) + .arg(&url) + .spawn() + .map_err(|e| e.to_string())?; + + Ok(()) +} + +#[tauri::command] +pub async fn open_in_vlc(url: String, path: String) -> Result<(), String> { + info!("Custom MVP path: {}", path); + let vlc_paths = if cfg!(target_os = "windows") { + vec![ + r"C:\Program Files\VideoLAN\VLC\vlc.exe", + r"C:\Program Files (x86)\VideoLAN\VLC\vlc.exe", + ] + } else if cfg!(target_os = "linux") { + vec!["/usr/bin/vlc", "/usr/local/bin/vlc", "/snap/bin/vlc"] + } else { + vec![ + "/Applications/VLC.app/Contents/MacOS/VLC", + "/opt/homebrew/bin/vlc", + "/usr/local/bin/vlc", + ] + }; + + let vlc_path = if !path.is_empty() && Path::new(&path).exists() { + path + } else { + vlc_paths + .iter() + .find(|&path| Path::new(path).exists()) + .map(|s| s.to_string()) + .unwrap_or_else(|| "vlc".to_string()) + }; + + info!("Using VLC player path: {}", vlc_path); + + Command::new(vlc_path) + .arg(&url) + .spawn() + .map_err(|e| e.to_string())?; + + Ok(()) +} diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs new file mode 100644 index 000000000..aa9ba2837 --- /dev/null +++ b/src-tauri/src/commands/mod.rs @@ -0,0 +1,2 @@ +pub mod epg; +pub mod media; diff --git a/src-tauri/src/epg.rs b/src-tauri/src/epg.rs index 4912702c0..263f06a69 100644 --- a/src-tauri/src/epg.rs +++ b/src-tauri/src/epg.rs @@ -233,3 +233,109 @@ pub async fn get_programs_by_channel( Ok(programs) } + +pub async fn get_epg_by_range( + start_time: String, + end_time: String, + skip: usize, + limit: usize, + playlist_channel_names: Vec, +) -> Result, String> { + println!("EPG Request - skip: {}, limit: {}", skip, limit); + println!("Channel names count: {}", playlist_channel_names.len()); + + match EPG_DATA.get() { + Some(epg_data) => { + let guard = epg_data.lock().map_err(|e| e.to_string())?; + let store = guard.as_ref().ok_or("EPG data not initialized")?; + + let mut result = Vec::new(); + + // Parse time range + let start = DateTime::parse_from_str(&start_time, "%Y%m%d%H%M%S %z") + .map_err(|e| e.to_string())? + .with_timezone(&Utc); + let end = DateTime::parse_from_str(&end_time, "%Y%m%d%H%M%S %z") + .map_err(|e| e.to_string())? + .with_timezone(&Utc); + + // First collect all channels with their programs + let mut all_channels_data = Vec::new(); + + // Collect all unique channel IDs across all sources that match playlist channel names + for source in &store.sources { + for (channel_id, programs) in &source.programs { + if let Some(first_program) = programs.first() { + let channel_name = first_program.channel.trim(); + if playlist_channel_names.iter().any(|name| name.trim() == channel_name) { + // Check if we already have this channel + if !all_channels_data.iter().any(|(id, _)| id == channel_id) { + let mut channel_programs = Vec::new(); + let mut channel_icon = None; + + // Collect programs from all sources for this channel + for source in &store.sources { + if let Some(progs) = source.programs.get(channel_id) { + let filtered_programs: Vec = progs + .iter() + .filter(|p| p.start <= end && p.stop >= start) + .cloned() + .collect(); + + if channel_icon.is_none() { + if let Some(prog) = filtered_programs.first() { + channel_icon = prog.icon.clone(); + } + } + + channel_programs.extend(filtered_programs); + } + } + + if !channel_programs.is_empty() { + // Sort programs by start time + channel_programs.sort_by(|a, b| a.start.cmp(&b.start)); + all_channels_data.push((channel_id.clone(), (channel_name.to_string(), channel_icon, channel_programs))); + } + } + } + } + } + } + + // Sort channels by name for consistent ordering + all_channels_data.sort_by(|a, b| a.1.0.cmp(&b.1.0)); + + println!("Total matching channels found: {}", all_channels_data.len()); + + // Now apply pagination to the full list of channels + let start_idx = skip.min(all_channels_data.len()); + let end_idx = (skip + limit).min(all_channels_data.len()); + let paginated_data = &all_channels_data[start_idx..end_idx]; + + println!("Returning channels from index {} to {}", start_idx, end_idx); + + // Convert to final format + for (channel_id, (channel_name, channel_icon, channel_programs)) in paginated_data { + result.push(EpgChannelWithPrograms { + id: channel_id.to_string(), + name: channel_name.clone(), + icon: channel_icon.clone(), + programs: channel_programs.clone(), + }); + } + + println!("Returning {} channels", result.len()); + Ok(result) + } + None => Err("EPG data not initialized".to_string()), + } +} + +#[derive(Debug, Serialize)] +pub struct EpgChannelWithPrograms { + pub id: String, + pub name: String, + pub icon: Option, + pub programs: Vec, +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 6a0ccd723..cae549bdb 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,106 +1,19 @@ -use log::info; -use std::path::Path; -use std::process::Command; - -#[tauri::command] -async fn open_in_mpv(url: String, path: String) -> Result<(), String> { - info!("Custom MVP path: {}", path); - let mpv_paths = if cfg!(target_os = "windows") { - vec![ - r"C:\Program Files\mpv\mpv.exe", - r"C:\Program Files (x86)\mpv\mpv.exe", - ] - } else if cfg!(target_os = "linux") { - vec!["/usr/bin/mpv", "/usr/local/bin/mpv", "/snap/bin/mpv"] - } else { - vec![ - "/Applications/mpv.app/Contents/MacOS/mpv", - "/opt/homebrew/bin/mpv", - "/usr/local/bin/mpv", - ] - }; - - let mpv_path = if !path.is_empty() && Path::new(&path).exists() { - path - } else { - mpv_paths - .iter() - .find(|&path| Path::new(path).exists()) - .map(|s| s.to_string()) - .unwrap_or_else(|| "mpv".to_string()) - }; - - info!("Using the following MPV player path: {}", mpv_path); - - Command::new(mpv_path) - .arg(&url) - .spawn() - .map_err(|e| e.to_string())?; - - Ok(()) -} - -#[tauri::command] -async fn open_in_vlc(url: String, path: String) -> Result<(), String> { - info!("Custom MVP path: {}", path); - let vlc_paths = if cfg!(target_os = "windows") { - vec![ - r"C:\Program Files\VideoLAN\VLC\vlc.exe", - r"C:\Program Files (x86)\VideoLAN\VLC\vlc.exe", - ] - } else if cfg!(target_os = "linux") { - vec!["/usr/bin/vlc", "/usr/local/bin/vlc", "/snap/bin/vlc"] - } else { - vec![ - "/Applications/VLC.app/Contents/MacOS/VLC", - "/opt/homebrew/bin/vlc", - "/usr/local/bin/vlc", - ] - }; - - let vlc_path = if !path.is_empty() && Path::new(&path).exists() { - path - } else { - vlc_paths - .iter() - .find(|&path| Path::new(path).exists()) - .map(|s| s.to_string()) - .unwrap_or_else(|| "vlc".to_string()) - }; - - info!("Using VLC player path: {}", vlc_path); - - Command::new(vlc_path) - .arg(&url) - .spawn() - .map_err(|e| e.to_string())?; - - Ok(()) -} - +mod commands; +mod database; mod epg; -use epg::get_programs_by_channel; -#[tauri::command] -async fn fetch_epg(url: Vec) -> Result<(), String> { - for single_url in url { - epg::fetch_and_parse_epg(single_url) - .await - .map_err(|e| e.to_string())?; - } - Ok(()) -} - -#[tauri::command] -async fn get_channel_programs(channel_id: String) -> Result, String> { - get_programs_by_channel(channel_id) - .await - .map_err(|e| e.to_string()) -} +use database::get_migrations; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { + let migrations = get_migrations(); + tauri::Builder::default() + .plugin( + tauri_plugin_sql::Builder::default() + .add_migrations("sqlite:database.db", migrations) + .build(), + ) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_updater::Builder::new().build()) @@ -110,10 +23,11 @@ pub fn run() { .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_http::init()) .invoke_handler(tauri::generate_handler![ - fetch_epg, - get_channel_programs, - open_in_mpv, - open_in_vlc + commands::epg::fetch_epg, + commands::epg::get_channel_programs, + commands::epg::get_epg_by_range, + commands::media::open_in_mpv, + commands::media::open_in_vlc ]) .setup(|app| { if cfg!(debug_assertions) {