mirror of
https://github.com/localsend/localsend.git
synced 2026-08-07 07:14:52 +00:00
refactor: move WebSendConfig out of v2
This commit is contained in:
@@ -3,7 +3,7 @@ use crate::http::dto_v2::{
|
||||
};
|
||||
use crate::http::server::controller::check_pin;
|
||||
use crate::http::server::error::AppError;
|
||||
use crate::http::server::event::ServerEventV2;
|
||||
use crate::http::server::event::WebSendEvent;
|
||||
use crate::http::server::query::parse_query;
|
||||
use crate::http::server::response::{full_body, BoxedBody, JsonResponse};
|
||||
use crate::http::server::{AppState, RequestClientInfo};
|
||||
@@ -48,6 +48,8 @@ const FILE_NAME_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
|
||||
.remove(b')');
|
||||
|
||||
/// Configuration for web send (download API): files offered for download by web browsers.
|
||||
///
|
||||
/// Web send can be enabled independently of the v2/v3 protocol endpoints.
|
||||
pub struct WebSendConfig {
|
||||
/// The files offered for download, mapped by file ID.
|
||||
pub files: HashMap<String, WebSendFile>,
|
||||
@@ -57,6 +59,9 @@ pub struct WebSendConfig {
|
||||
|
||||
/// Translations for the web page, served via `/i18n.json`.
|
||||
pub i18n: WebSendI18n,
|
||||
|
||||
/// Channel on which the server emits events that must be handled by the application.
|
||||
pub event_tx: mpsc::Sender<WebSendEvent>,
|
||||
}
|
||||
|
||||
/// A file offered for download.
|
||||
@@ -118,7 +123,7 @@ pub(crate) struct WebPageState {
|
||||
pub(crate) i18n: WebSendI18n,
|
||||
|
||||
/// Channel on which server events are emitted to the application.
|
||||
pub(crate) event_tx: mpsc::Sender<ServerEventV2>,
|
||||
pub(crate) event_tx: mpsc::Sender<WebSendEvent>,
|
||||
|
||||
/// Download sessions, keyed by session ID (the client's IP address).
|
||||
pub(crate) sessions: Mutex<HashMap<String, WebSendSession>>,
|
||||
@@ -128,12 +133,12 @@ pub(crate) struct WebPageState {
|
||||
}
|
||||
|
||||
impl WebPageState {
|
||||
pub(crate) fn new(config: WebSendConfig, event_tx: mpsc::Sender<ServerEventV2>) -> Self {
|
||||
pub(crate) fn new(config: WebSendConfig) -> Self {
|
||||
Self {
|
||||
files: config.files,
|
||||
pin: config.pin,
|
||||
i18n: config.i18n,
|
||||
event_tx,
|
||||
event_tx: config.event_tx,
|
||||
sessions: Mutex::new(HashMap::new()),
|
||||
pin_attempts: Mutex::new(LruCache::new(NonZeroUsize::new(200).unwrap())),
|
||||
}
|
||||
@@ -225,7 +230,7 @@ pub(crate) async fn prepare_download(
|
||||
let mut pending_guard = PendingWebSessionGuard::new(web.clone(), session_id.clone());
|
||||
|
||||
let (decision_tx, decision_rx) = oneshot::channel();
|
||||
let event = ServerEventV2::PrepareDownload {
|
||||
let event = WebSendEvent::PrepareDownload {
|
||||
ip: client_info.ip,
|
||||
session_id: session_id.clone(),
|
||||
user_agent,
|
||||
|
||||
@@ -58,6 +58,20 @@ pub enum ServerEventV2 {
|
||||
result_tx: oneshot::Sender<Result<(), String>>,
|
||||
},
|
||||
|
||||
/// An upload session ended.
|
||||
SessionEnd {
|
||||
/// The session ID of the ended session.
|
||||
session_id: String,
|
||||
|
||||
/// Why the session ended.
|
||||
reason: SessionEndReasonV2,
|
||||
},
|
||||
}
|
||||
|
||||
/// Events emitted by the web send (download API) endpoints that must be handled
|
||||
/// by the application. Web send can be enabled independently of the v2 endpoints.
|
||||
#[derive(Debug)]
|
||||
pub enum WebSendEvent {
|
||||
/// A web client requests to download the shared files
|
||||
/// via `POST /api/localsend/v2/prepare-download`.
|
||||
///
|
||||
@@ -76,15 +90,6 @@ pub enum ServerEventV2 {
|
||||
/// Channel to send the decision (`true` to accept, `false` to decline).
|
||||
decision_tx: oneshot::Sender<bool>,
|
||||
},
|
||||
|
||||
/// An upload session ended.
|
||||
SessionEnd {
|
||||
/// The session ID of the ended session.
|
||||
session_id: String,
|
||||
|
||||
/// Why the session ended.
|
||||
reason: SessionEndReasonV2,
|
||||
},
|
||||
}
|
||||
|
||||
/// The application's decision for a prepare-upload request.
|
||||
|
||||
+17
-20
@@ -40,10 +40,6 @@ pub struct ServerConfigV2 {
|
||||
|
||||
/// Channel on which the server emits events that must be handled by the application.
|
||||
pub event_tx: mpsc::Sender<ServerEventV2>,
|
||||
|
||||
/// Configuration for web send (download API).
|
||||
/// `None` disables the web page and download routes.
|
||||
pub web_send: Option<WebSendConfig>,
|
||||
}
|
||||
|
||||
/// Runtime state of the v2 protocol endpoints.
|
||||
@@ -80,21 +76,21 @@ struct AppState {
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
fn new(info: Arc<Mutex<ClientInfo>>, v2_config: Option<ServerConfigV2>) -> Self {
|
||||
let (v2, web) = match v2_config {
|
||||
Some(config) => (
|
||||
Some(Arc::new(V2State {
|
||||
pin: config.pin,
|
||||
event_tx: config.event_tx.clone(),
|
||||
session: Mutex::new(None),
|
||||
pin_attempts: Mutex::new(LruCache::new(NonZeroUsize::new(200).unwrap())),
|
||||
})),
|
||||
config
|
||||
.web_send
|
||||
.map(|web_config| Arc::new(WebPageState::new(web_config, config.event_tx))),
|
||||
),
|
||||
None => (None, None),
|
||||
};
|
||||
fn new(
|
||||
info: Arc<Mutex<ClientInfo>>,
|
||||
v2_config: Option<ServerConfigV2>,
|
||||
web_send_config: Option<WebSendConfig>,
|
||||
) -> Self {
|
||||
let v2 = v2_config.map(|config| {
|
||||
Arc::new(V2State {
|
||||
pin: config.pin,
|
||||
event_tx: config.event_tx,
|
||||
session: Mutex::new(None),
|
||||
pin_attempts: Mutex::new(LruCache::new(NonZeroUsize::new(200).unwrap())),
|
||||
})
|
||||
});
|
||||
|
||||
let web = web_send_config.map(|config| Arc::new(WebPageState::new(config)));
|
||||
|
||||
Self {
|
||||
info,
|
||||
@@ -116,12 +112,13 @@ pub async fn start_with_port(
|
||||
tls_config: Option<TlsConfig>,
|
||||
info: ClientInfo,
|
||||
v2_config: Option<ServerConfigV2>,
|
||||
web_send_config: Option<WebSendConfig>,
|
||||
stop_rx: oneshot::Receiver<()>,
|
||||
) -> anyhow::Result<()> {
|
||||
let ipv4_socket_addr = SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), port);
|
||||
let ipv6_socket_addr = SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), port);
|
||||
let info = Arc::new(Mutex::new(info));
|
||||
let state = AppState::new(info.clone(), v2_config);
|
||||
let state = AppState::new(info.clone(), v2_config, web_send_config);
|
||||
|
||||
let ipv4_listener = tokio::net::TcpListener::bind(ipv4_socket_addr).await?;
|
||||
let ipv6_listener = match bind_ipv6_only(ipv6_socket_addr) {
|
||||
|
||||
+1
-10
@@ -184,15 +184,6 @@ async fn server_test() -> Result<()> {
|
||||
let _ = result_tx.send(Ok(()));
|
||||
});
|
||||
}
|
||||
ServerEventV2::PrepareDownload {
|
||||
ip,
|
||||
session_id,
|
||||
decision_tx,
|
||||
..
|
||||
} => {
|
||||
tracing::info!("Prepare download from {ip} (session {session_id})");
|
||||
let _ = decision_tx.send(true);
|
||||
}
|
||||
ServerEventV2::SessionEnd { session_id, reason } => {
|
||||
tracing::info!("Session {session_id} ended: {reason:?}");
|
||||
}
|
||||
@@ -210,8 +201,8 @@ async fn server_test() -> Result<()> {
|
||||
Some(ServerConfigV2 {
|
||||
pin: None,
|
||||
event_tx,
|
||||
web_send: None,
|
||||
}),
|
||||
None,
|
||||
stop_rx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -68,7 +68,6 @@ async fn start_test_server(pin: Option<String>, accept: bool) -> TestServer {
|
||||
ServerEventV2::SessionEnd { session_id, reason } => {
|
||||
session_ends.lock().await.push((session_id, reason));
|
||||
}
|
||||
ServerEventV2::PrepareDownload { .. } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,8 +88,8 @@ async fn start_test_server(pin: Option<String>, accept: bool) -> TestServer {
|
||||
Some(ServerConfigV2 {
|
||||
pin,
|
||||
event_tx,
|
||||
web_send: None,
|
||||
}),
|
||||
None,
|
||||
stop_rx,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use bytes::Bytes;
|
||||
use localsend::http::client::{ClientError, LsHttpClientV2};
|
||||
use localsend::http::dto::ProtocolType;
|
||||
use localsend::http::server::event::ServerEventV2;
|
||||
use localsend::http::server::event::{ServerEventV2, WebSendEvent};
|
||||
use localsend::http::server::{
|
||||
start_with_port, ServerConfigV2, WebSendConfig, WebSendFile, WebSendFileContent, WebSendI18n,
|
||||
};
|
||||
@@ -29,22 +29,33 @@ async fn start_test_server(web_send: Option<WebSendConfig>, accept: bool) -> Tes
|
||||
let port = free_port();
|
||||
let prepare_download_events = Arc::new(AtomicU32::new(0));
|
||||
|
||||
let (event_tx, mut event_rx) = tokio::sync::mpsc::channel::<ServerEventV2>(16);
|
||||
// Web send emits its own event type, independent of the v2 protocol events.
|
||||
let (web_event_tx, mut web_event_rx) = tokio::sync::mpsc::channel::<WebSendEvent>(16);
|
||||
|
||||
tokio::spawn({
|
||||
let prepare_download_events = prepare_download_events.clone();
|
||||
async move {
|
||||
while let Some(event) = event_rx.recv().await {
|
||||
if let ServerEventV2::PrepareDownload { decision_tx, .. } = event {
|
||||
prepare_download_events.fetch_add(1, Ordering::SeqCst);
|
||||
let _ = decision_tx.send(accept);
|
||||
}
|
||||
while let Some(WebSendEvent::PrepareDownload { decision_tx, .. }) =
|
||||
web_event_rx.recv().await
|
||||
{
|
||||
prepare_download_events.fetch_add(1, Ordering::SeqCst);
|
||||
let _ = decision_tx.send(accept);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// v2 stays enabled so the `/info` endpoint (which advertises `download`) can be
|
||||
// exercised. Its event channel is unused by these tests.
|
||||
let (v2_event_tx, _v2_event_rx) = tokio::sync::mpsc::channel::<ServerEventV2>(16);
|
||||
|
||||
let (stop_tx, stop_rx) = oneshot::channel::<()>();
|
||||
|
||||
// Web send is configured independently of the v2 endpoints.
|
||||
let web_send = web_send.map(|mut config| {
|
||||
config.event_tx = web_event_tx;
|
||||
config
|
||||
});
|
||||
|
||||
start_with_port(
|
||||
port,
|
||||
None, // plain HTTP
|
||||
@@ -57,9 +68,9 @@ async fn start_test_server(web_send: Option<WebSendConfig>, accept: bool) -> Tes
|
||||
},
|
||||
Some(ServerConfigV2 {
|
||||
pin: None,
|
||||
event_tx,
|
||||
web_send,
|
||||
event_tx: v2_event_tx,
|
||||
}),
|
||||
web_send,
|
||||
stop_rx,
|
||||
)
|
||||
.await
|
||||
@@ -139,11 +150,16 @@ fn web_send_config(pin: Option<String>) -> (WebSendConfig, PathBuf, Vec<u8>) {
|
||||
),
|
||||
]);
|
||||
|
||||
// The event channel is a placeholder; `start_test_server` replaces it with
|
||||
// the one whose receiver counts `PrepareDownload` events.
|
||||
let (event_tx, _event_rx) = tokio::sync::mpsc::channel::<WebSendEvent>(16);
|
||||
|
||||
(
|
||||
WebSendConfig {
|
||||
files,
|
||||
pin,
|
||||
i18n: WebSendI18n::default(),
|
||||
event_tx,
|
||||
},
|
||||
disk_path,
|
||||
disk_content,
|
||||
|
||||
Reference in New Issue
Block a user