Replaced actix with axum

This commit is contained in:
euzu
2025-03-11 16:07:06 +01:00
parent 988f87173e
commit fe50ecbb23
60 changed files with 1774 additions and 1934 deletions
+44 -33
View File
@@ -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;
}
}
}
+22 -26
View File
@@ -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)
}
}
+7 -4
View File
@@ -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>,
-7
View File
@@ -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()
}
}
+29 -21
View File
@@ -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)
}
-7
View File
@@ -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)]
+2
View File
@@ -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}"),
}
}
}
+13 -6
View File
@@ -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}");
}
});
}
}
+2 -2
View File
@@ -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();
+3 -2
View File
@@ -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,
+5 -5
View File
@@ -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());
+28 -28
View File
@@ -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
}
}
+4 -4
View File
@@ -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,