Skip to content

Commit 3312439

Browse files
authored
Feature/m3u playlist with extension (#552)
m3u playlist with extensions
1 parent 88c4438 commit 3312439

15 files changed

Lines changed: 73 additions & 46 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ resolver = "2"
55
[profile.release]
66
debug = false
77
opt-level = 'z' # Optimize for size.
8-
lto = true # Enable Link Time Optimization
8+
lto = "fat" # Enable Link Time Optimization
99
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
1010
panic = 'abort' # Abort on panic
1111
strip = true

backend/src/processing/input_cache.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ pub struct InputStatus {
2727
pub clusters: HashMap<String, ClusterStatus>,
2828
}
2929

30-
pub fn resolve_input_storage_path(working_dir: &str, input_name: &str) -> PathBuf {
31-
if let Ok(path) = get_input_storage_path(input_name, working_dir) { path } else {
30+
pub async fn resolve_input_storage_path(working_dir: &str, input_name: &str) -> PathBuf {
31+
if let Ok(path) = get_input_storage_path(input_name, working_dir).await { path } else {
3232
build_input_storage_path(input_name, working_dir)
3333
}
3434
}

backend/src/processing/processor/playlist.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ async fn playlist_download_from_input(client: &reqwest::Client, app_config: &Arc
282282
let working_dir = &config.working_dir;
283283

284284
// Check Status
285-
let storage_path = input_cache::resolve_input_storage_path(working_dir, &input.name);
285+
let storage_path = input_cache::resolve_input_storage_path(working_dir, &input.name).await;
286286
let mut status = input_cache::load_input_status(&storage_path);
287287
let cache_duration = input.cache_duration_seconds;
288288

backend/src/processing/processor/xtream_series.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ async fn playlist_resolve_series_info(app_config: &Arc<AppConfig>, client: &reqw
2626

2727
let input = fpl.input;
2828
let working_dir = &app_config.config.load().working_dir;
29-
let storage_path = match get_input_storage_path(&input.name, working_dir) {
29+
let storage_path = match get_input_storage_path(&input.name, working_dir).await {
3030
Ok(storage_path) => storage_path,
3131
Err(err) => {
3232
error!("Can't resolve series info, input storage directory for input '{}' failed: {err}", input.name);

backend/src/processing/processor/xtream_vod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub async fn playlist_resolve_vod(app_config: &Arc<AppConfig>,
2727

2828
let input = fpl.input;
2929
let working_dir = &app_config.config.load().working_dir;
30-
let storage_path = match get_input_storage_path(&input.name, working_dir) {
30+
let storage_path = match get_input_storage_path(&input.name, working_dir).await {
3131
Ok(storage_path) => storage_path,
3232
Err(err) => {
3333
error!("Can't resolve vod, input storage directory for input '{}' failed: {err}", input.name);

backend/src/repository/m3u_playlist_iterator.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::utils::FileReadGuard;
1212
use std::collections::HashSet;
1313
use std::iter::Peekable;
1414
use log::error;
15-
use shared::utils::Internable;
15+
use shared::utils::{extract_extension_from_url, Internable};
1616

1717
#[allow(clippy::struct_excessive_bools)]
1818
pub struct M3uPlaylistIterator {
@@ -41,7 +41,7 @@ impl M3uPlaylistIterator {
4141

4242
let m3u_output = target.get_m3u_output().ok_or_else(|| info_err!("Unexpected failure, missing m3u target output for target {}", target.name))?;
4343
let config = cfg.config.load();
44-
let target_path = ensure_target_storage_path(&config, target.name.as_str())?;
44+
let target_path = ensure_target_storage_path(&config, target.name.as_str()).await?;
4545
let m3u_path = m3u_get_file_path_for_db(&target_path);
4646

4747
let file_lock = cfg.file_locks.read_lock(&m3u_path).await;
@@ -107,7 +107,7 @@ impl M3uPlaylistIterator {
107107
+ 32; // separators and id
108108
if typed { cap += stream_type.len() + 1; }
109109

110-
if typed {
110+
let rewritten_url = if typed {
111111
shared::concat_string!(
112112
cap = cap;
113113
&self.base_url, "/", prefix_path, "/", stream_type, "/",
@@ -119,7 +119,9 @@ impl M3uPlaylistIterator {
119119
&self.base_url, "/", prefix_path, "/",
120120
&self.username, "/", &self.password, "/", &m3u_pli.virtual_id.to_string()
121121
)
122-
}
122+
};
123+
124+
extract_extension_from_url(&m3u_pli.url).map(|ext| shared::concat_string!(&rewritten_url, ext)).unwrap_or(rewritten_url)
123125
}
124126

125127
fn get_stream_url(&self, m3u_pli: &M3uPlaylistItem, typed: bool) -> String {

backend/src/repository/m3u_repository.rs

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ use crate::repository::bplustree::{BPlusTree, BPlusTreeQuery};
55
use crate::repository::m3u_playlist_iterator::M3uPlaylistM3uTextIterator;
66
use crate::repository::playlist_repository::get_input_m3u_playlist_file_path;
77
use crate::repository::storage::{get_input_storage_path, get_target_storage_path};
8-
use crate::repository::storage_const;
8+
use crate::repository::{storage_const};
99
use crate::repository::xtream_repository::CategoryKey;
1010
use crate::utils;
1111
use crate::utils::{async_file_writer, file_exists_async, FileReadGuard, IO_BUFFER_SIZE};
1212
use indexmap::IndexMap;
1313
use log::error;
14-
use shared::concat_string;
14+
use shared::{concat_string, notify_err_res};
1515
use shared::error::{notify_err, str_to_io_error, string_to_io_error, TuliproxError};
1616
use shared::model::{M3uPlaylistItem, PlaylistGroup};
1717
use shared::model::{PlaylistItem, PlaylistItemType, XtreamCluster};
@@ -29,21 +29,41 @@ macro_rules! cant_write_result {
2929
}
3030
}
3131

32+
macro_rules! await_playlist_write {
33+
($expr:expr, $fmt:literal $(, $args:expr)* ) => {{
34+
$expr.await.map_err(|err| {
35+
notify_err!($fmt $(, $args)*, err)
36+
})?
37+
}};
38+
}
39+
3240
pub fn m3u_get_file_path_for_db(target_path: &Path) -> PathBuf {
33-
target_path.join(PathBuf::from(concat_string!(storage_const::FILE_M3U, ".", storage_const::FILE_SUFFIX_DB)))
41+
target_path.join(storage_const::PATH_M3U).join(concat_string!(storage_const::FILE_M3U, ".", storage_const::FILE_SUFFIX_DB))
3442
}
3543

3644
pub fn m3u_get_epg_file_path_for_target(target_path: &Path) -> PathBuf {
37-
let path = target_path.join(PathBuf::from(concat_string!(storage_const::FILE_M3U, ".", storage_const::FILE_SUFFIX_DB)));
45+
let path = target_path.join(storage_const::PATH_M3U).join(concat_string!(storage_const::FILE_M3U, ".", storage_const::FILE_SUFFIX_DB));
3846
utils::add_prefix_to_filename(&path, "epg_", Some(storage_const::FILE_SUFFIX_DB))
3947
}
4048

41-
macro_rules! await_playlist_write {
42-
($expr:expr, $fmt:literal $(, $args:expr)* ) => {{
43-
$expr.await.map_err(|err| {
44-
notify_err!($fmt $(, $args)*, err)
45-
})?
46-
}};
49+
pub fn m3u_get_storage_path(cfg: &Config, target_name: &str) -> Option<PathBuf> {
50+
get_target_storage_path(cfg, target_name).map(|target_path| target_path.join(PathBuf::from(storage_const::PATH_M3U)))
51+
}
52+
53+
pub async fn ensure_m3u_storage_path(cfg: &Config, target_name: &str) -> Result<PathBuf, TuliproxError> {
54+
if let Some(path) = m3u_get_storage_path(cfg, target_name) {
55+
if tokio::fs::create_dir_all(&path).await.is_err() {
56+
let msg = format!(
57+
"Failed to save m3u data, can't create directory {}",
58+
&path.display()
59+
);
60+
return notify_err_res!("{msg}");
61+
}
62+
Ok(path)
63+
} else {
64+
let msg = format!("Failed to save m3u data, can't create directory for target {target_name}");
65+
notify_err_res!("{msg}")
66+
}
4767
}
4868

4969
async fn persist_m3u_playlist_as_text(
@@ -90,6 +110,9 @@ pub async fn m3u_write_playlist(
90110
return Ok(());
91111
}
92112

113+
let config = cfg.config.load();
114+
let _m3u_path = ensure_m3u_storage_path(&config, target.name.as_str()).await?;
115+
93116
let m3u_path = m3u_get_file_path_for_db(target_path);
94117
let m3u_playlist = Arc::new(
95118
new_playlist
@@ -169,7 +192,7 @@ pub async fn iter_raw_m3u_target_playlist(config: &AppConfig, target: &ConfigTar
169192

170193
pub async fn iter_raw_m3u_input_playlist(app_config: &AppConfig, input: &ConfigInput, cluster: Option<XtreamCluster>) -> Option<(FileReadGuard, Box<dyn Iterator<Item=M3uPlaylistItem> + Send>)> {
171194
let working_dir = &app_config.config.load().working_dir;
172-
let storage_path = get_input_storage_path(&input.name, working_dir).ok()?;
195+
let storage_path = get_input_storage_path(&input.name, working_dir).await.ok()?;
173196
let m3u_path = get_input_m3u_playlist_file_path(&storage_path, &input.name);
174197

175198
iter_raw_m3u_playlist::<u32, Arc<str>>(app_config, &m3u_path, cluster).await

backend/src/repository/playlist_repository.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pub async fn persist_playlist(app_config: &Arc<AppConfig>, playlist: &mut [Playl
3737
target: &ConfigTarget, playlist_state: Option<&Arc<PlaylistStorageState>>) -> Result<(), Vec<TuliproxError>> {
3838
let mut errors = vec![];
3939
let config = &app_config.config.load();
40-
let target_path = match ensure_target_storage_path(config, &target.name) {
40+
let target_path = match ensure_target_storage_path(config, &target.name).await {
4141
Ok(path) => path,
4242
Err(err) => return Err(vec![err]),
4343
};
@@ -355,7 +355,7 @@ pub async fn persist_input_playlist(app_config: &Arc<AppConfig>, input: &ConfigI
355355
match input.input_type {
356356
InputType::Xtream | InputType::XtreamBatch => {
357357
let working_dir = &app_config.config.load().working_dir;
358-
let storage_path = match get_input_storage_path(&input.name, working_dir) {
358+
let storage_path = match get_input_storage_path(&input.name, working_dir).await {
359359
Ok(storage_path) => storage_path,
360360
Err(err) => {
361361
return (playlist, Some(info_err!("Error creating input storage directory for input '{}' failed: {err}", input.name)));
@@ -367,7 +367,7 @@ pub async fn persist_input_playlist(app_config: &Arc<AppConfig>, input: &ConfigI
367367
InputType::M3u | InputType::M3uBatch => {
368368
// Persist M3U
369369
let working_dir = &app_config.config.load().working_dir;
370-
let storage_path = match get_input_storage_path(&input.name, working_dir) {
370+
let storage_path = match get_input_storage_path(&input.name, working_dir).await {
371371
Ok(storage_path) => storage_path,
372372
Err(err) => {
373373
return (playlist, Some(info_err!("Error creating input storage directory for input '{}' failed: {err}", input.name)));
@@ -382,7 +382,7 @@ pub async fn persist_input_playlist(app_config: &Arc<AppConfig>, input: &ConfigI
382382
InputType::Library => {
383383
// Persist local library playlist
384384
let working_dir = &app_config.config.load().working_dir;
385-
let storage_path = match get_input_storage_path(&input.name, working_dir) {
385+
let storage_path = match get_input_storage_path(&input.name, working_dir).await {
386386
Ok(storage_path) => storage_path,
387387
Err(err) => {
388388
return (playlist, Some(info_err!("Error creating input storage directory for input '{}' failed: {err}", input.name)));
@@ -400,7 +400,7 @@ pub async fn persist_input_playlist(app_config: &Arc<AppConfig>, input: &ConfigI
400400
pub async fn load_input_playlist(ctx: &PlaylistProcessingContext, input: &ConfigInput, clusters: Option<&[XtreamCluster]>) -> Result<Box<dyn PlaylistSource>, TuliproxError> {
401401
let app_config = &ctx.config;
402402
let working_dir = &app_config.config.load().working_dir;
403-
let storage_path = get_input_storage_path(&input.name, working_dir)
403+
let storage_path = get_input_storage_path(&input.name, working_dir).await
404404
.map_err(|e| info_err!("Error getting input path: {e}"))?;
405405

406406
let disk_based_processing = app_config.config.load().disk_based_processing;

backend/src/repository/storage.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ pub(in crate::repository) fn get_target_id_mapping_file(target_path: &Path) -> P
1111
target_path.join(storage_const::FILE_ID_MAPPING)
1212
}
1313

14-
pub fn ensure_target_storage_path(cfg: &Config, target_name: &str) -> Result<PathBuf, TuliproxError> {
14+
pub async fn ensure_target_storage_path(cfg: &Config, target_name: &str) -> Result<PathBuf, TuliproxError> {
1515
if let Some(path) = get_target_storage_path(cfg, target_name) {
16-
if std::fs::create_dir_all(&path).is_err() {
16+
if tokio::fs::create_dir_all(&path).await.is_err() {
1717
let msg = format!("Failed to save target data, can't create directory {}", path.display());
1818
return notify_err_res!("{msg}");
1919
}
@@ -40,14 +40,14 @@ pub fn build_input_storage_path(input_name: &str, working_dir: &str) -> PathBuf
4040
Path::new(working_dir).join(name)
4141
}
4242

43-
pub fn get_input_storage_path(input_name: &str, working_dir: &str) -> std::io::Result<PathBuf> {
43+
pub async fn get_input_storage_path(input_name: &str, working_dir: &str) -> std::io::Result<PathBuf> {
4444
let path = build_input_storage_path(input_name, working_dir);
4545
// Create the directory and return the path or propagate the error
46-
std::fs::create_dir_all(&path).map(|()| path)
46+
tokio::fs::create_dir_all(&path).await.map(|()| path)
4747
}
4848

49-
pub fn ensure_input_storage_path(cfg: &Config, input_name: &str) -> Result<PathBuf, TuliproxError> {
50-
get_input_storage_path(input_name, &cfg.working_dir)
49+
pub async fn ensure_input_storage_path(cfg: &Config, input_name: &str) -> Result<PathBuf, TuliproxError> {
50+
get_input_storage_path(input_name, &cfg.working_dir).await
5151
.map_err(|err| {
5252
notify_err!("Failed to save input data, can't create directory for input {input_name}: {err}")
5353
})

backend/src/repository/storage_const.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ pub(in crate::repository) const FILE_SUFFIX_INDEX: &str = "idx";
44
pub(in crate::repository) const FILE_ID_MAPPING: &str = "id_mapping.db";
55
pub(in crate::repository) const FILE_STRM: &str = "strm";
66
pub(in crate::repository) const FILE_M3U: &str = "m3u";
7+
pub(in crate::repository) const PATH_M3U: &str = "m3u";
8+
79
pub const M3U_STREAM_PATH: &str = "m3u-stream";
810
pub const M3U_RESOURCE_PATH: &str = "resource/m3u";
911
pub const EPG_RESOURCE_PATH: &str = "resource/epg";

0 commit comments

Comments
 (0)