Create new struct GrandMother and refactor to her
This commit is contained in:
		@@ -10,6 +10,6 @@ directories = "4.0.1"
 | 
				
			|||||||
futures-util = "0.3.25"
 | 
					futures-util = "0.3.25"
 | 
				
			||||||
indicatif = { version = "0.17.3", features = ["tokio"] }
 | 
					indicatif = { version = "0.17.3", features = ["tokio"] }
 | 
				
			||||||
reqwest = { version = "0.11.13", features = ["blocking", "deflate", "gzip", "rustls", "rustls-tls", "stream"] }
 | 
					reqwest = { version = "0.11.13", features = ["blocking", "deflate", "gzip", "rustls", "rustls-tls", "stream"] }
 | 
				
			||||||
serde = { version = "1.0.152", features = ["serde_derive"] }
 | 
					serde = { version = "1.0.152", features = ["serde_derive","rc"] }
 | 
				
			||||||
serde_json = "1.0.93"
 | 
					serde_json = "1.0.93"
 | 
				
			||||||
tokio = { version = "1.24.2", features = ["full"] }
 | 
					tokio = { version = "1.24.2", features = ["full"] }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										120
									
								
								src/config.rs
									
									
									
									
									
								
							
							
						
						
									
										120
									
								
								src/config.rs
									
									
									
									
									
								
							@@ -1,8 +1,9 @@
 | 
				
			|||||||
use std::{
 | 
					use std::{
 | 
				
			||||||
    fs::{self, File},
 | 
					    fs::{self, File},
 | 
				
			||||||
    io::{self, BufReader},
 | 
					    io::{self, BufReader},
 | 
				
			||||||
    ops::{Deref, DerefMut},
 | 
					    ops::Deref,
 | 
				
			||||||
    path::{Path, PathBuf},
 | 
					    path::{Path, PathBuf},
 | 
				
			||||||
 | 
					    rc::Rc,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use directories::ProjectDirs;
 | 
					use directories::ProjectDirs;
 | 
				
			||||||
@@ -10,20 +11,15 @@ use serde::{Deserialize, Serialize};
 | 
				
			|||||||
use serde_json;
 | 
					use serde_json;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    download_with_progress, downloader::DualWriter, get_mut_ref, m3u8::DataEntry, M3u8, Readline,
 | 
					    download_with_progress, get_mut_ref, m3u8::DataEntry, Readline, APP_IDENTIFIER,
 | 
				
			||||||
 | 
					    JSON_CONFIG_FILENAME, STANDARD_OFFLINE_FILENAME, STANDARD_PLAYLIST_FILENAME,
 | 
				
			||||||
 | 
					    STANDARD_SEEN_LINKS_FILENAME,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const JSON_CONFIG_FILENAME: &'static str = "config.json";
 | 
					 | 
				
			||||||
const APP_IDENTIFIER: [&'static str; 3] = ["com", "billenius", "ilovetv"];
 | 
					 | 
				
			||||||
const STANDARD_PLAYLIST_FILENAME: &'static str = "playlist.m3u8";
 | 
					 | 
				
			||||||
const STANDARD_SEEN_LINKS_FILENAME: &'static str = "watched_links.json";
 | 
					 | 
				
			||||||
const STANDARD_OFFLINE_FILENAME: &'static str = "ilovetv_offline.json";
 | 
					 | 
				
			||||||
const MAX_TRIES: u8 = 4;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Serialize, Deserialize, Debug)]
 | 
					#[derive(Serialize, Deserialize, Debug)]
 | 
				
			||||||
pub struct Conf {
 | 
					pub struct Conf {
 | 
				
			||||||
    pub playlist_filename: String,
 | 
					    pub playlist_filename: String,
 | 
				
			||||||
    pub playlist_url: String,
 | 
					    pub playlist_url: Rc<String>,
 | 
				
			||||||
    pub last_search: Option<String>,
 | 
					    pub last_search: Option<String>,
 | 
				
			||||||
    pub seen_links_filename: String,
 | 
					    pub seen_links_filename: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -45,7 +41,7 @@ impl Conf {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Get fresh config with url from user
 | 
					        // Get fresh config with url from user
 | 
				
			||||||
        let playlist_url = Self::user_setup();
 | 
					        let playlist_url = Self::user_setup().into();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(Self {
 | 
					        Ok(Self {
 | 
				
			||||||
            playlist_filename: STANDARD_PLAYLIST_FILENAME.to_owned(),
 | 
					            playlist_filename: STANDARD_PLAYLIST_FILENAME.to_owned(),
 | 
				
			||||||
@@ -83,7 +79,7 @@ impl Conf {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pub struct Configuration {
 | 
					pub struct Configuration {
 | 
				
			||||||
    pub conf: Conf,
 | 
					    pub conf: Conf,
 | 
				
			||||||
    pub playlist_path: PathBuf,
 | 
					    pub playlist_path: Rc<PathBuf>,
 | 
				
			||||||
    pub seen_links_path: PathBuf,
 | 
					    pub seen_links_path: PathBuf,
 | 
				
			||||||
    pub seen_links: Vec<String>,
 | 
					    pub seen_links: Vec<String>,
 | 
				
			||||||
    config_file_path: PathBuf,
 | 
					    config_file_path: PathBuf,
 | 
				
			||||||
@@ -115,7 +111,7 @@ impl Configuration {
 | 
				
			|||||||
        )?;
 | 
					        )?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Playlist
 | 
					        // Playlist
 | 
				
			||||||
        let playlist_path = cache_dir.join(&configuration.playlist_filename);
 | 
					        let playlist_path = cache_dir.join(&configuration.playlist_filename).into();
 | 
				
			||||||
        let seen_links_path = cache_dir.join(&configuration.seen_links_filename);
 | 
					        let seen_links_path = cache_dir.join(&configuration.seen_links_filename);
 | 
				
			||||||
        let seen_links = Self::get_watched(&seen_links_path).unwrap_or_default();
 | 
					        let seen_links = Self::get_watched(&seen_links_path).unwrap_or_default();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -156,12 +152,6 @@ impl Configuration {
 | 
				
			|||||||
        serde_json::from_reader(reader).ok()
 | 
					        serde_json::from_reader(reader).ok()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async fn just_download(&self) -> Result<String, String> {
 | 
					 | 
				
			||||||
        download_with_progress(&self.playlist_url, None)
 | 
					 | 
				
			||||||
            .await?
 | 
					 | 
				
			||||||
            .get_string()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn get_datafile_content(datafile: &PathBuf) -> Option<Vec<DataEntry>> {
 | 
					    fn get_datafile_content(datafile: &PathBuf) -> Option<Vec<DataEntry>> {
 | 
				
			||||||
        let reader = BufReader::new(File::open(datafile).ok()?);
 | 
					        let reader = BufReader::new(File::open(datafile).ok()?);
 | 
				
			||||||
        serde_json::from_reader(reader).ok()
 | 
					        serde_json::from_reader(reader).ok()
 | 
				
			||||||
@@ -175,95 +165,3 @@ impl Deref for Configuration {
 | 
				
			|||||||
        &self.conf
 | 
					        &self.conf
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
struct Playlist {
 | 
					 | 
				
			||||||
    pub entries: Vec<M3u8>,
 | 
					 | 
				
			||||||
    path_to_playlist: PathBuf,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Playlist {
 | 
					 | 
				
			||||||
    pub fn new(path_to_playlist: PathBuf) -> Self {
 | 
					 | 
				
			||||||
        todo!()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn write() -> Result<(), io::Error> {
 | 
					 | 
				
			||||||
        todo!()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn get_saved_playlist(path: &Path) -> Option<String> {
 | 
					 | 
				
			||||||
        if !Self::should_update_playlist(path) {
 | 
					 | 
				
			||||||
            return fs::read_to_string(path).ok();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        None
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn should_update_playlist(path: &Path) -> bool {
 | 
					 | 
				
			||||||
        fs::metadata(path)
 | 
					 | 
				
			||||||
            .and_then(|metadata| {
 | 
					 | 
				
			||||||
                Ok({
 | 
					 | 
				
			||||||
                    let seconds = metadata.modified()?;
 | 
					 | 
				
			||||||
                    seconds
 | 
					 | 
				
			||||||
                        .elapsed()
 | 
					 | 
				
			||||||
                        .expect("Failed to get systemtime")
 | 
					 | 
				
			||||||
                        .as_secs()
 | 
					 | 
				
			||||||
                        > 60 * 60 * 24 * 3
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            .map_or_else(
 | 
					 | 
				
			||||||
                |_| {
 | 
					 | 
				
			||||||
                    println!("Could not find playlist-file, Downloading a new one");
 | 
					 | 
				
			||||||
                    false
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                |x| x,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn get_playlist(&self, url: &str, path: &Path) -> Result<String, String> {
 | 
					 | 
				
			||||||
        let content = if let Some(content) = Self::get_saved_playlist(path) {
 | 
					 | 
				
			||||||
            content
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            let downloaded = Self::download_playlist(url).await?;
 | 
					 | 
				
			||||||
            if let Err(e) = fs::write(path, &downloaded) {
 | 
					 | 
				
			||||||
                println!(
 | 
					 | 
				
			||||||
                    "Failed to save downloaded playlist to file, {:?}, path: '{}'",
 | 
					 | 
				
			||||||
                    e,
 | 
					 | 
				
			||||||
                    path.as_os_str().to_str().unwrap()
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            downloaded
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(content)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn download_playlist(url: &str) -> Result<String, String> {
 | 
					 | 
				
			||||||
        let mut counter: u8 = 0;
 | 
					 | 
				
			||||||
        loop {
 | 
					 | 
				
			||||||
            counter += 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let downloaded = download_with_progress(url, None)
 | 
					 | 
				
			||||||
                .await
 | 
					 | 
				
			||||||
                .and_then(DualWriter::get_string);
 | 
					 | 
				
			||||||
            if let Ok(content) = downloaded {
 | 
					 | 
				
			||||||
                break Ok(content);
 | 
					 | 
				
			||||||
            } else if counter > MAX_TRIES {
 | 
					 | 
				
			||||||
                break Err("Failed to download playlist".to_owned());
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            println!("Retrying {}/{}", counter + 1, MAX_TRIES);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Deref for Playlist {
 | 
					 | 
				
			||||||
    type Target = Vec<M3u8>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn deref(&self) -> &Self::Target {
 | 
					 | 
				
			||||||
        &self.entries
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl DerefMut for Playlist {
 | 
					 | 
				
			||||||
    fn deref_mut(&mut self) -> &mut Self::Target {
 | 
					 | 
				
			||||||
        &mut self.entries
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										17
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								src/lib.rs
									
									
									
									
									
								
							@@ -1,14 +1,23 @@
 | 
				
			|||||||
 | 
					mod config;
 | 
				
			||||||
 | 
					mod downloader;
 | 
				
			||||||
mod m3u8;
 | 
					mod m3u8;
 | 
				
			||||||
mod parser;
 | 
					mod parser;
 | 
				
			||||||
 | 
					mod playlist;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::io::{stdin, stdout, Stdin, StdoutLock, Write};
 | 
					use std::io::{stdin, stdout, Stdin, StdoutLock, Write};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub use config::Configuration;
 | 
				
			||||||
 | 
					pub use downloader::download_with_progress;
 | 
				
			||||||
pub use m3u8::{DataEntry, M3u8};
 | 
					pub use m3u8::{DataEntry, M3u8};
 | 
				
			||||||
pub use parser::Parser;
 | 
					pub use parser::Parser;
 | 
				
			||||||
mod config;
 | 
					pub use playlist::{GrandMother, Playlist};
 | 
				
			||||||
pub use config::Configuration;
 | 
					 | 
				
			||||||
mod downloader;
 | 
					 | 
				
			||||||
pub use downloader::download_with_progress;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const JSON_CONFIG_FILENAME: &'static str = "config.json";
 | 
				
			||||||
 | 
					pub const APP_IDENTIFIER: [&'static str; 3] = ["com", "billenius", "ilovetv"];
 | 
				
			||||||
 | 
					pub const STANDARD_PLAYLIST_FILENAME: &'static str = "playlist.m3u8";
 | 
				
			||||||
 | 
					pub const STANDARD_SEEN_LINKS_FILENAME: &'static str = "watched_links.json";
 | 
				
			||||||
 | 
					pub const STANDARD_OFFLINE_FILENAME: &'static str = "ilovetv_offline.json";
 | 
				
			||||||
 | 
					pub const MAX_TRIES: u8 = 4;
 | 
				
			||||||
pub struct Readline<'a> {
 | 
					pub struct Readline<'a> {
 | 
				
			||||||
    stdout: StdoutLock<'a>,
 | 
					    stdout: StdoutLock<'a>,
 | 
				
			||||||
    stdin: Stdin,
 | 
					    stdin: Stdin,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										38
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -4,7 +4,8 @@ use std::rc::Rc;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use colored::Colorize;
 | 
					use colored::Colorize;
 | 
				
			||||||
use ilovetv::{
 | 
					use ilovetv::{
 | 
				
			||||||
    download_with_progress, get_mut_ref, Configuration, DataEntry, M3u8, Parser, Readline,
 | 
					    download_with_progress, get_mut_ref, Configuration, DataEntry, GrandMother, M3u8, Parser,
 | 
				
			||||||
 | 
					    Readline,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[tokio::main]
 | 
					#[tokio::main]
 | 
				
			||||||
@@ -32,9 +33,10 @@ async fn main() {
 | 
				
			|||||||
    .iter()
 | 
					    .iter()
 | 
				
			||||||
    .for_each(|s| println!("{}", &s));
 | 
					    .for_each(|s| println!("{}", &s));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let config = Rc::new(Configuration::new().expect("Failed to write to configfile"));
 | 
					    let gm = GrandMother::new(Configuration::new().expect("Failed to write to configfile"))
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
    let parser = Parser::new(config.clone()).await;
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					    // let parser = Parser::new(config.clone()).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut mpv_fs = false;
 | 
					    let mut mpv_fs = false;
 | 
				
			||||||
    let mut search_result: Option<Rc<Vec<&M3u8>>> = None;
 | 
					    let mut search_result: Option<Rc<Vec<&M3u8>>> = None;
 | 
				
			||||||
@@ -55,7 +57,7 @@ async fn main() {
 | 
				
			|||||||
                // Refresh playlist
 | 
					                // Refresh playlist
 | 
				
			||||||
                "r" => {
 | 
					                "r" => {
 | 
				
			||||||
                    search_result = None;
 | 
					                    search_result = None;
 | 
				
			||||||
                    refresh(&parser).await;
 | 
					                    gm.refresh_dirty().await;
 | 
				
			||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                // Toggle fullscreen for mpv
 | 
					                // Toggle fullscreen for mpv
 | 
				
			||||||
@@ -68,7 +70,7 @@ async fn main() {
 | 
				
			|||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                "l" => {
 | 
					                "l" => {
 | 
				
			||||||
                    search = if let Some(s) = config.last_search.as_ref() {
 | 
					                    search = if let Some(s) = gm.config.last_search.as_ref() {
 | 
				
			||||||
                        s
 | 
					                        s
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        println!("There is no search saved from earlier");
 | 
					                        println!("There is no search saved from earlier");
 | 
				
			||||||
@@ -76,19 +78,19 @@ async fn main() {
 | 
				
			|||||||
                    };
 | 
					                    };
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                "c" => {
 | 
					                "c" => {
 | 
				
			||||||
                    config.update_last_search_ugly(None);
 | 
					                    gm.config.update_last_search_ugly(None);
 | 
				
			||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                _ => {}
 | 
					                _ => {}
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            search_result = Some(Rc::new(parser.find(search)));
 | 
					            search_result = Some(Rc::new(gm.parser.find(search)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if search_result.as_ref().unwrap().is_empty() {
 | 
					            if search_result.as_ref().unwrap().is_empty() {
 | 
				
			||||||
                println!("Nothing found");
 | 
					                println!("Nothing found");
 | 
				
			||||||
                search_result = None;
 | 
					                search_result = None;
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            config.update_last_search_ugly(Some(search.to_owned()));
 | 
					            gm.config.update_last_search_ugly(Some(search.to_owned()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Let them choose which one to stream
 | 
					        // Let them choose which one to stream
 | 
				
			||||||
@@ -114,7 +116,7 @@ async fn main() {
 | 
				
			|||||||
            "r" => {
 | 
					            "r" => {
 | 
				
			||||||
                println!("Refreshing local m3u8-file");
 | 
					                println!("Refreshing local m3u8-file");
 | 
				
			||||||
                search_result = None;
 | 
					                search_result = None;
 | 
				
			||||||
                refresh(&parser).await;
 | 
					                gm.refresh_dirty().await;
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            "f" => {
 | 
					            "f" => {
 | 
				
			||||||
@@ -140,14 +142,14 @@ async fn main() {
 | 
				
			|||||||
                    ask_which_to_download(&mut readline, &search_result.as_ref().unwrap());
 | 
					                    ask_which_to_download(&mut readline, &search_result.as_ref().unwrap());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                for to_download in download_selections.iter() {
 | 
					                for to_download in download_selections.iter() {
 | 
				
			||||||
                    let path = config.data_dir.join(&to_download.name);
 | 
					                    let path = gm.config.data_dir.join(&to_download.name);
 | 
				
			||||||
                    let path = path.to_string_lossy().to_string();
 | 
					                    let path = path.to_string_lossy().to_string();
 | 
				
			||||||
                    download_m3u8(to_download, Some(&path)).await;
 | 
					                    download_m3u8(to_download, Some(&path)).await;
 | 
				
			||||||
                    let data_entry = DataEntry::new((*to_download).clone(), path);
 | 
					                    let data_entry = DataEntry::new((*to_download).clone(), path);
 | 
				
			||||||
                    config.push_datafile_ugly(data_entry);
 | 
					                    gm.config.push_datafile_ugly(data_entry);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if let Err(e) = config.write_datafile() {
 | 
					                if let Err(e) = gm.config.write_datafile() {
 | 
				
			||||||
                    println!(
 | 
					                    println!(
 | 
				
			||||||
                        "Failed to information about downloaded entries for offline use {:?}",
 | 
					                        "Failed to information about downloaded entries for offline use {:?}",
 | 
				
			||||||
                        e
 | 
					                        e
 | 
				
			||||||
@@ -162,7 +164,7 @@ async fn main() {
 | 
				
			|||||||
            Ok(k) => {
 | 
					            Ok(k) => {
 | 
				
			||||||
                let search_result = search_result.as_ref().unwrap();
 | 
					                let search_result = search_result.as_ref().unwrap();
 | 
				
			||||||
                stream(search_result[k - 1], mpv_fs);
 | 
					                stream(search_result[k - 1], mpv_fs);
 | 
				
			||||||
                parser.save_watched();
 | 
					                gm.save_watched();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Err(e) => println!("Have to be a valid number! {:?}", e),
 | 
					            Err(e) => println!("Have to be a valid number! {:?}", e),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -251,11 +253,3 @@ fn stream(m3u8item: &M3u8, launch_in_fullscreen: bool) {
 | 
				
			|||||||
        .output()
 | 
					        .output()
 | 
				
			||||||
        .expect("Could not listen for output");
 | 
					        .expect("Could not listen for output");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * I know that this is also frowned upon, but it is perfectly safe right here,
 | 
					 | 
				
			||||||
 * even though the borrowchecker complains
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
async fn refresh(parser: &Parser) {
 | 
					 | 
				
			||||||
    unsafe { get_mut_ref(parser) }.forcefully_update().await;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,24 +1,15 @@
 | 
				
			|||||||
use std::fs;
 | 
					 | 
				
			||||||
use std::ops::Deref;
 | 
					use std::ops::Deref;
 | 
				
			||||||
use std::rc::Rc;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::m3u8::M3u8;
 | 
					use crate::m3u8::M3u8;
 | 
				
			||||||
use crate::Configuration;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const MAX_TRIES: usize = 4;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct Parser {
 | 
					pub struct Parser {
 | 
				
			||||||
    configuration: Rc<Configuration>,
 | 
					 | 
				
			||||||
    m3u8_items: Vec<M3u8>,
 | 
					    m3u8_items: Vec<M3u8>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Parser {
 | 
					impl Parser {
 | 
				
			||||||
    pub async fn new(configuration: Rc<Configuration>) -> Self {
 | 
					    pub async fn new(m3u_content: &str, watched_links: &Vec<&str>) -> Self {
 | 
				
			||||||
        let m3u8_items = Self::get_parsed_m3u8(&configuration).await.unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            configuration,
 | 
					            m3u8_items: Self::parse_m3u8(m3u_content, watched_links),
 | 
				
			||||||
            m3u8_items,
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -30,41 +21,34 @@ impl Parser {
 | 
				
			|||||||
            .collect()
 | 
					            .collect()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn forcefully_update(&mut self) {
 | 
					    /*
 | 
				
			||||||
        let mut counter = 0;
 | 
					     * I know that this is also frowned upon, but it is perfectly safe right here,
 | 
				
			||||||
        let content = loop {
 | 
					     * even though the borrowchecker complains
 | 
				
			||||||
            counter += 1;
 | 
					     */
 | 
				
			||||||
            let content = self.download_playlist().await;
 | 
					    // async fn refresh(&self) {
 | 
				
			||||||
            if counter > MAX_TRIES {
 | 
					    //     unsafe { get_mut_ref(&self.pla) }.forcefully_update().await;
 | 
				
			||||||
                return;
 | 
					    // }
 | 
				
			||||||
            } else if content.is_ok() {
 | 
					 | 
				
			||||||
                break content.unwrap();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            println!("Retrying {}/{}", counter, MAX_TRIES);
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.m3u8_items = Self::parse_m3u8(content, &self.seen_links);
 | 
					    pub async fn forcefully_update(&mut self, content: &str) {
 | 
				
			||||||
    }
 | 
					        let seen_links: &Vec<&str> = &self
 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn save_watched(&self) {
 | 
					 | 
				
			||||||
        let watched_items = self
 | 
					 | 
				
			||||||
            .m3u8_items
 | 
					            .m3u8_items
 | 
				
			||||||
            .iter()
 | 
					            .iter()
 | 
				
			||||||
            .filter(|item| item.watched)
 | 
					            .filter(|x| x.watched)
 | 
				
			||||||
            .map(|item| item.link.clone())
 | 
					            .map(|x| x.link.as_str())
 | 
				
			||||||
            .collect::<Vec<String>>();
 | 
					            .collect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let resp = fs::write(
 | 
					        self.m3u8_items = Self::parse_m3u8(content, seen_links);
 | 
				
			||||||
            &self.seen_links_path,
 | 
					 | 
				
			||||||
            serde_json::to_string(&watched_items).unwrap(),
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        if let Err(e) = resp {
 | 
					 | 
				
			||||||
            eprintln!("Failed to write watched links {:?}", e);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn parse_m3u8(content: String, watched_links: &Vec<String>) -> Vec<M3u8> {
 | 
					    pub fn get_watched(&self) -> Vec<&String> {
 | 
				
			||||||
 | 
					        self.m3u8_items
 | 
				
			||||||
 | 
					            .iter()
 | 
				
			||||||
 | 
					            .filter(|x| x.watched)
 | 
				
			||||||
 | 
					            .map(|x| &x.link)
 | 
				
			||||||
 | 
					            .collect()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn parse_m3u8(content: &str, watched_links: &Vec<&str>) -> Vec<M3u8> {
 | 
				
			||||||
        let mut m3u8_items: Vec<M3u8> = Vec::new();
 | 
					        let mut m3u8_items: Vec<M3u8> = Vec::new();
 | 
				
			||||||
        let interesting_lines: Vec<String> = content
 | 
					        let interesting_lines: Vec<String> = content
 | 
				
			||||||
            .replacen("#EXTM3U\n", "", 1)
 | 
					            .replacen("#EXTM3U\n", "", 1)
 | 
				
			||||||
@@ -85,8 +69,8 @@ impl Parser {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            let name_start = interesting_lines[i].rfind(",").unwrap() + 1;
 | 
					            let name_start = interesting_lines[i].rfind(",").unwrap() + 1;
 | 
				
			||||||
            let name = &interesting_lines[i][name_start..];
 | 
					            let name = &interesting_lines[i][name_start..];
 | 
				
			||||||
            let link = &interesting_lines[i + 1];
 | 
					            let link = interesting_lines[i + 1].as_str();
 | 
				
			||||||
            let is_watched = watched_links.contains(link);
 | 
					            let is_watched = watched_links.contains(&link);
 | 
				
			||||||
            let m3u8_item = M3u8 {
 | 
					            let m3u8_item = M3u8 {
 | 
				
			||||||
                tvg_id: items[0].to_owned(),
 | 
					                tvg_id: items[0].to_owned(),
 | 
				
			||||||
                tvg_name: items[1].to_owned(),
 | 
					                tvg_name: items[1].to_owned(),
 | 
				
			||||||
@@ -100,19 +84,12 @@ impl Parser {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        m3u8_items
 | 
					        m3u8_items
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    async fn get_parsed_m3u8(config: &Configuration) -> Result<Vec<M3u8>, String> {
 | 
					 | 
				
			||||||
        Ok(Self::parse_m3u8(
 | 
					 | 
				
			||||||
            config.get_playlist().await?,
 | 
					 | 
				
			||||||
            &config.seen_links,
 | 
					 | 
				
			||||||
        ))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Deref for Parser {
 | 
					impl Deref for Parser {
 | 
				
			||||||
    type Target = Configuration;
 | 
					    type Target = Vec<M3u8>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn deref(&self) -> &Self::Target {
 | 
					    fn deref(&self) -> &Self::Target {
 | 
				
			||||||
        &self.configuration
 | 
					        &self.m3u8_items
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										165
									
								
								src/playlist.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								src/playlist.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,165 @@
 | 
				
			|||||||
 | 
					use std::{
 | 
				
			||||||
 | 
					    borrow::BorrowMut,
 | 
				
			||||||
 | 
					    fs,
 | 
				
			||||||
 | 
					    ops::{Deref, DerefMut},
 | 
				
			||||||
 | 
					    path::PathBuf,
 | 
				
			||||||
 | 
					    rc::Rc,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{download_with_progress, downloader::DualWriter, Configuration, Parser, MAX_TRIES};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Playlist {
 | 
				
			||||||
 | 
					    pub content: String,
 | 
				
			||||||
 | 
					    path_to_playlist: Rc<PathBuf>,
 | 
				
			||||||
 | 
					    url: Rc<String>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Playlist {
 | 
				
			||||||
 | 
					    pub async fn new(path_to_playlist: Rc<PathBuf>, url: Rc<String>) -> Result<Self, String> {
 | 
				
			||||||
 | 
					        let mut me = Self {
 | 
				
			||||||
 | 
					            content: String::new(),
 | 
				
			||||||
 | 
					            path_to_playlist,
 | 
				
			||||||
 | 
					            url,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        me.content = me.get_saved_or_download().await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(me)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_saved(&self) -> Option<String> {
 | 
				
			||||||
 | 
					        if !self.should_update() {
 | 
				
			||||||
 | 
					            return fs::read_to_string(&*self.path_to_playlist).ok();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        None
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn should_update(&self) -> bool {
 | 
				
			||||||
 | 
					        fs::metadata(&*self.path_to_playlist)
 | 
				
			||||||
 | 
					            .and_then(|metadata| {
 | 
				
			||||||
 | 
					                Ok({
 | 
				
			||||||
 | 
					                    let seconds = metadata.modified()?;
 | 
				
			||||||
 | 
					                    seconds
 | 
				
			||||||
 | 
					                        .elapsed()
 | 
				
			||||||
 | 
					                        .expect("Failed to get systemtime")
 | 
				
			||||||
 | 
					                        .as_secs()
 | 
				
			||||||
 | 
					                        > 60 * 60 * 24 * 3
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .map_or_else(
 | 
				
			||||||
 | 
					                |_| {
 | 
				
			||||||
 | 
					                    println!("Could not find playlist-file, Downloading a new one");
 | 
				
			||||||
 | 
					                    false
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                |x| x,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn get_saved_or_download(&self) -> Result<String, String> {
 | 
				
			||||||
 | 
					        let content = if let Some(content) = self.get_saved() {
 | 
				
			||||||
 | 
					            content
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            let downloaded = self.download().await?;
 | 
				
			||||||
 | 
					            if let Err(e) = fs::write(&*self.path_to_playlist, &downloaded) {
 | 
				
			||||||
 | 
					                println!(
 | 
				
			||||||
 | 
					                    "Failed to save downloaded playlist to file, {:?}, path: '{}'",
 | 
				
			||||||
 | 
					                    e,
 | 
				
			||||||
 | 
					                    &self.path_to_playlist.as_os_str().to_str().unwrap()
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            downloaded
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(content)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn download(&self) -> Result<String, String> {
 | 
				
			||||||
 | 
					        let mut counter: u8 = 0;
 | 
				
			||||||
 | 
					        loop {
 | 
				
			||||||
 | 
					            counter += 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let downloaded = download_with_progress(&self.url, None)
 | 
				
			||||||
 | 
					                .await
 | 
				
			||||||
 | 
					                .and_then(DualWriter::get_string);
 | 
				
			||||||
 | 
					            if let Ok(content) = downloaded {
 | 
				
			||||||
 | 
					                break Ok(content);
 | 
				
			||||||
 | 
					            } else if counter > MAX_TRIES {
 | 
				
			||||||
 | 
					                break Err("Failed to download playlist".to_owned());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            println!("Retrying {}/{}", counter + 1, MAX_TRIES);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Deref for Playlist {
 | 
				
			||||||
 | 
					    type Target = String;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn deref(&self) -> &Self::Target {
 | 
				
			||||||
 | 
					        &self.content
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl DerefMut for Playlist {
 | 
				
			||||||
 | 
					    fn deref_mut(&mut self) -> &mut Self::Target {
 | 
				
			||||||
 | 
					        &mut self.content
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct GrandMother {
 | 
				
			||||||
 | 
					    pub parser: Parser,
 | 
				
			||||||
 | 
					    pub playlist: Playlist,
 | 
				
			||||||
 | 
					    pub config: Configuration,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl GrandMother {
 | 
				
			||||||
 | 
					    pub async fn new(config: Configuration) -> Result<Self, String> {
 | 
				
			||||||
 | 
					        let playlist = Playlist::new(config.playlist_path.clone(), config.playlist_url.clone());
 | 
				
			||||||
 | 
					        let seen_links = config.seen_links.iter().map(|x| x.as_str()).collect();
 | 
				
			||||||
 | 
					        let playlist = playlist.await?;
 | 
				
			||||||
 | 
					        let playlist_content = playlist.get_saved_or_download().await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let parser = Parser::new(&playlist_content, &seen_links).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(Self {
 | 
				
			||||||
 | 
					            parser,
 | 
				
			||||||
 | 
					            playlist,
 | 
				
			||||||
 | 
					            config,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn refresh_dirty(&self) {
 | 
				
			||||||
 | 
					        let ptr = self as *const Self as *mut Self;
 | 
				
			||||||
 | 
					        unsafe { &mut *ptr }.refresh().await;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn refresh(&mut self) {
 | 
				
			||||||
 | 
					        let mut counter = 0;
 | 
				
			||||||
 | 
					        let content = loop {
 | 
				
			||||||
 | 
					            counter += 1;
 | 
				
			||||||
 | 
					            let content = self.playlist.download().await;
 | 
				
			||||||
 | 
					            if counter > MAX_TRIES {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if let Ok(content) = content {
 | 
				
			||||||
 | 
					                break content;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            println!("Retrying {}/{}", counter, MAX_TRIES);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let watched_links = self.parser.get_watched();
 | 
				
			||||||
 | 
					        let watched_links = watched_links.iter().map(|x| x.as_str()).collect();
 | 
				
			||||||
 | 
					        self.parser = Parser::new(&content, &watched_links).await;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn save_watched(&self) {
 | 
				
			||||||
 | 
					        let watched_items = self.parser.get_watched();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let resp = fs::write(
 | 
				
			||||||
 | 
					            &self.config.seen_links_path,
 | 
				
			||||||
 | 
					            serde_json::to_string(&watched_items).unwrap(),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if let Err(e) = resp {
 | 
				
			||||||
 | 
					            eprintln!("Failed to write watched links {:?}", e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user