refactored, renamed processor to m3u-processing, moved function from main to utils

This commit is contained in:
euzu
2021-09-30 11:11:30 +02:00
parent 58e3d76f90
commit fa1f48e201
5 changed files with 68 additions and 80 deletions
+17 -21
View File
@@ -1,16 +1,12 @@
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum ItemField {
Group,
Name,
Title,
}
impl fmt::Display for ItemField {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
impl std::fmt::Display for ItemField {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
ItemField::Group => write!(f, "Group"),
ItemField::Name => write!(f, "Name"),
@@ -19,22 +15,22 @@ impl fmt::Display for ItemField {
}
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub enum FilterMode {
Discard,
Include,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct ConfigFilter {
pub field: ItemField,
pub pattern: String,
#[serde(skip_serializing, skip_deserializing)]
pub re: Option<Regex>,
pub re: Option<regex::Regex>,
}
impl ConfigFilter {
pub fn prepare(&mut self) -> () {
pub(crate) fn prepare(&mut self) -> () {
let re = regex::Regex::new(&self.pattern);
if re.is_err() {
println!("cant parse regex: {}", &self.pattern);
@@ -44,29 +40,29 @@ impl ConfigFilter {
}
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct ConfigFilters {
pub mode: FilterMode,
pub rules: Vec<ConfigFilter>,
}
impl ConfigFilters {
pub fn is_include(&self) -> bool {
pub(crate) fn is_include(&self) -> bool {
matches!(self.mode, FilterMode::Include)
}
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct ConfigRename {
pub field: ItemField,
pub pattern: String,
pub new_name: String,
#[serde(skip_serializing, skip_deserializing)]
pub re: Option<Regex>,
pub re: Option<regex::Regex>,
}
impl ConfigRename {
pub fn prepare(&mut self) -> () {
pub(crate) fn prepare(&mut self) -> () {
let re = regex::Regex::new(&self.pattern);
if re.is_err() {
println!("cant parse regex: {}", &self.pattern);
@@ -77,33 +73,33 @@ impl ConfigRename {
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct ConfigTarget {
pub filename: String,
pub filter: ConfigFilters,
pub rename: Vec<ConfigRename>,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct ConfigInput {
pub url: String,
pub persist: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Config {
pub input: ConfigInput,
pub targets: Vec<ConfigTarget>,
}
impl Config {
pub fn prepare(&mut self) {
pub(crate) fn prepare(&mut self) {
for t in &mut self.targets {
for f in &mut t.filter.rules {
f.prepare();
}
for r in &mut t.rename {
r.prepare();
r.prepare();
}
}
}
+15 -20
View File
@@ -1,11 +1,6 @@
// https://de.wikipedia.org/wiki/M3U
// https://siptv.eu/howto/playlist.html
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::fmt;
use std::str::Chars;
pub struct PlaylistItemHeader {
pub id: String,
pub name: String,
@@ -38,8 +33,8 @@ impl Clone for PlaylistItemHeader {
}
}
impl fmt::Display for PlaylistItemHeader {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
impl std::fmt::Display for PlaylistItemHeader {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.write_str("id:")?;
fmt.write_str(&self.id)?;
fmt.write_str(", name:")?;
@@ -80,8 +75,8 @@ impl Clone for PlaylistItem {
}
}
impl fmt::Display for PlaylistItem {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
impl std::fmt::Display for PlaylistItem {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.write_str("header:")?;
fmt.write_str(&self.header.to_string())?;
fmt.write_str(", url:")?;
@@ -91,7 +86,7 @@ impl fmt::Display for PlaylistItem {
}
impl PlaylistItem {
pub fn to_m3u(&self) -> String {
pub(crate) fn to_m3u(&self) -> String {
let mut line = format!("#EXTINF:-1 tvg-id=\"{}\" tvg-name=\"{}\" tvg-logo=\"{}\" group-title=\"{}\"", self.header.id, self.header.name, self.header.logo, self.header.group);
if !self.header.logo_small.is_empty() {
line = format!("{} tvg-logo-small=\"!{}\"", line, self.header.logo_small);
@@ -118,8 +113,8 @@ pub struct PlaylistGroup {
pub channels: Vec<PlaylistItem>,
}
impl fmt::Display for PlaylistGroup {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
impl std::fmt::Display for PlaylistGroup {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.write_str("title:")?;
fmt.write_str(&self.title.to_string())?;
fmt.write_str(", channels:")?;
@@ -128,7 +123,7 @@ impl fmt::Display for PlaylistGroup {
}
}
fn token_value<'a>(it: &'a mut Chars) -> String {
fn token_value<'a>(it: &'a mut std::str::Chars) -> String {
let oc = it.next();
if oc.is_some() && oc.unwrap() == '"' {
return get_value(it);
@@ -136,7 +131,7 @@ fn token_value<'a>(it: &'a mut Chars) -> String {
return String::from("");
}
fn get_value<'a>(it: &'a mut Chars) -> String {
fn get_value<'a>(it: &'a mut std::str::Chars) -> String {
let mut result: Vec<char> = vec![];
let mut oc = it.next();
while oc.is_some() && oc.unwrap() != '"' {
@@ -146,7 +141,7 @@ fn get_value<'a>(it: &'a mut Chars) -> String {
return String::from(result.iter().collect::<String>());
}
fn token_till(it: &mut Chars, stop_char: char) -> Option<String> {
fn token_till(it: &mut std::str::Chars, stop_char: char) -> Option<String> {
let mut result: Vec<char> = vec![];
loop {
let c = it.next();
@@ -161,7 +156,7 @@ fn token_till(it: &mut Chars, stop_char: char) -> Option<String> {
if result.len() > 0 { Some(String::from(result.iter().collect::<String>())) } else { None }
}
fn skip_digit(it: &mut Chars) -> Option<char> {
fn skip_digit(it: &mut std::str::Chars) -> Option<char> {
let mut c: Option<char>;
loop {
c = it.next();
@@ -222,8 +217,8 @@ fn decode_header(content: &String) -> PlaylistItemHeader {
plih
}
pub fn decode(lines: &Vec<String>) -> Vec<PlaylistGroup> {
let mut groups: HashMap<String, Vec<PlaylistItem>> = HashMap::new();
pub(crate) fn decode(lines: &Vec<String>) -> Vec<PlaylistGroup> {
let mut groups: std::collections::HashMap<String, Vec<PlaylistItem>> = std::collections::HashMap::new();
let mut sort_order: Vec<String> = vec![];
let mut header: Option<String> = None;
let mut group: Option<String> = None;
@@ -248,11 +243,11 @@ pub fn decode(lines: &Vec<String>) -> Vec<PlaylistGroup> {
let key = String::from(&item.header.group);
let key2 = String::from(&item.header.group);
match groups.entry(key) {
Entry::Vacant(e) => {
std::collections::hash_map::Entry::Vacant(e) => {
e.insert(vec![item]);
sort_order.push(key2);
}
Entry::Occupied(mut e) => { e.get_mut().push(item); }
std::collections::hash_map::Entry::Occupied(mut e) => { e.get_mut().push(item); }
}
}
header = None;
+2 -3
View File
@@ -1,12 +1,11 @@
use std::fs::File;
use std::io::Write;
use crate::{config, m3u};
use crate::m3u::PlaylistItem;
pub fn write_m3u(playlist: &Vec<m3u::PlaylistGroup>, cfg: &config::Config) {
pub(crate) fn write_m3u(playlist: &Vec<m3u::PlaylistGroup>, cfg: &config::Config) {
for t in &cfg.targets {
let mut file = match File::create(&t.filename) {
let mut file = match std::fs::File::create(&t.filename) {
Ok(file) => file,
Err(_) => {
println!("cant open file: {}", t.filename);
+4 -11
View File
@@ -1,7 +1,6 @@
use std::path::{PathBuf};
mod m3u;
mod config;
mod processor;
mod m3u_processing;
mod utils;
fn main() {
@@ -40,17 +39,11 @@ fn main() {
let input_arg = matches.value_of("input");
let url_str = input_arg.unwrap_or(if cfg.input.url.is_empty() { "playlist.m3u" } else { cfg.input.url.as_str() });
let persist_file = match input_arg {
Some(_) => matches.value_of("persist").map_or(None, |p|prepare_persist_path(p)),
None => if cfg.input.persist.is_empty() {None} else { prepare_persist_path(cfg.input.persist.as_str())},
Some(_) => matches.value_of("persist").map_or(None, |p| utils::prepare_persist_path(p)),
None => if cfg.input.persist.is_empty() { None } else { utils::prepare_persist_path(cfg.input.persist.as_str()) },
};
let lines: Vec<String> = utils::get_input_content(url_str, persist_file);
let result = m3u::decode(&lines);
processor::write_m3u(&result, &cfg);
}
fn prepare_persist_path(file_name: &str) -> Option<PathBuf> {
let now = chrono::Local::now();
let filename = file_name.replace("{}", now.format("%Y%m%d_%H%M%S").to_string().as_str());
Some(PathBuf::from(filename))
m3u_processing::write_m3u(&result, &cfg);
}
+30 -25
View File
@@ -1,8 +1,6 @@
use url::{Url};
use std::io::{BufRead, Write};
use std::path::{PathBuf};
pub fn get_default_config_path() -> String {
pub(crate) fn get_default_config_path() -> String {
let default_path = std::path::Path::new("./");
let current_exe = std::env::current_exe();
let path: &std::path::Path = match current_exe {
@@ -17,7 +15,7 @@ pub fn get_default_config_path() -> String {
})
}
pub fn open_file(file_name: &str) -> std::fs::File {
pub(crate) fn open_file(file_name: &str) -> std::fs::File {
let file = match std::fs::File::open(file_name) {
Ok(file) => file,
Err(_) => {
@@ -28,8 +26,8 @@ pub fn open_file(file_name: &str) -> std::fs::File {
file
}
pub fn get_input_content(url_str: &str, persist_file: Option<PathBuf>) -> Vec<String> {
match url_str.parse::<Url>() {
pub(crate) fn get_input_content(url_str: &str, persist_file: Option<std::path::PathBuf>) -> Vec<String> {
match url_str.parse::<url::Url>() {
Ok(url) => match download_content(url, persist_file) {
Ok(content) => content,
Err(e) => {
@@ -43,11 +41,11 @@ pub fn get_input_content(url_str: &str, persist_file: Option<PathBuf>) -> Vec<St
if persist_file.is_some() {
let to_file = &persist_file.unwrap();
match std::fs::copy(file, to_file) {
Ok(_) => {},
Ok(_) => {}
Err(e) => println!("cant persist to: {} => {}", to_file.to_str().unwrap_or("?"), e),
}
} ;
std::io::BufReader::new(open_file(url_str)).lines(). map(|l| l.unwrap()).collect()
};
std::io::BufReader::new(open_file(url_str)).lines().map(|l| l.unwrap()).collect()
} else {
println!("cant read input url: {} => {}", url_str, e);
std::process::exit(1);
@@ -56,38 +54,45 @@ pub fn get_input_content(url_str: &str, persist_file: Option<PathBuf>) -> Vec<St
}
}
fn download_content(url: Url, persist_file: Option<PathBuf>) -> Result<Vec<String>, String> {
fn download_content(url: url::Url, persist_file: Option<std::path::PathBuf>) -> Result<Vec<String>, String> {
match reqwest::blocking::get(url) {
Ok(response) => {
if response.status().is_success() {
match response.text_with_charset("utf8") {
Ok(text) => {
persists_playlist(persist_file, &text);
let result= text.lines().map(|l| String::from(l)).collect();
let result = text.lines().map(|l| String::from(l)).collect();
Ok(result)
},
}
Err(e) => Err(e.to_string())
}
} else {
Err(format!("Request failed: {}", response.status()))
}
},
}
Err(e) => Err(e.to_string())
}
}
fn persists_playlist(persist_file: Option<PathBuf>, text: &String) {
match persist_file {
Some(path_buf) => {
let filename = &path_buf.to_str().unwrap_or("?");
match std::fs::File::create(&path_buf) {
Ok(mut file) => match file.write_all(text.as_bytes()) {
Ok(_) => println!("persisted: {}", filename),
Err(e) => println!("failed to persist file {}, {}", filename, e)
},
fn persists_playlist(persist_file: Option<std::path::PathBuf>, text: &String) {
match persist_file {
Some(path_buf) => {
let filename = &path_buf.to_str().unwrap_or("?");
match std::fs::File::create(&path_buf) {
Ok(mut file) => match file.write_all(text.as_bytes()) {
Ok(_) => println!("persisted: {}", filename),
Err(e) => println!("failed to persist file {}, {}", filename, e)
}
},
None => {}
},
Err(e) => println!("failed to persist file {}, {}", filename, e)
}
}
None => {}
}
}
pub(crate) fn prepare_persist_path(file_name: &str) -> Option<std::path::PathBuf> {
let now = chrono::Local::now();
let filename = file_name.replace("{}", now.format("%Y%m%d_%H%M%S").to_string().as_str());
Some(std::path::PathBuf::from(filename))
}