start with offlineparser and grandmother

This commit is contained in:
2023-03-05 16:35:01 +01:00
parent fd41100b95
commit c97761bc00
11 changed files with 324 additions and 39 deletions

View File

@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
use serde_json;
use crate::{
get_mut_ref, m3u8::DataEntry, Readline, APP_IDENTIFIER, JSON_CONFIG_FILENAME,
get_mut_ref, m3u8::OfflineEntry, Readline, APP_IDENTIFIER, JSON_CONFIG_FILENAME,
STANDARD_OFFLINE_FILENAME, STANDARD_PLAYLIST_FILENAME, STANDARD_SEEN_LINKS_FILENAME,
};
@ -83,7 +83,7 @@ pub struct Configuration {
pub seen_links: Vec<String>,
config_file_path: PathBuf,
pub data_dir: PathBuf,
pub datafile_content: Vec<DataEntry>,
pub offlinefile_content: Rc<Vec<OfflineEntry>>,
}
impl Configuration {
@ -94,8 +94,8 @@ impl Configuration {
// Make sure all the dirs for the project are setup correctly
let config_dir = project_dirs.config_dir();
let cache_dir = project_dirs.cache_dir().to_path_buf();
let data_dir = project_dirs.data_local_dir().to_path_buf();
for dir in [&config_dir, &cache_dir.as_path(), &data_dir.as_path()].iter() {
let offline_dir = project_dirs.data_local_dir().to_path_buf();
for dir in [&config_dir, &cache_dir.as_path(), &offline_dir.as_path()].iter() {
if !dir.exists() {
let _ = fs::create_dir_all(dir);
}
@ -115,8 +115,9 @@ impl Configuration {
let seen_links = Self::get_watched(&seen_links_path).unwrap_or_default();
// Datadir
let datafile = data_dir.join(STANDARD_OFFLINE_FILENAME);
let datafile_content = Self::get_datafile_content(&datafile).unwrap_or_default();
let offlinefile = offline_dir.join(STANDARD_OFFLINE_FILENAME);
let offlinefile_content =
Rc::new(Self::get_offline_content(&offlinefile).unwrap_or_default());
Ok(Self {
conf: configuration,
@ -124,8 +125,8 @@ impl Configuration {
seen_links,
seen_links_path,
config_file_path,
data_dir,
datafile_content,
data_dir: offline_dir,
offlinefile_content,
})
}
@ -137,13 +138,13 @@ impl Configuration {
}
}
pub fn push_datafile_ugly(&self, data_entry: DataEntry) {
unsafe { get_mut_ref(&self.datafile_content) }.push(data_entry);
pub fn push_offlinefile_ugly(&self, data_entry: OfflineEntry) {
unsafe { get_mut_ref(&*self.offlinefile_content) }.push(data_entry);
}
pub fn write_datafile(&self) -> Result<(), io::Error> {
let path = self.data_dir.join(STANDARD_OFFLINE_FILENAME);
fs::write(path, serde_json::to_string(&self.datafile_content)?)
fs::write(path, serde_json::to_string(&self.offlinefile_content)?)
}
fn get_watched(path: &Path) -> Option<Vec<String>> {
@ -151,7 +152,7 @@ impl Configuration {
serde_json::from_reader(reader).ok()
}
fn get_datafile_content(datafile: &PathBuf) -> Option<Vec<DataEntry>> {
fn get_offline_content(datafile: &PathBuf) -> Option<Vec<OfflineEntry>> {
let reader = BufReader::new(File::open(datafile).ok()?);
serde_json::from_reader(reader).ok()
}

27
src/getm3u8.rs Normal file
View File

@ -0,0 +1,27 @@
use crate::M3u8;
pub trait GetM3u8 {
fn get_m3u8(&self) -> Vec<&M3u8>;
}
pub trait WatchedFind {
fn find(&self, name: &str) -> Vec<&M3u8>;
fn get_watched(&self) -> Vec<&M3u8>;
}
impl<T> WatchedFind for T
where
T: GetM3u8,
{
fn find(&self, name: &str) -> Vec<&M3u8> {
let name = name.to_lowercase();
self.get_m3u8()
.into_iter()
.filter(|item| item.name.to_lowercase().contains(&name) || item.tvg_id.contains(&name))
.collect()
}
fn get_watched(&self) -> Vec<&M3u8> {
self.get_m3u8().into_iter().filter(|x| x.watched).collect()
}
}

View File

@ -1,14 +1,17 @@
use std::fs;
use crate::{Configuration, Parser, Playlist, MAX_TRIES};
use crate::{getm3u8::WatchedFind, Configuration, GetM3u8, Parser, Playlist, MAX_TRIES};
pub struct GrandMother {
pub parser: Parser,
pub struct GrandMother<T>
where
T: GetM3u8,
{
pub parser: T,
pub playlist: Playlist,
pub config: Configuration,
}
impl GrandMother {
impl GrandMother<Parser> {
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();
@ -43,8 +46,12 @@ impl GrandMother {
println!("Retrying {}/{}", counter, MAX_TRIES);
};
let watched_links = self.parser.get_watched();
let watched_links = watched_links.iter().map(|x| x.as_str()).collect();
let watched_links = self
.parser
.get_watched()
.iter()
.map(|x| x.link.as_str())
.collect();
self.parser = Parser::new(&content, &watched_links).await;
}

View File

@ -2,17 +2,21 @@ mod config;
mod downloader;
mod grandmother;
mod m3u8;
mod opt;
mod parser;
mod playlist;
mod getm3u8;
use std::io::{stdin, stdout, Stdin, StdoutLock, Write};
pub use config::Configuration;
pub use downloader::download_with_progress;
pub use grandmother::GrandMother;
pub use m3u8::{DataEntry, M3u8};
pub use m3u8::{OfflineEntry, M3u8};
pub use opt::{Mode, Opt};
pub use parser::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"];
@ -20,6 +24,7 @@ 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> {
stdout: StdoutLock<'a>,
stdin: Stdin,

View File

@ -1,6 +1,8 @@
use colored::Colorize;
use serde::{Deserialize, Serialize};
use std::{fmt::Display, ops::Deref};
use std::{fmt::Display, ops::Deref, rc::Rc};
use crate::{Configuration, GetM3u8};
#[derive(Serialize, Deserialize, Clone, Hash)]
pub struct M3u8 {
@ -26,20 +28,39 @@ impl Display for M3u8 {
}
#[derive(Serialize, Deserialize, Clone, Hash)]
pub struct DataEntry {
pub struct OfflineEntry {
m3u8: M3u8,
pub path: String,
}
impl DataEntry {
impl OfflineEntry {
pub fn new(m3u8: M3u8, path: String) -> Self {
Self { m3u8, path }
}
}
impl Deref for DataEntry {
impl Deref for OfflineEntry {
type Target = M3u8;
fn deref(&self) -> &Self::Target {
&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()
}
}

View File

@ -2,13 +2,18 @@ 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, DataEntry, GrandMother, M3u8, Readline,
download_with_progress, get_mut_ref, Configuration, GetM3u8, GrandMother, M3u8, Mode,
OfflineEntry, Opt, Parser, Readline,
};
use structopt::StructOpt;
#[tokio::main]
async fn main() {
let opt = Opt::from_args();
// Greet the user
[
format!(
@ -144,8 +149,8 @@ async fn main() {
let path = gm.config.data_dir.join(&to_download.name);
let path = path.to_string_lossy().to_string();
download_m3u8(to_download, Some(&path)).await;
let data_entry = DataEntry::new((*to_download).clone(), path);
gm.config.push_datafile_ugly(data_entry);
let data_entry = OfflineEntry::new((*to_download).clone(), path);
gm.config.push_offlinefile_ugly(data_entry);
}
if let Err(e) = gm.config.write_datafile() {
@ -170,6 +175,34 @@ 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>>,

37
src/opt.rs Normal file
View File

@ -0,0 +1,37 @@
use std::str::FromStr;
use structopt::StructOpt;
#[derive(StructOpt, Debug)]
#[structopt(name = "ilovetv")]
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,
}
#[derive(Debug)]
pub enum Mode {
Online,
Offline,
Ask,
}
impl Default for Mode {
fn default() -> Self {
Self::Ask
}
}
impl FromStr for Mode {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"online" => Ok(Self::Online),
"offline" => Ok(Self::Offline),
"ask" | "default" => Ok(Self::Ask),
_ => Err("No such enum"),
}
}
}

View File

@ -1,6 +1,12 @@
use std::ops::Deref;
use std::{
fs::{self, File},
io::BufReader,
ops::Deref,
};
use crate::m3u8::M3u8;
use serde::Serialize;
use crate::{m3u8::M3u8, Configuration, GetM3u8};
pub struct Parser {
m3u8_items: Vec<M3u8>,
@ -40,14 +46,6 @@ impl Parser {
self.m3u8_items = Self::parse_m3u8(content, seen_links);
}
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 interesting_lines: Vec<String> = content
@ -93,3 +91,24 @@ impl Deref for Parser {
&self.m3u8_items
}
}
impl GetM3u8 for Parser {
fn get_m3u8(&self) -> Vec<&M3u8> {
self.m3u8_items.iter().collect()
}
}
#[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!()
}
}

View File

@ -10,7 +10,7 @@ use crate::{download_with_progress, downloader::DualWriter, MAX_TRIES};
pub struct Playlist {
pub content: String,
path_to_playlist: Rc<PathBuf>,
url: Rc<String>,
url: Option<Rc<String>>,
}
impl Playlist {
@ -18,7 +18,7 @@ impl Playlist {
let mut me = Self {
content: String::new(),
path_to_playlist,
url,
url: Some(url),
};
me.content = me.get_saved_or_download().await?;
@ -76,7 +76,13 @@ impl Playlist {
loop {
counter += 1;
let downloaded = download_with_progress(&self.url, None)
let url = self
.url
.as_ref()
.clone()
.ok_or_else(|| String::from("In offline mode"))?;
let downloaded = download_with_progress(url, None)
.await
.and_then(DualWriter::get_string);
if let Ok(content) = downloaded {