mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-16 04:42:18 +02:00
Replaced actix with axum
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use crate::model::config::{ConfigInput, ConfigInputAlias, InputType};
|
||||
use parking_lot::RwLock;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
/// This struct represents an individual provider configuration with fields like:
|
||||
///
|
||||
@@ -53,8 +53,8 @@ impl ProviderConfig {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_exhausted(&self) -> bool {
|
||||
self.max_connections > 0 && *self.current_connections.read() >= self.max_connections
|
||||
pub async fn is_exhausted(&self) -> bool {
|
||||
self.max_connections > 0 && *self.current_connections.read().await >= self.max_connections
|
||||
}
|
||||
//
|
||||
// #[inline]
|
||||
@@ -62,8 +62,8 @@ impl ProviderConfig {
|
||||
// !self.is_exhausted()
|
||||
// }
|
||||
|
||||
pub fn try_allocate(&self, force: bool) -> bool {
|
||||
let mut connections = self.current_connections.write();
|
||||
pub async fn try_allocate(&self, force: bool) -> bool {
|
||||
let mut connections = self.current_connections.write().await;
|
||||
if force || *connections < self.max_connections {
|
||||
*connections += 1;
|
||||
return true;
|
||||
@@ -71,8 +71,8 @@ impl ProviderConfig {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn release(&self) {
|
||||
let mut connections = self.current_connections.write();
|
||||
pub async fn release(&self) {
|
||||
let mut connections = self.current_connections.write().await;
|
||||
if *connections > 0 {
|
||||
*connections -= 1;
|
||||
}
|
||||
@@ -90,17 +90,17 @@ enum ProviderLineup {
|
||||
}
|
||||
|
||||
impl ProviderLineup {
|
||||
fn acquire(&self, force: bool) -> Option<&ProviderConfig> {
|
||||
async fn acquire(&self, force: bool) -> Option<&ProviderConfig> {
|
||||
match self {
|
||||
ProviderLineup::Single(lineup) => lineup.acquire(force),
|
||||
ProviderLineup::Multi(lineup) => lineup.acquire(force),
|
||||
ProviderLineup::Single(lineup) => lineup.acquire(force).await,
|
||||
ProviderLineup::Multi(lineup) => lineup.acquire(force).await,
|
||||
}
|
||||
}
|
||||
|
||||
fn release(&self, provider_id: u16) {
|
||||
async fn release(&self, provider_id: u16) {
|
||||
match self {
|
||||
ProviderLineup::Single(lineup) => lineup.release(provider_id),
|
||||
ProviderLineup::Multi(lineup) => lineup.release(provider_id),
|
||||
ProviderLineup::Single(lineup) => lineup.release(provider_id).await,
|
||||
ProviderLineup::Multi(lineup) => lineup.release(provider_id).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,17 +118,17 @@ impl SingleProviderLineup {
|
||||
}
|
||||
}
|
||||
|
||||
fn acquire(&self, force: bool) -> Option<&ProviderConfig> {
|
||||
if self.provider.try_allocate(force) {
|
||||
async fn acquire(&self, force: bool) -> Option<&ProviderConfig> {
|
||||
if self.provider.try_allocate(force).await {
|
||||
Some(&self.provider)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn release(&self, provider_id: u16) {
|
||||
async fn release(&self, provider_id: u16) {
|
||||
if self.provider.id == provider_id {
|
||||
self.provider.release();
|
||||
self.provider.release().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,10 +145,17 @@ enum ProviderPriorityGroup {
|
||||
}
|
||||
|
||||
impl ProviderPriorityGroup {
|
||||
fn is_exhausted(&self) -> bool {
|
||||
async fn is_exhausted(&self) -> bool {
|
||||
match self {
|
||||
ProviderPriorityGroup::SingleProviderGroup(g) => g.is_exhausted(),
|
||||
ProviderPriorityGroup::MultiProviderGroup(_, groups) => groups.iter().all(ProviderConfig::is_exhausted)
|
||||
ProviderPriorityGroup::SingleProviderGroup(g) => g.is_exhausted().await,
|
||||
ProviderPriorityGroup::MultiProviderGroup(_, groups) => {
|
||||
for g in groups {
|
||||
if !g.is_exhausted().await {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,10 +228,10 @@ impl MultiProviderLineup {
|
||||
/// println!("No available providers in group 0.");
|
||||
/// }
|
||||
/// ```
|
||||
fn acquire_next_provider_from_group(priority_group: &ProviderPriorityGroup) -> Option<&ProviderConfig> {
|
||||
async fn acquire_next_provider_from_group(priority_group: &ProviderPriorityGroup) -> Option<&ProviderConfig> {
|
||||
match priority_group {
|
||||
ProviderPriorityGroup::SingleProviderGroup(p) => {
|
||||
if p.try_allocate(false) {
|
||||
if p.try_allocate(false).await {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
@@ -234,7 +241,7 @@ impl MultiProviderLineup {
|
||||
for _ in 0..provider_count {
|
||||
let p = pg.get(idx).unwrap();
|
||||
idx = (idx + 1) % provider_count;
|
||||
if p.try_allocate(false) {
|
||||
if p.try_allocate(false).await {
|
||||
index.store(idx, Ordering::SeqCst);
|
||||
return Some(p);
|
||||
}
|
||||
@@ -275,15 +282,15 @@ impl MultiProviderLineup {
|
||||
/// println!("No available providers.");
|
||||
/// }
|
||||
/// ```
|
||||
fn acquire(&self, force: bool) -> Option<&ProviderConfig> {
|
||||
async fn acquire(&self, force: bool) -> Option<&ProviderConfig> {
|
||||
let mut main_idx = self.index.load(Ordering::SeqCst);
|
||||
let provider_count = self.providers.len();
|
||||
|
||||
for _ in 0..provider_count {
|
||||
let priority_group = &self.providers[main_idx];
|
||||
main_idx = (main_idx + 1) % provider_count;
|
||||
if let Some(provider) = Self::acquire_next_provider_from_group(priority_group) {
|
||||
if priority_group.is_exhausted() {
|
||||
if let Some(provider) = Self::acquire_next_provider_from_group(priority_group).await {
|
||||
if priority_group.is_exhausted().await {
|
||||
self.index.store(main_idx, Ordering::SeqCst);
|
||||
}
|
||||
return Some(provider);
|
||||
@@ -308,19 +315,19 @@ impl MultiProviderLineup {
|
||||
}
|
||||
|
||||
|
||||
fn release(&self, provider_id: u16) {
|
||||
async fn release(&self, provider_id: u16) {
|
||||
for g in &self.providers {
|
||||
match g {
|
||||
ProviderPriorityGroup::SingleProviderGroup(pc) => {
|
||||
if pc.id == provider_id {
|
||||
pc.release();
|
||||
pc.release().await;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ProviderPriorityGroup::MultiProviderGroup(_, group) => {
|
||||
for pc in group {
|
||||
if pc.id == provider_id {
|
||||
pc.release();
|
||||
pc.release().await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -328,6 +335,7 @@ impl MultiProviderLineup {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub struct ActiveProviderManager {
|
||||
@@ -352,13 +360,16 @@ impl ActiveProviderManager {
|
||||
self.providers.insert(name.to_string(), lineup);
|
||||
}
|
||||
|
||||
pub fn acquire_connection(&self, lineup_name: &str) -> Option<&ProviderConfig> {
|
||||
self.providers.get(lineup_name).and_then(|lineup| lineup.acquire(self.user_access_control))
|
||||
pub async fn acquire_connection(&self, lineup_name: &str) -> Option<&ProviderConfig> {
|
||||
match self.providers.get(lineup_name) {
|
||||
None => None,
|
||||
Some(lineup) => lineup.acquire(self.user_access_control).await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn release_connection(&self, lineup_name: &str, provider_id: u16) {
|
||||
pub async fn release_connection(&self, lineup_name: &str, provider_id: u16) {
|
||||
if let Some(lineup) = self.providers.get(lineup_name) {
|
||||
lineup.release(provider_id);
|
||||
lineup.release(provider_id).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use parking_lot::RwLock;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub struct ActiveUserManager {
|
||||
pub user: RwLock<HashMap<String, AtomicU32>>,
|
||||
@@ -19,44 +19,40 @@ impl ActiveUserManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn user_connections(&self, username: &str) -> u32 {
|
||||
if let Some(counter) = self.user.read().get(username) {
|
||||
pub async fn user_connections(&self, username: &str) -> u32 {
|
||||
if let Some(counter) = self.user.read().await.get(username) {
|
||||
return counter.load(std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
pub fn active_users(&self) -> usize {
|
||||
self.user.read().len()
|
||||
pub async fn active_users(&self) -> usize {
|
||||
self.user.read().await.len()
|
||||
}
|
||||
|
||||
pub fn active_connections(&self) -> usize {
|
||||
self.user.read().values().map(|c| c.load(Ordering::SeqCst) as usize).sum()
|
||||
pub async fn active_connections(&self) -> usize {
|
||||
self.user.read().await.values().map(|c| c.load(Ordering::SeqCst) as usize).sum()
|
||||
}
|
||||
|
||||
pub fn add_connection(&self, username: &str) -> (usize, usize) {
|
||||
{
|
||||
let mut lock = self.user.write();
|
||||
if let Some(counter) = lock.get(username) {
|
||||
counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
} else {
|
||||
lock.insert(username.to_string(), AtomicU32::new(1));
|
||||
}
|
||||
drop(lock);
|
||||
pub async fn add_connection(&self, username: &str) -> (usize, usize) {
|
||||
let mut lock = self.user.write().await;
|
||||
if let Some(counter) = lock.get(username) {
|
||||
counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
} else {
|
||||
lock.insert(username.to_string(), AtomicU32::new(1));
|
||||
}
|
||||
(self.active_users(), self.active_connections())
|
||||
drop(lock);
|
||||
(self.active_users().await, self.active_connections().await)
|
||||
}
|
||||
|
||||
pub fn remove_connection(&self, username: &str) -> (usize, usize) {
|
||||
{
|
||||
let mut lock = self.user.write();
|
||||
if let Some(counter) = lock.get(username) {
|
||||
if counter.fetch_sub(1, std::sync::atomic::Ordering::SeqCst) == 1 {
|
||||
lock.remove(username);
|
||||
}
|
||||
pub async fn remove_connection(&self, username: &str) -> (usize, usize) {
|
||||
let mut lock = self.user.write().await;
|
||||
if let Some(counter) = lock.get(username) {
|
||||
if counter.fetch_sub(1, std::sync::atomic::Ordering::SeqCst) == 1 {
|
||||
lock.remove(username);
|
||||
}
|
||||
drop(lock);
|
||||
}
|
||||
(self.active_users(), self.active_connections())
|
||||
drop(lock);
|
||||
(self.active_users().await, self.active_connections().await)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::sync::{Arc};
|
||||
use parking_lot::{Mutex};
|
||||
use tokio::sync::{Mutex};
|
||||
use std::sync::Arc;
|
||||
use crate::api::model::active_provider_manager::ActiveProviderManager;
|
||||
use crate::api::model::active_user_manager::ActiveUserManager;
|
||||
use crate::api::model::download::DownloadQueue;
|
||||
@@ -8,6 +8,8 @@ use crate::model::config::{Config};
|
||||
use crate::model::hdhomerun_config::HdHomeRunDeviceConfig;
|
||||
use crate::tools::lru_cache::LRUResourceCache;
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub config: Arc<Config>,
|
||||
pub http_client: Arc<reqwest::Client>,
|
||||
@@ -19,11 +21,12 @@ pub struct AppState {
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn get_active_connections_for_user(&self, username: &str) -> u32 {
|
||||
self.active_users.user_connections(username)
|
||||
pub async fn get_active_connections_for_user(&self, username: &str) -> u32 {
|
||||
self.active_users.user_connections(username).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HdHomerunAppState {
|
||||
pub app_state: Arc<AppState>,
|
||||
pub device: Arc<HdHomeRunDeviceConfig>,
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::ffi::OsStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tokio::sync::{RwLock, Mutex};
|
||||
use std::sync::Arc;
|
||||
use actix_web::web;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use unidecode::unidecode;
|
||||
|
||||
@@ -141,9 +140,3 @@ pub struct FileDownloadRequest {
|
||||
pub url: String,
|
||||
pub filename: String,
|
||||
}
|
||||
|
||||
impl From<web::Json<Self>> for FileDownloadRequest {
|
||||
fn from(req: web::Json<Self>) -> Self {
|
||||
req.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use crate::utils::debug_if_enabled;
|
||||
use actix_web::http::header::{HeaderName, HeaderValue};
|
||||
use actix_web::{HttpResponseBuilder};
|
||||
use reqwest::{StatusCode};
|
||||
use std::collections::{HashSet};
|
||||
use std::str::FromStr;
|
||||
@@ -16,35 +14,45 @@ pub fn get_response_headers(headers: &HeaderMap) -> Vec<(String, String)> {
|
||||
response_headers
|
||||
}
|
||||
|
||||
pub fn get_stream_response_with_headers(custom: Option<(Vec<(String, String)>, StatusCode)>, stream_url: &str) -> HttpResponseBuilder {
|
||||
let mut headers = Vec::<(HeaderName, HeaderValue)>::with_capacity(12);
|
||||
pub fn get_stream_response_with_headers(custom: Option<(Vec<(String, String)>, StatusCode)>, stream_url: &str) -> (axum::http::StatusCode, axum::http::HeaderMap) {
|
||||
let mut headers = HeaderMap::new();
|
||||
let mut added_headers: HashSet<String> = HashSet::new();
|
||||
let mut status = 200_u16;
|
||||
let mut status = StatusCode::OK;
|
||||
|
||||
if let Some((custom_headers, status_code)) = custom {
|
||||
status = status_code.as_u16();
|
||||
for header in custom_headers {
|
||||
headers.push((HeaderName::from_str(&header.0).unwrap(), HeaderValue::from_str(header.1.as_str()).unwrap()));
|
||||
added_headers.insert(header.0.to_string());
|
||||
status = status_code;
|
||||
for (key, value) in custom_headers {
|
||||
if let (Ok(name), Ok(val)) = (axum::http::HeaderName::from_str(&key), axum::http::HeaderValue::from_str(&value)) {
|
||||
headers.insert(name.clone(), val);
|
||||
added_headers.insert(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let default_headers = vec![
|
||||
(actix_web::http::header::CONTENT_TYPE, HeaderValue::from_str("application/octet-stream").unwrap()),
|
||||
(actix_web::http::header::CONNECTION, HeaderValue::from_str("keep-alive").unwrap()),
|
||||
("content-type", "application/octet-stream"),
|
||||
("connection", "keep-alive"),
|
||||
];
|
||||
|
||||
for header in default_headers {
|
||||
if !added_headers.contains(header.0.as_str()) {
|
||||
headers.push(header);
|
||||
for (key, value) in default_headers {
|
||||
if !added_headers.contains(key) {
|
||||
if let (Ok(name), Ok(val)) = (axum::http::HeaderName::from_str(key), axum::http::HeaderValue::from_str(value)) {
|
||||
headers.insert(name, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
headers.push((actix_web::http::header::DATE, HeaderValue::from_str(&chrono::Utc::now().to_rfc2822()).unwrap()));
|
||||
|
||||
let mut response_builder = actix_web::HttpResponse::build(actix_web::http::StatusCode::from_u16(status).unwrap());
|
||||
debug_if_enabled!("Responding stream request {} with status {status}, headers {headers:?}", sanitize_sensitive_info(stream_url));
|
||||
for header in headers {
|
||||
response_builder.insert_header(header);
|
||||
// Füge das aktuelle Datum hinzu
|
||||
if let Ok(date_header) = axum::http::HeaderValue::from_str(&chrono::Utc::now().to_rfc2822()) {
|
||||
headers.insert(axum::http::HeaderName::from_static("date"), date_header);
|
||||
}
|
||||
response_builder
|
||||
|
||||
debug_if_enabled!(
|
||||
"Responding stream request {} with status {}, headers {:?}",
|
||||
sanitize_sensitive_info(stream_url),
|
||||
status,
|
||||
headers
|
||||
);
|
||||
|
||||
(status, headers)
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
use actix_web::web;
|
||||
use serde::de::{self, Deserializer, Unexpected};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -43,12 +42,6 @@ pub struct PlaylistRequest {
|
||||
pub source_name: Option<String>,
|
||||
}
|
||||
|
||||
impl From<web::Json<Self>> for PlaylistRequest {
|
||||
fn from(req: web::Json<Self>) -> Self {
|
||||
req.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Default)]
|
||||
pub struct UserApiRequest {
|
||||
#[serde(default)]
|
||||
|
||||
@@ -6,6 +6,7 @@ pub enum StreamError {
|
||||
// StdIo(std::io::Error),
|
||||
// ReceiverClosed,
|
||||
ReceiverError(BroadcastStreamRecvError),
|
||||
LockError(String)
|
||||
}
|
||||
|
||||
impl StreamError {
|
||||
@@ -26,6 +27,7 @@ impl std::fmt::Display for StreamError {
|
||||
// StreamError::StdIo(e) => write!(f, "IO error: {e}"),
|
||||
// StreamError::ReceiverClosed => write!(f, "Receiver closed"),
|
||||
StreamError::ReceiverError(e) => write!(f, "Receiver error {e}"),
|
||||
StreamError::LockError(e) => write!(f, "{e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ pub(in crate::api) struct ActiveClientStream {
|
||||
}
|
||||
|
||||
impl ActiveClientStream {
|
||||
pub(crate) fn new(inner: ResponseStream, active_clients: Arc<ActiveUserManager>, user: &ProxyUserCredentials, log_active_clients: bool) -> Self {
|
||||
let (client_count, connection_count) = active_clients.add_connection(&user.username);
|
||||
pub(crate) async fn new(inner: ResponseStream, active_clients: Arc<ActiveUserManager>, user: &ProxyUserCredentials, log_active_clients: bool) -> Self {
|
||||
let (client_count, connection_count) = active_clients.add_connection(&user.username).await;
|
||||
if log_active_clients {
|
||||
info!("Active clients: {client_count}, active connections {connection_count}");
|
||||
}
|
||||
@@ -36,9 +36,16 @@ impl Stream for ActiveClientStream {
|
||||
|
||||
impl Drop for ActiveClientStream {
|
||||
fn drop(&mut self) {
|
||||
let (client_count, connection_count) = self.active_clients.remove_connection(&self.username);
|
||||
if self.log_active_clients {
|
||||
info!("Active clients: {client_count}, active connections {connection_count}");
|
||||
}
|
||||
let username = self.username.clone();
|
||||
let log_active_clients = self.log_active_clients;
|
||||
let active_clients = Arc::clone(&self.active_clients);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let username = username.clone();
|
||||
let (client_count, connection_count) = active_clients.remove_connection(&username).await;
|
||||
if log_active_clients {
|
||||
info!("Active clients: {client_count}, active connections {connection_count}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ pub(in crate::api::model) struct BufferedStream {
|
||||
impl BufferedStream {
|
||||
pub fn new(stream: ResponseStream, buffer_size: usize, client_close_signal: Arc<AtomicOnceFlag>, _url: &str) -> Self {
|
||||
let (tx, rx) = channel(buffer_size);
|
||||
actix_rt::spawn(Self::buffer_stream(tx, stream, client_close_signal));
|
||||
tokio::spawn(Self::buffer_stream(tx, stream, client_close_signal));
|
||||
Self {
|
||||
stream: ReceiverStream::new(rx)
|
||||
}
|
||||
@@ -45,7 +45,7 @@ impl BufferedStream {
|
||||
}
|
||||
Some(Err(err)) => {
|
||||
trace!("Buffered Stream Error: {err:?}");
|
||||
// actix_web::rt::time::sleep(sleep_duration).await;
|
||||
// tokio::time::sleep(sleep_duration).await;
|
||||
// Attempt to send error to client
|
||||
if tx.send(Err(err)).await.is_err() {
|
||||
client_close_signal.notify();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::io::Write;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::task::{Context, Poll};
|
||||
use bytes::Bytes;
|
||||
@@ -26,7 +27,7 @@ pub struct PersistPipeStream<S, W> {
|
||||
completed: bool,
|
||||
writer: W,
|
||||
size: AtomicUsize,
|
||||
callback: Box<dyn Fn(usize)>,
|
||||
callback: Arc<dyn Fn(usize) + Send + Sync>,
|
||||
}
|
||||
|
||||
impl<S, W> PersistPipeStream<S, W>
|
||||
@@ -39,7 +40,7 @@ where
|
||||
/// - `inner`: The input stream providing the data.
|
||||
/// - `writer`: The writer to which the data is written.
|
||||
/// - `callback`: A callback function to be called with the total size upon stream completion.
|
||||
pub fn new(inner: S, writer: W, callback: Box<dyn Fn(usize)>) -> Self {
|
||||
pub fn new(inner: S, writer: W, callback: Arc<dyn Fn(usize) + Send + Sync>) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
completed: false,
|
||||
|
||||
@@ -7,13 +7,13 @@ use crate::model::config::{Config, ConfigInput};
|
||||
use crate::model::playlist::PlaylistItemType;
|
||||
use crate::utils::debug_if_enabled;
|
||||
use crate::utils::network::request::{get_request_headers, sanitize_sensitive_info};
|
||||
use actix_web::HttpRequest;
|
||||
use bytes::Bytes;
|
||||
use futures::stream::BoxStream;
|
||||
use futures::TryStreamExt;
|
||||
use log::{debug, error};
|
||||
use reqwest::StatusCode;
|
||||
use std::sync::Arc;
|
||||
use axum::http::HeaderMap;
|
||||
use url::Url;
|
||||
|
||||
type BoxedProviderStream = BoxStream<'static, Result<Bytes, StreamError>>;
|
||||
@@ -45,11 +45,11 @@ pub fn get_header_filter_for_item_type(item_type: PlaylistItemType) -> HeaderFil
|
||||
pub async fn get_provider_pipe_stream(cfg: &Config,
|
||||
http_client: &Arc<reqwest::Client>,
|
||||
stream_url: &Url,
|
||||
req: &HttpRequest,
|
||||
req_headers: &HeaderMap,
|
||||
input: Option<&ConfigInput>,
|
||||
item_type: PlaylistItemType) -> ProviderStreamResponse {
|
||||
let filter_header = get_header_filter_for_item_type(item_type);
|
||||
let req_headers = get_headers_from_request(req, &filter_header);
|
||||
let req_headers = get_headers_from_request(req_headers, &filter_header);
|
||||
debug_if_enabled!("Stream requested with headers: {:?}", req_headers.iter().map(|header| (header.0, String::from_utf8_lossy(header.1))).collect::<Vec<_>>());
|
||||
// These are the configured headers for this input.
|
||||
let input_headers = input.map(|i| i.headers.clone());
|
||||
@@ -84,10 +84,10 @@ pub async fn get_provider_pipe_stream(cfg: &Config,
|
||||
pub async fn get_provider_reconnect_buffered_stream(cfg: &Config,
|
||||
http_client: &Arc<reqwest::Client>,
|
||||
stream_url: &Url,
|
||||
req: &HttpRequest,
|
||||
req_headers: &HeaderMap,
|
||||
input: Option<&ConfigInput>,
|
||||
options: BufferStreamOptions) -> ProviderStreamResponse {
|
||||
match create_provider_stream(cfg, Arc::clone(http_client), stream_url, req, input, options).await {
|
||||
match create_provider_stream(cfg, Arc::clone(http_client), stream_url, req_headers, input, options).await {
|
||||
None => (None, None),
|
||||
Some((stream, info)) => {
|
||||
(Some(stream), info)
|
||||
|
||||
@@ -9,7 +9,6 @@ use crate::model::playlist::PlaylistItemType;
|
||||
use crate::tools::atomic_once_flag::AtomicOnceFlag;
|
||||
use crate::utils::debug_if_enabled;
|
||||
use crate::utils::network::request::{classify_content_type, get_request_headers, sanitize_sensitive_info, MimeCategory};
|
||||
use actix_web::HttpRequest;
|
||||
use bytes::Bytes;
|
||||
use futures::stream::{self, BoxStream};
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
@@ -153,7 +152,7 @@ impl ProviderStreamOptions {
|
||||
|
||||
fn get_request_range_start_bytes(req_headers: &HashMap<String, Vec<u8>>) -> Option<usize> {
|
||||
// range header looks like bytes=1234-5566/2345345 or bytes=0-
|
||||
if let Some(req_range) = req_headers.get(actix_web::http::header::RANGE.as_str()) {
|
||||
if let Some(req_range) = req_headers.get(axum::http::header::RANGE.as_str()) {
|
||||
if let Some(bytes_range) = req_range.strip_prefix(b"bytes=") {
|
||||
if let Some(index) = bytes_range.iter().position(|&x| x == b'-') {
|
||||
let start_bytes = &bytes_range[..index];
|
||||
@@ -169,13 +168,13 @@ fn get_request_range_start_bytes(req_headers: &HashMap<String, Vec<u8>>) -> Opti
|
||||
}
|
||||
|
||||
fn get_client_stream_request_params(
|
||||
req: &HttpRequest,
|
||||
req_headers: &HeaderMap,
|
||||
input: Option<&ConfigInput>,
|
||||
options: &BufferStreamOptions) -> (usize, Option<usize>, bool, HeaderMap)
|
||||
{
|
||||
let stream_buffer_size = if options.is_buffer_enabled() { options.get_stream_buffer_size() } else { 1 };
|
||||
let filter_header = get_header_filter_for_item_type(options.item_type);
|
||||
let mut req_headers = get_headers_from_request(req, &filter_header);
|
||||
let mut req_headers = get_headers_from_request(req_headers, &filter_header);
|
||||
debug_if_enabled!("Stream requested with headers: {:?}", req_headers.iter().map(|header| (header.0, String::from_utf8_lossy(header.1))).collect::<Vec<_>>());
|
||||
// we need the range bytes from client request for seek ing to the right position
|
||||
let req_range_start_bytes = get_request_range_start_bytes(&req_headers);
|
||||
@@ -277,7 +276,7 @@ async fn stream_provider(client: Arc<reqwest::Client>, stream_options: ProviderS
|
||||
if !stream_options.should_continue() {
|
||||
return None;
|
||||
}
|
||||
actix_web::rt::time::sleep(Duration::from_millis(100)).await;
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
debug_if_enabled!("Stopped reconnecting stream {}", sanitize_sensitive_info(url.as_str()));
|
||||
None
|
||||
@@ -310,17 +309,17 @@ async fn get_initial_stream(cfg: &Config, client: Arc<reqwest::Client>, stream_o
|
||||
break;
|
||||
}
|
||||
connect_err += 1;
|
||||
actix_web::rt::time::sleep(Duration::from_millis(100)).await;
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
stream_options.cancel_reconnect();
|
||||
None
|
||||
}
|
||||
|
||||
fn create_provider_stream_options(stream_url: &Url,
|
||||
req: &HttpRequest,
|
||||
req_headers: &HeaderMap,
|
||||
input: Option<&ConfigInput>,
|
||||
options: &BufferStreamOptions) -> ProviderStreamOptions {
|
||||
let (buffer_size, req_range_start_bytes, reconnect, headers) = get_client_stream_request_params(req, input, options);
|
||||
let (buffer_size, req_range_start_bytes, reconnect, headers) = get_client_stream_request_params(req_headers, input, options);
|
||||
let url = stream_url.clone();
|
||||
let range_bytes = Arc::new(req_range_start_bytes.map(AtomicUsize::new));
|
||||
let continue_flag = Arc::new(AtomicOnceFlag::new());
|
||||
@@ -338,10 +337,10 @@ fn create_provider_stream_options(stream_url: &Url,
|
||||
pub async fn create_provider_stream(cfg: &Config,
|
||||
client: Arc<reqwest::Client>,
|
||||
stream_url: &Url,
|
||||
req: &HttpRequest,
|
||||
req_headers: &HeaderMap,
|
||||
input: Option<&ConfigInput>,
|
||||
options: BufferStreamOptions) -> Option<ProviderStreamFactoryResponse> {
|
||||
let stream_options = create_provider_stream_options(stream_url, req, input, &options);
|
||||
let stream_options = create_provider_stream_options(stream_url, req_headers, input, &options);
|
||||
|
||||
let client_stream_factory = |stream, reconnect_flag, range_cnt| {
|
||||
let stream = if stream_options.is_buffered() && !options.is_shared_stream() {
|
||||
@@ -407,7 +406,7 @@ pub async fn create_provider_stream(cfg: &Config,
|
||||
// let req = TestRequest::get().uri("/test").to_request();
|
||||
// let _response = test::call_service(&server, req).await;
|
||||
// }
|
||||
// async fn test_stream_handler(req: HttpRequest) -> HttpResponse {
|
||||
// async fn test_stream_handler(req: axum::http::Request<axum::body::Body>) -> impl axum::response::IntoResponse + Send {
|
||||
// let cfg = Config::default();
|
||||
// let mut counter = 5;
|
||||
// let client = Arc::new(reqwest::Client::new());
|
||||
|
||||
@@ -3,12 +3,12 @@ use crate::api::model::streams::provider_stream_factory::STREAM_QUEUE_SIZE;
|
||||
use crate::api::model::stream_error::StreamError;
|
||||
use crate::utils::debug_if_enabled;
|
||||
use crate::utils::network::request::sanitize_sensitive_info;
|
||||
use parking_lot::{RwLock};
|
||||
use bytes::Bytes;
|
||||
use futures::stream::BoxStream;
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc};
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::sync::mpsc::{Sender};
|
||||
|
||||
use std::pin::Pin;
|
||||
@@ -73,16 +73,16 @@ impl SharedStreamState {
|
||||
}
|
||||
}
|
||||
|
||||
fn subscribe(&self) -> BoxStream<'static, Result<Bytes, StreamError>> {
|
||||
async fn subscribe(&self) -> BoxStream<'static, Result<Bytes, StreamError>> {
|
||||
let (tx, rx) = mpsc::channel(self.buf_size);
|
||||
self.subscribers.write().push(tx);
|
||||
self.subscribers.write().await.push(tx);
|
||||
convert_stream(ReceiverStream::new(rx).boxed())
|
||||
}
|
||||
|
||||
fn broadcast<S, E>(&self, stream_url: &str, bytes_stream: S, shared_streams: Arc<SharedStreamManager>)
|
||||
where
|
||||
S: Stream<Item=Result<Bytes, E>> + Unpin + 'static,
|
||||
E: std::fmt::Debug
|
||||
S: Stream<Item=Result<Bytes, E>> + Unpin + 'static + std::marker::Send,
|
||||
E: std::fmt::Debug + std::marker::Send
|
||||
{
|
||||
let starving_size = self.buf_size-4;
|
||||
let sleep_duration = Duration::from_millis(10);
|
||||
@@ -92,28 +92,28 @@ impl SharedStreamState {
|
||||
let streaming_url = stream_url.to_string();
|
||||
|
||||
//Spawn a task to forward items from the source stream to the broadcast channel
|
||||
actix_rt::spawn(async move {
|
||||
tokio::spawn(async move {
|
||||
while let Some(item) = source_stream.next().await {
|
||||
if let Ok(data) = item {
|
||||
if subscriber.read().is_empty() {
|
||||
if subscriber.read().await.is_empty() {
|
||||
debug_if_enabled!("No active subscribers. Closing shared provider stream {}", sanitize_sensitive_info(&streaming_url));
|
||||
// Cleanup for removing unused shared streams
|
||||
shared_streams.unregister(&streaming_url);
|
||||
shared_streams.unregister(&streaming_url).await;
|
||||
break;
|
||||
}
|
||||
|
||||
let start_time = Instant::now();
|
||||
loop {
|
||||
if subscriber.read().iter().any(|sender| sender.capacity() >= starving_size) {
|
||||
if subscriber.read().await.iter().any(|sender| sender.capacity() >= starving_size) {
|
||||
break;
|
||||
}
|
||||
actix_web::rt::time::sleep(sleep_duration_starve).await;
|
||||
tokio::time::sleep(sleep_duration_starve).await;
|
||||
if start_time.elapsed().as_secs() > 5 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut subs = subscriber.write();
|
||||
let mut subs = subscriber.write().await;
|
||||
// TODO use drain_filter
|
||||
(*subs).retain(|sender| {
|
||||
match sender.try_send(data.clone()) {
|
||||
@@ -126,10 +126,10 @@ impl SharedStreamState {
|
||||
}
|
||||
});
|
||||
}
|
||||
actix_web::rt::time::sleep(sleep_duration).await;
|
||||
tokio::time::sleep(sleep_duration).await;
|
||||
}
|
||||
debug_if_enabled!("Shared stream exhausted. Closing shared provider stream {}", sanitize_sensitive_info(&streaming_url));
|
||||
shared_streams.unregister(&streaming_url);
|
||||
shared_streams.unregister(&streaming_url).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -147,46 +147,46 @@ impl SharedStreamManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_shared_state_headers(&self, stream_url: &str) -> Option<Vec<(String, String)>> {
|
||||
self.shared_streams.read().get(stream_url).map(|s| s.headers.clone())
|
||||
pub async fn get_shared_state_headers(&self, stream_url: &str) -> Option<Vec<(String, String)>> {
|
||||
self.shared_streams.read().await.get(stream_url).map(|s| s.headers.clone())
|
||||
}
|
||||
|
||||
fn unregister(&self, stream_url: &str) {
|
||||
self.shared_streams.write().remove(stream_url);
|
||||
async fn unregister(&self, stream_url: &str) {
|
||||
let _ = self.shared_streams.write().await.remove(stream_url);
|
||||
}
|
||||
|
||||
fn subscribe_stream(&self, stream_url: &str) -> Option<BoxStream<'static, Result<Bytes, StreamError>>> {
|
||||
let stream_data = self.shared_streams.read().get(stream_url)?.subscribe();
|
||||
async fn subscribe_stream(&self, stream_url: &str) -> Option<BoxStream<'static, Result<Bytes, StreamError>>> {
|
||||
let stream_data = self.shared_streams.read().await.get(stream_url)?.subscribe().await;
|
||||
Some(stream_data)
|
||||
}
|
||||
|
||||
fn register(&self, stream_url: &str, shared_state: SharedStreamState) {
|
||||
self.shared_streams.write().insert(stream_url.to_string(), shared_state);
|
||||
async fn register(&self, stream_url: &str, shared_state: SharedStreamState) {
|
||||
let _= self.shared_streams.write().await.insert(stream_url.to_string(), shared_state);
|
||||
}
|
||||
|
||||
pub(crate) fn subscribe<S, E>(
|
||||
pub(crate) async fn subscribe<S, E>(
|
||||
app_state: &AppState,
|
||||
stream_url: &str,
|
||||
bytes_stream: S,
|
||||
headers: Vec<(String, String)>,
|
||||
buffer_size: usize,)
|
||||
where
|
||||
S: Stream<Item=Result<Bytes, E>> + Unpin + 'static,
|
||||
E: std::fmt::Debug
|
||||
S: Stream<Item=Result<Bytes, E>> + Unpin + 'static + std::marker::Send,
|
||||
E: std::fmt::Debug + std::marker::Send
|
||||
{
|
||||
let buf_size = std::cmp::max(buffer_size, STREAM_QUEUE_SIZE);
|
||||
let shared_state = SharedStreamState::new(headers, buf_size);
|
||||
shared_state.broadcast(stream_url, bytes_stream, Arc::clone(&app_state.shared_stream_manager));
|
||||
app_state.shared_stream_manager.register(stream_url, shared_state);
|
||||
app_state.shared_stream_manager.register(stream_url, shared_state).await;
|
||||
debug_if_enabled!("Created shared provider stream {}", sanitize_sensitive_info(stream_url));
|
||||
}
|
||||
|
||||
/// Creates a broadcast notify stream for the given URL if a shared stream exists.
|
||||
pub fn subscribe_shared_stream(
|
||||
pub async fn subscribe_shared_stream(
|
||||
app_state: &AppState,
|
||||
stream_url: &str,
|
||||
) -> Option<BoxStream<'static, Result<Bytes, StreamError>>> {
|
||||
debug_if_enabled!("Responding existing shared client stream {}", sanitize_sensitive_info(stream_url));
|
||||
app_state.shared_stream_manager.subscribe_stream(stream_url)
|
||||
app_state.shared_stream_manager.subscribe_stream(stream_url).await
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::model::api_proxy::{ApiProxyServerInfo, ProxyUserCredentials, ProxyUserStatus};
|
||||
use chrono::{Duration, Local};
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct XtreamUserInfoResponse {
|
||||
pub password: String,
|
||||
pub username: String,
|
||||
@@ -17,7 +17,7 @@ pub struct XtreamUserInfoResponse {
|
||||
pub status: String, // "Active"
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct XtreamServerInfoResponse {
|
||||
pub url: String,
|
||||
pub port: String,
|
||||
@@ -29,7 +29,7 @@ pub struct XtreamServerInfoResponse {
|
||||
pub time_now: String, //"2021-06-28 17:07:37"
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct XtreamAuthorizationResponse {
|
||||
pub user_info: XtreamUserInfoResponse,
|
||||
pub server_info: XtreamServerInfoResponse,
|
||||
|
||||
Reference in New Issue
Block a user