2021-10-07 20:11:54 +02:00
|
|
|
use actix_cors::Cors;
|
2024-12-05 19:54:34 +01:00
|
|
|
use actix_web::middleware::Logger;
|
2025-01-02 18:31:52 +01:00
|
|
|
use actix_web::web::Data;
|
2024-12-05 19:54:34 +01:00
|
|
|
use actix_web::{web, App, HttpResponse, HttpServer};
|
2025-01-02 18:31:52 +01:00
|
|
|
use async_std::sync::{Mutex, RwLock};
|
2024-05-02 15:22:23 +02:00
|
|
|
use log::info;
|
2025-01-02 18:31:52 +01:00
|
|
|
use std::collections::{HashMap, VecDeque};
|
|
|
|
|
use std::io::ErrorKind;
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
use std::sync::Arc;
|
2021-10-07 20:11:54 +02:00
|
|
|
|
2024-05-02 15:22:23 +02:00
|
|
|
use crate::api::m3u_api::m3u_api_register;
|
2024-12-05 19:54:34 +01:00
|
|
|
use crate::api::model::app_state::AppState;
|
|
|
|
|
use crate::api::model::download::DownloadQueue;
|
2023-11-02 20:03:22 +01:00
|
|
|
use crate::api::scheduler::start_scheduler;
|
2024-05-02 15:22:23 +02:00
|
|
|
use crate::api::v1_api::v1_api_register;
|
|
|
|
|
use crate::api::web_index::index_register;
|
|
|
|
|
use crate::api::xmltv_api::xmltv_api_register;
|
|
|
|
|
use crate::api::xtream_api::xtream_api_register;
|
2025-01-02 18:31:52 +01:00
|
|
|
use crate::model::config::{validate_targets, Config, ProcessTargets, ScheduleConfig};
|
2024-05-17 18:05:48 +02:00
|
|
|
use crate::model::healthcheck::Healthcheck;
|
2024-03-27 09:25:22 +01:00
|
|
|
use crate::processing::playlist_processor;
|
2024-05-17 18:05:48 +02:00
|
|
|
use crate::VERSION;
|
2021-10-07 20:11:54 +02:00
|
|
|
|
2024-05-02 15:22:23 +02:00
|
|
|
fn get_web_dir_path(web_ui_enabled: bool, web_root: &str) -> Result<PathBuf, std::io::Error> {
|
|
|
|
|
let web_dir = web_root.to_string();
|
|
|
|
|
let web_dir_path = PathBuf::from(&web_dir);
|
2024-05-04 20:30:20 +02:00
|
|
|
if web_ui_enabled && (!&web_dir_path.exists() || !&web_dir_path.is_dir()) {
|
|
|
|
|
return Err(std::io::Error::new(ErrorKind::NotFound,
|
|
|
|
|
format!("web_root does not exists or is not an directory: {:?}", &web_dir_path)));
|
2024-05-02 15:22:23 +02:00
|
|
|
};
|
|
|
|
|
Ok(web_dir_path)
|
2021-10-07 20:11:54 +02:00
|
|
|
}
|
|
|
|
|
|
2024-05-17 18:05:48 +02:00
|
|
|
async fn healthcheck() -> HttpResponse {
|
|
|
|
|
let ts = chrono::offset::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
2024-12-05 19:54:34 +01:00
|
|
|
HttpResponse::Ok().json(Healthcheck {
|
2024-05-17 18:05:48 +02:00
|
|
|
status: "ok".to_string(),
|
|
|
|
|
version: VERSION.to_string(),
|
2024-12-05 19:54:34 +01:00
|
|
|
time: ts,
|
2024-05-17 18:05:48 +02:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-02 18:31:52 +01:00
|
|
|
fn create_shared_data(cfg: &Arc<Config>) -> Data<AppState> {
|
|
|
|
|
Data::new(AppState {
|
|
|
|
|
config: Arc::clone(cfg),
|
2023-11-06 21:33:48 +01:00
|
|
|
downloads: Arc::from(DownloadQueue {
|
2023-11-08 11:46:03 +01:00
|
|
|
queue: Arc::from(Mutex::new(VecDeque::new())),
|
2023-11-06 21:33:48 +01:00
|
|
|
active: Arc::from(RwLock::new(None)),
|
2023-11-08 14:55:39 +01:00
|
|
|
finished: Arc::from(RwLock::new(Vec::new())),
|
2023-11-24 22:53:33 +01:00
|
|
|
}),
|
2024-12-05 19:54:34 +01:00
|
|
|
shared_streams: Arc::new(Mutex::new(HashMap::new())),
|
2025-01-02 18:31:52 +01:00
|
|
|
})
|
|
|
|
|
}
|
2023-09-26 19:41:02 +02:00
|
|
|
|
2025-01-02 18:31:52 +01:00
|
|
|
fn exec_update_on_boot(cfg: &Arc<Config>, targets: &Arc<ProcessTargets>) {
|
2024-03-27 09:25:22 +01:00
|
|
|
if cfg.update_on_boot {
|
2025-01-02 18:31:52 +01:00
|
|
|
let cfg_clone = Arc::clone(cfg);
|
|
|
|
|
let targets_clone = Arc::clone(targets);
|
2024-03-27 09:25:22 +01:00
|
|
|
actix_rt::spawn(
|
|
|
|
|
async move { playlist_processor::exec_processing(cfg_clone, targets_clone).await }
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-01-02 18:31:52 +01:00
|
|
|
}
|
|
|
|
|
|
2024-03-27 09:25:22 +01:00
|
|
|
|
2025-01-02 18:31:52 +01:00
|
|
|
fn get_process_targets(cfg: &Arc<Config>, process_targets: &Arc<ProcessTargets>, exec_targets: Option<&Vec<String>>) -> Arc<ProcessTargets> {
|
|
|
|
|
if let Ok(user_targets) = validate_targets(exec_targets, &cfg.sources) {
|
|
|
|
|
if user_targets.enabled {
|
|
|
|
|
if !process_targets.enabled {
|
|
|
|
|
return Arc::new(user_targets);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let inputs: Vec<u16> = user_targets.inputs.iter()
|
|
|
|
|
.filter(|&id| process_targets.inputs.contains(id))
|
|
|
|
|
.copied()
|
|
|
|
|
.collect();
|
|
|
|
|
let targets: Vec<u16> = user_targets.targets.iter()
|
|
|
|
|
.filter(|&id| process_targets.inputs.contains(id))
|
|
|
|
|
.copied()
|
|
|
|
|
.collect();
|
|
|
|
|
return Arc::new(ProcessTargets {
|
|
|
|
|
enabled: user_targets.enabled,
|
|
|
|
|
inputs,
|
|
|
|
|
targets,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Arc::clone(process_targets)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn exec_scheduler(cfg: &Arc<Config>, targets: &Arc<ProcessTargets>) {
|
|
|
|
|
let schedules: Vec<ScheduleConfig> = if let Some(schedules) = &cfg.schedules {
|
|
|
|
|
schedules.clone()
|
|
|
|
|
} else {
|
|
|
|
|
vec![]
|
|
|
|
|
};
|
|
|
|
|
for schedule in schedules {
|
|
|
|
|
let expression = schedule.schedule.to_string();
|
|
|
|
|
let exec_targets = get_process_targets(cfg, targets, schedule.targets.as_ref());
|
|
|
|
|
let cfg_clone = Arc::clone(cfg);
|
|
|
|
|
actix_rt::spawn(async move {
|
|
|
|
|
start_scheduler(expression.as_str(), cfg_clone, exec_targets).await;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_web_auth_enabled(cfg: &Arc<Config>, web_ui_enabled: bool, web_dir_path: &PathBuf) -> bool {
|
2024-05-02 15:22:23 +02:00
|
|
|
if web_ui_enabled {
|
|
|
|
|
info!("Web root: {:?}", &web_dir_path);
|
2024-05-03 11:08:15 +02:00
|
|
|
if let Some(web_auth) = &cfg.web_auth {
|
2025-01-02 18:31:52 +01:00
|
|
|
return web_auth.enabled;
|
2024-05-03 11:08:15 +02:00
|
|
|
}
|
2024-05-02 15:22:23 +02:00
|
|
|
}
|
2025-01-02 18:31:52 +01:00
|
|
|
false
|
|
|
|
|
}
|
2024-05-02 15:22:23 +02:00
|
|
|
|
2025-01-02 18:31:52 +01:00
|
|
|
#[actix_web::main]
|
|
|
|
|
pub async fn start_server(cfg: Arc<Config>, targets: Arc<ProcessTargets>) -> futures::io::Result<()> {
|
|
|
|
|
let host = cfg.api.host.to_string();
|
|
|
|
|
let port = cfg.api.port;
|
|
|
|
|
let web_ui_enabled = cfg.web_ui_enabled;
|
|
|
|
|
let web_dir_path = match get_web_dir_path(web_ui_enabled, cfg.api.web_root.as_str()) {
|
|
|
|
|
Ok(result) => result,
|
|
|
|
|
Err(err) => return Err(err)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
exec_scheduler(&cfg, &targets);
|
|
|
|
|
exec_update_on_boot(&cfg, &targets);
|
|
|
|
|
let web_auth_enabled = is_web_auth_enabled(&cfg, web_ui_enabled, &web_dir_path);
|
|
|
|
|
|
|
|
|
|
let shared_data = create_shared_data(&cfg);
|
2023-09-26 19:41:02 +02:00
|
|
|
// Web Server
|
2024-05-02 15:22:23 +02:00
|
|
|
HttpServer::new(move || {
|
|
|
|
|
App::new()
|
|
|
|
|
.wrap(Logger::default())
|
|
|
|
|
.wrap(Cors::default()
|
|
|
|
|
.supports_credentials()
|
|
|
|
|
.allow_any_origin()
|
|
|
|
|
.allowed_methods(vec!["GET", "POST", "OPTIONS", "HEAD"])
|
|
|
|
|
.allow_any_header()
|
|
|
|
|
.max_age(3600))
|
|
|
|
|
.app_data(shared_data.clone())
|
2024-05-03 01:47:41 +02:00
|
|
|
// .wrap(Condition::new(web_auth_enabled, ErrorHandlers::new().handler(StatusCode::UNAUTHORIZED, handle_unauthorized)))
|
|
|
|
|
.configure(|srvcfg| {
|
2024-05-02 15:22:23 +02:00
|
|
|
if web_ui_enabled {
|
2024-05-03 01:47:41 +02:00
|
|
|
srvcfg.service(actix_files::Files::new("/static", web_dir_path.join("static")));
|
2024-05-03 11:08:15 +02:00
|
|
|
srvcfg.configure(v1_api_register(web_auth_enabled));
|
2024-05-02 15:22:23 +02:00
|
|
|
}
|
2024-05-17 18:05:48 +02:00
|
|
|
srvcfg.service(web::resource("/healthcheck").route(web::get().to(healthcheck)));
|
2024-05-02 15:22:23 +02:00
|
|
|
})
|
|
|
|
|
.configure(xtream_api_register)
|
|
|
|
|
.configure(m3u_api_register)
|
|
|
|
|
.configure(xmltv_api_register)
|
2024-05-03 01:47:41 +02:00
|
|
|
.configure(|srvcfg| {
|
2024-05-02 15:22:23 +02:00
|
|
|
if web_ui_enabled {
|
2024-05-03 11:08:15 +02:00
|
|
|
srvcfg.configure(index_register(&web_dir_path));
|
2024-05-02 15:22:23 +02:00
|
|
|
}
|
|
|
|
|
})
|
2024-05-10 12:01:48 +02:00
|
|
|
}).bind(format!("{host}:{port}"))?.run().await
|
2023-09-26 19:41:02 +02:00
|
|
|
}
|