Early working version
This commit is contained in:
parent
2b30b5335f
commit
81006d4c5c
@ -1,3 +1,5 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::M3u8;
|
||||
|
||||
pub trait GetM3u8 {
|
||||
@ -9,10 +11,7 @@ pub trait WatchedFind {
|
||||
fn get_watched(&self) -> Vec<&M3u8>;
|
||||
}
|
||||
|
||||
impl<T> WatchedFind for T
|
||||
where
|
||||
T: GetM3u8,
|
||||
{
|
||||
impl<T: ?Sized + GetM3u8> WatchedFind for T {
|
||||
fn find(&self, name: &str) -> Vec<&M3u8> {
|
||||
let name = name.to_lowercase();
|
||||
self.get_m3u8()
|
||||
@ -25,3 +24,9 @@ where
|
||||
self.get_m3u8().into_iter().filter(|x| x.watched).collect()
|
||||
}
|
||||
}
|
||||
pub trait GetPlayPath {
|
||||
fn get_path_to_play(&self, link: Rc<String>) -> Result<Rc<String>, String>;
|
||||
}
|
||||
|
||||
pub trait M3u8PlayPath: GetM3u8 + GetPlayPath {}
|
||||
impl<T: GetM3u8 + GetPlayPath> M3u8PlayPath for T {}
|
||||
|
@ -1,44 +1,61 @@
|
||||
#[allow(unused_imports)]
|
||||
use crate::GetM3u8;
|
||||
use crate::{
|
||||
getm3u8::{M3u8PlayPath, WatchedFind},
|
||||
Configuration, OfflineParser, Parser, Playlist, MAX_TRIES,
|
||||
};
|
||||
use std::fs;
|
||||
|
||||
use crate::{getm3u8::WatchedFind, Configuration, GetM3u8, Parser, Playlist, MAX_TRIES};
|
||||
type Error = String;
|
||||
|
||||
pub struct GrandMother<T>
|
||||
where
|
||||
T: GetM3u8,
|
||||
{
|
||||
pub parser: T,
|
||||
pub playlist: Playlist,
|
||||
pub struct GrandMother {
|
||||
pub parser: Box<dyn M3u8PlayPath>,
|
||||
pub playlist: Option<Playlist>,
|
||||
pub config: Configuration,
|
||||
}
|
||||
|
||||
impl GrandMother<Parser> {
|
||||
pub async fn new(config: Configuration) -> Result<Self, String> {
|
||||
impl GrandMother {
|
||||
pub async fn new(config: Configuration) -> Result<Self, Error> {
|
||||
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;
|
||||
let parser: Box<dyn M3u8PlayPath> =
|
||||
Box::new(Parser::new(&playlist_content, &seen_links).await);
|
||||
|
||||
Ok(Self {
|
||||
parser,
|
||||
playlist,
|
||||
playlist: Some(playlist),
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn refresh_dirty(&self) {
|
||||
let ptr = self as *const Self as *mut Self;
|
||||
unsafe { &mut *ptr }.refresh().await;
|
||||
pub fn new_offline(config: Configuration) -> Self {
|
||||
let parser: Box<dyn M3u8PlayPath> = Box::new(OfflineParser::new(&config));
|
||||
Self {
|
||||
parser,
|
||||
playlist: None,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn refresh(&mut self) {
|
||||
pub async fn refresh_dirty(&self) -> Result<(), Error> {
|
||||
let ptr = self as *const Self as *mut Self;
|
||||
unsafe { &mut *ptr }.refresh().await
|
||||
}
|
||||
|
||||
pub async fn refresh(&mut self) -> Result<(), Error> {
|
||||
let mut counter = 0;
|
||||
let content = loop {
|
||||
counter += 1;
|
||||
let content = self.playlist.download().await;
|
||||
let content = self
|
||||
.playlist
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Cannot refresh playlist in offlinemode")?
|
||||
.download()
|
||||
.await;
|
||||
if counter > MAX_TRIES {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
if let Ok(content) = content {
|
||||
break content;
|
||||
@ -52,7 +69,9 @@ impl GrandMother<Parser> {
|
||||
.iter()
|
||||
.map(|x| x.link.as_str())
|
||||
.collect();
|
||||
self.parser = Parser::new(&content, &watched_links).await;
|
||||
self.parser = Box::new(Parser::new(&content, &watched_links).await);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save_watched(&self) {
|
||||
|
37
src/lib.rs
37
src/lib.rs
@ -1,22 +1,23 @@
|
||||
mod config;
|
||||
mod downloader;
|
||||
pub mod getm3u8;
|
||||
mod grandmother;
|
||||
mod m3u8;
|
||||
mod opt;
|
||||
mod parser;
|
||||
mod playlist;
|
||||
mod getm3u8;
|
||||
|
||||
use std::io::{stdin, stdout, Stdin, StdoutLock, Write};
|
||||
|
||||
use async_recursion::async_recursion;
|
||||
pub use config::Configuration;
|
||||
pub use downloader::download_with_progress;
|
||||
pub use getm3u8::{GetM3u8, GetPlayPath, WatchedFind};
|
||||
pub use grandmother::GrandMother;
|
||||
pub use m3u8::{OfflineEntry, M3u8};
|
||||
pub use m3u8::{M3u8, OfflineEntry};
|
||||
pub use opt::{Mode, Opt};
|
||||
pub use parser::Parser;
|
||||
pub use parser::{OfflineParser, Parser};
|
||||
pub use playlist::Playlist;
|
||||
pub use getm3u8::GetM3u8;
|
||||
|
||||
pub const JSON_CONFIG_FILENAME: &'static str = "config.json";
|
||||
pub const APP_IDENTIFIER: [&'static str; 3] = ["com", "billenius", "ilovetv"];
|
||||
@ -56,3 +57,31 @@ pub unsafe fn get_mut_ref<T>(reference: &T) -> &mut T {
|
||||
let ptr = reference as *const T as *mut T;
|
||||
&mut *ptr
|
||||
}
|
||||
|
||||
#[async_recursion(?Send)]
|
||||
pub async fn get_gm(
|
||||
mode: Mode,
|
||||
readline: &mut Readline<'_>,
|
||||
config: Configuration,
|
||||
) -> Result<GrandMother, String> {
|
||||
match mode {
|
||||
Mode::Online => GrandMother::new(config).await,
|
||||
Mode::Offline => Ok(GrandMother::new_offline(config)),
|
||||
Mode::Ask => loop {
|
||||
let input = readline
|
||||
.input("Online/Offline mode? [1/2] ")
|
||||
.trim()
|
||||
.parse::<u8>();
|
||||
if let Ok(num) = input {
|
||||
if num == 1 {
|
||||
return get_gm(Mode::Online, readline, config).await;
|
||||
} else if num == 2 {
|
||||
return get_gm(Mode::Offline, readline, config).await;
|
||||
}
|
||||
println!("Has to be either 1 (Onine) or 2 (Offline)");
|
||||
} else {
|
||||
println!("Has to be a number");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
27
src/m3u8.rs
27
src/m3u8.rs
@ -2,7 +2,8 @@ use colored::Colorize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt::Display, ops::Deref, rc::Rc};
|
||||
|
||||
use crate::{Configuration, GetM3u8};
|
||||
#[allow(unused_imports)]
|
||||
use crate::GetM3u8;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Hash)]
|
||||
pub struct M3u8 {
|
||||
@ -11,7 +12,7 @@ pub struct M3u8 {
|
||||
pub tvg_logo: String,
|
||||
pub group_title: String,
|
||||
pub name: String,
|
||||
pub link: String,
|
||||
pub link: Rc<String>,
|
||||
pub watched: bool,
|
||||
}
|
||||
|
||||
@ -30,11 +31,11 @@ impl Display for M3u8 {
|
||||
#[derive(Serialize, Deserialize, Clone, Hash)]
|
||||
pub struct OfflineEntry {
|
||||
m3u8: M3u8,
|
||||
pub path: String,
|
||||
pub path: Rc<String>,
|
||||
}
|
||||
|
||||
impl OfflineEntry {
|
||||
pub fn new(m3u8: M3u8, path: String) -> Self {
|
||||
pub fn new(m3u8: M3u8, path: Rc<String>) -> Self {
|
||||
Self { m3u8, path }
|
||||
}
|
||||
}
|
||||
@ -46,21 +47,3 @@ impl Deref for OfflineEntry {
|
||||
&self.m3u8
|
||||
}
|
||||
}
|
||||
|
||||
struct OfflineParser {
|
||||
entries: Rc<Vec<OfflineEntry>>,
|
||||
}
|
||||
|
||||
impl OfflineParser {
|
||||
pub fn new(conf: &Configuration) -> Self {
|
||||
Self {
|
||||
entries: conf.offlinefile_content.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetM3u8 for OfflineParser {
|
||||
fn get_m3u8(&self) -> Vec<&M3u8> {
|
||||
self.entries.iter().map(|x| &**x).collect()
|
||||
}
|
||||
}
|
||||
|
83
src/main.rs
83
src/main.rs
@ -2,14 +2,16 @@ use std::num::ParseIntError;
|
||||
use std::process::Command;
|
||||
use std::rc::Rc;
|
||||
|
||||
use async_recursion::async_recursion;
|
||||
use colored::Colorize;
|
||||
use ilovetv::{
|
||||
download_with_progress, get_mut_ref, Configuration, GetM3u8, GrandMother, M3u8, Mode,
|
||||
OfflineEntry, Opt, Parser, Readline,
|
||||
};
|
||||
use structopt::StructOpt;
|
||||
|
||||
use ilovetv::{
|
||||
download_with_progress, get_gm, get_mut_ref, Configuration, M3u8, OfflineEntry, Opt, Readline,
|
||||
WatchedFind,
|
||||
};
|
||||
#[allow(unused_imports)]
|
||||
use ilovetv::{GetM3u8, GetPlayPath, OfflineParser};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let opt = Opt::from_args();
|
||||
@ -37,14 +39,21 @@ async fn main() {
|
||||
.iter()
|
||||
.for_each(|s| println!("{}", &s));
|
||||
|
||||
let gm = GrandMother::new(Configuration::new().expect("Failed to write to configfile"))
|
||||
.await
|
||||
.unwrap();
|
||||
// let gm = GrandMother::new(Configuration::new().expect("Failed to write to configfile"))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let parser = Parser::new(config.clone()).await;
|
||||
|
||||
let mut mpv_fs = false;
|
||||
let mut search_result: Option<Rc<Vec<&M3u8>>> = None;
|
||||
let mut readline = Readline::new();
|
||||
let gm = get_gm(
|
||||
opt.mode,
|
||||
&mut readline,
|
||||
Configuration::new().expect("Failed to write to configfile"),
|
||||
)
|
||||
.await
|
||||
.expect("Failed to retrive online playlist");
|
||||
|
||||
loop {
|
||||
// Dont't perform a search if user has just watched, instead present the previous search
|
||||
@ -61,7 +70,12 @@ async fn main() {
|
||||
// Refresh playlist
|
||||
"r" => {
|
||||
search_result = None;
|
||||
gm.refresh_dirty().await;
|
||||
if let Err(e) = gm.refresh_dirty().await {
|
||||
println!(
|
||||
"Cannot refresh. This is probably due to offlinemode {:?}",
|
||||
e
|
||||
);
|
||||
};
|
||||
continue;
|
||||
}
|
||||
// Toggle fullscreen for mpv
|
||||
@ -120,7 +134,12 @@ async fn main() {
|
||||
"r" => {
|
||||
println!("Refreshing local m3u8-file");
|
||||
search_result = None;
|
||||
gm.refresh_dirty().await;
|
||||
if let Err(e) = gm.refresh_dirty().await {
|
||||
println!(
|
||||
"Cannot refresh. This is probably due to offlinemode {:?}",
|
||||
e
|
||||
);
|
||||
};
|
||||
continue;
|
||||
}
|
||||
"f" => {
|
||||
@ -147,7 +166,7 @@ async fn main() {
|
||||
|
||||
for to_download in download_selections.iter() {
|
||||
let path = gm.config.data_dir.join(&to_download.name);
|
||||
let path = path.to_string_lossy().to_string();
|
||||
let path = Rc::new(path.to_string_lossy().to_string());
|
||||
download_m3u8(to_download, Some(&path)).await;
|
||||
let data_entry = OfflineEntry::new((*to_download).clone(), path);
|
||||
gm.config.push_offlinefile_ugly(data_entry);
|
||||
@ -167,7 +186,15 @@ async fn main() {
|
||||
match choosen {
|
||||
Ok(k) => {
|
||||
let search_result = search_result.as_ref().unwrap();
|
||||
stream(search_result[k - 1], mpv_fs);
|
||||
let to_play = search_result[k - 1];
|
||||
let path_link = if let Ok(link) = gm.parser.get_path_to_play(to_play.link.clone()) {
|
||||
link
|
||||
} else {
|
||||
println!("Not possible to refresh playlist while in offlinemode");
|
||||
continue;
|
||||
};
|
||||
|
||||
stream(to_play, &*path_link, mpv_fs);
|
||||
gm.save_watched();
|
||||
}
|
||||
Err(e) => println!("Have to be a valid number! {:?}", e),
|
||||
@ -175,34 +202,6 @@ async fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_recursion(?Send)]
|
||||
async fn get_gm(
|
||||
mode: Mode,
|
||||
readline: &mut Readline<'_>,
|
||||
config: Configuration,
|
||||
) -> Result<GrandMother<Parser>, String> {
|
||||
match mode {
|
||||
Mode::Online => GrandMother::new(config).await,
|
||||
Mode::Offline => GrandMother::new_in_offline(config),
|
||||
Mode::Ask => loop {
|
||||
let input = readline
|
||||
.input("Online/Offline mode? [1/2]")
|
||||
.trim()
|
||||
.parse::<u8>();
|
||||
if let Ok(num) = input {
|
||||
if num == 1 {
|
||||
return get_gm(Mode::Online, readline, config).await;
|
||||
} else if num == 2 {
|
||||
return get_gm(Mode::Offline, readline, config).await;
|
||||
}
|
||||
println!("Has to be either 1 (Onine) or 2 (Offline)");
|
||||
} else {
|
||||
println!("Has to be a number");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn ask_which_to_download<'a>(
|
||||
readline: &mut Readline,
|
||||
search_result: &Rc<Vec<&'a M3u8>>,
|
||||
@ -272,10 +271,10 @@ async fn download_m3u8(file_to_download: &M3u8, path: Option<&str>) {
|
||||
* in this context and also the most efficient way.
|
||||
* With other words, it's BLAZINGLY FAST
|
||||
*/
|
||||
fn stream(m3u8item: &M3u8, launch_in_fullscreen: bool) {
|
||||
fn stream(m3u8item: &M3u8, link: &String, launch_in_fullscreen: bool) {
|
||||
let mut m3u8item = unsafe { get_mut_ref(m3u8item) };
|
||||
m3u8item.watched = true;
|
||||
let mut args: Vec<&str> = vec![&m3u8item.link];
|
||||
let mut args: Vec<&str> = vec![link];
|
||||
if launch_in_fullscreen {
|
||||
args.push("--fs");
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ pub struct Opt {
|
||||
#[structopt(short, long, default_value = "ask")]
|
||||
/// Choose whether to launch in offlinemode, onlinemode or wheter to ask during startup.
|
||||
/// In offlinemode it's only possible to watch downloaded entries
|
||||
pub offline_mode: Mode,
|
||||
pub mode: Mode,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -1,12 +1,8 @@
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::BufReader,
|
||||
ops::Deref,
|
||||
};
|
||||
use std::{ops::Deref, rc::Rc};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{m3u8::M3u8, Configuration, GetM3u8};
|
||||
use crate::{m3u8::M3u8, Configuration, GetM3u8, GetPlayPath, OfflineEntry};
|
||||
|
||||
pub struct Parser {
|
||||
m3u8_items: Vec<M3u8>,
|
||||
@ -75,7 +71,7 @@ impl Parser {
|
||||
tvg_logo: items[2].to_owned(),
|
||||
group_title: items[3].to_owned(),
|
||||
name: name.to_owned(),
|
||||
link: link.to_string(),
|
||||
link: Rc::new(link.to_string()),
|
||||
watched: is_watched,
|
||||
};
|
||||
m3u8_items.push(m3u8_item);
|
||||
@ -98,17 +94,36 @@ impl GetM3u8 for Parser {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct OfflineEntry {
|
||||
m3u8: M3u8,
|
||||
path: String,
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
struct OfflineParser {
|
||||
m3u8_items: Vec<OfflineEntry>,
|
||||
}
|
||||
impl OfflineParser {
|
||||
pub fn new(config: &Configuration) -> Result<Self, std::io::Error> {
|
||||
todo!()
|
||||
impl GetPlayPath for Parser {
|
||||
fn get_path_to_play<'a>(&'a self, link: Rc<String>) -> Result<Rc<String>, String> {
|
||||
Ok(link.clone())
|
||||
}
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
pub struct OfflineParser {
|
||||
m3u8_items: Rc<Vec<OfflineEntry>>,
|
||||
}
|
||||
impl OfflineParser {
|
||||
pub fn new(config: &Configuration) -> Self {
|
||||
Self {
|
||||
m3u8_items: config.offlinefile_content.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetPlayPath for OfflineParser {
|
||||
fn get_path_to_play(&self, link: Rc<String>) -> Result<Rc<String>, String> {
|
||||
for offline_entry in &*self.m3u8_items {
|
||||
if *offline_entry.link == *link {
|
||||
return Ok(offline_entry.path.clone());
|
||||
}
|
||||
}
|
||||
Err("Not stored for offline use".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl GetM3u8 for OfflineParser {
|
||||
fn get_m3u8(&self) -> Vec<&M3u8> {
|
||||
self.m3u8_items.iter().map(|x| &**x).collect()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user