use State instead of extension

This commit is contained in:
Joe Ardent 2025-07-04 15:15:52 -07:00
parent 164b9f2395
commit 4047b29a50
9 changed files with 230 additions and 65 deletions

148
Cargo.lock generated
View file

@ -55,6 +55,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "atomic"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340"
dependencies = [
"bytemuck",
]
[[package]] [[package]]
name = "atomic-waker" name = "atomic-waker"
version = "1.1.2" version = "1.1.2"
@ -184,6 +193,12 @@ version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "bytemuck"
version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.10.1" version = "1.10.1"
@ -440,6 +455,22 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "figment"
version = "0.10.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3"
dependencies = [
"atomic",
"parking_lot",
"pear",
"serde",
"tempfile",
"toml",
"uncased",
"version_check",
]
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -880,6 +911,12 @@ version = "2.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
[[package]]
name = "inlinable_string"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
[[package]] [[package]]
name = "instability" name = "instability"
version = "0.3.7" version = "0.3.7"
@ -950,6 +987,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"axum", "axum",
"chrono", "chrono",
"figment",
"julid-rs", "julid-rs",
"mime", "mime",
"mime_guess", "mime_guess",
@ -1386,6 +1424,29 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pear"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467"
dependencies = [
"inlinable_string",
"pear_codegen",
"yansi",
]
[[package]]
name = "pear_codegen"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147"
dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.1" version = "2.3.1"
@ -1437,6 +1498,19 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "proc-macro2-diagnostics"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"version_check",
"yansi",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.40" version = "1.0.40"
@ -1740,6 +1814,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_spanned"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@ -2039,6 +2122,47 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "toml"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"toml_write",
"winnow",
]
[[package]]
name = "toml_write"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]] [[package]]
name = "tower" name = "tower"
version = "0.5.2" version = "0.5.2"
@ -2118,6 +2242,15 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "uncased"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "unicase" name = "unicase"
version = "2.8.1" version = "2.8.1"
@ -2578,6 +2711,15 @@ version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "winnow"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "winsafe" name = "winsafe"
version = "0.0.19" version = "0.0.19"
@ -2599,6 +2741,12 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.8.0" version = "0.8.0"

View file

@ -6,6 +6,7 @@ edition = "2024"
[dependencies] [dependencies]
axum = { version = "0.8", features = ["macros"] } axum = { version = "0.8", features = ["macros"] }
chrono = "0.4" chrono = "0.4"
figment = { version = "0.10", features = ["toml", "test", "env"] }
julid-rs = { version = "1", default-features = false, features = ["serde"] } julid-rs = { version = "1", default-features = false, features = ["serde"] }
mime = "0.3" mime = "0.3"
mime_guess = "2" mime_guess = "2"

View file

@ -1,17 +1,16 @@
use std::{collections::HashMap, net::SocketAddr, sync::Arc}; use std::{net::SocketAddr, sync::Arc};
use tokio::sync::Mutex;
use axum::{ use axum::{
Json,
extract::{ConnectInfo, State}, extract::{ConnectInfo, State},
Extension, Json,
}; };
use crate::{models::device::DeviceInfo, Client}; use crate::{Client, JoecalState, models::device::DeviceInfo};
impl Client { impl Client {
pub async fn announce_http(&self, ip: Option<SocketAddr>) -> crate::error::Result<()> { pub async fn announce_http(&self, ip: Option<SocketAddr>) -> crate::error::Result<()> {
if let Some(ip) = ip { if let Some(ip) = ip {
let url = format!("http://{}/api/localsend/v2/register", ip); let url = format!("http://{ip}/api/localsend/v2/register");
let client = reqwest::Client::new(); let client = reqwest::Client::new();
client.post(&url).json(&self.device).send().await?; client.post(&url).json(&self.device).send().await?;
} }
@ -19,16 +18,17 @@ impl Client {
} }
pub async fn announce_http_legacy(&self) -> crate::error::Result<()> { pub async fn announce_http_legacy(&self) -> crate::error::Result<()> {
// send the reqwest to all local ip addresses from 192.168.0.0 to 192.168.255.255 // send the reqwest to all local ip addresses from 192.168.0.0 to
// 192.168.255.255
let mut address_list = Vec::new(); let mut address_list = Vec::new();
for j in 0..256 { for j in 0..256 {
for k in 0..256 { for k in 0..256 {
address_list.push(format!("192.168.{:03}.{}:53317", j, k)); address_list.push(format!("192.168.{j:03}.{k}:53317"));
} }
} }
for ip in address_list { for ip in address_list {
let url = format!("http://{}/api/localsend/v2/register", ip); let url = format!("http://{ip}/api/localsend/v2/register");
self.http_client self.http_client
.post(&url) .post(&url)
.json(&self.device) .json(&self.device)
@ -40,16 +40,16 @@ impl Client {
} }
pub async fn register_device( pub async fn register_device(
State(peers): State<Arc<Mutex<HashMap<String, (SocketAddr, DeviceInfo)>>>>, State(state): State<Arc<JoecalState>>,
Extension(client): Extension<DeviceInfo>,
ConnectInfo(addr): ConnectInfo<SocketAddr>, ConnectInfo(addr): ConnectInfo<SocketAddr>,
Json(device): Json<DeviceInfo>, Json(device): Json<DeviceInfo>,
) -> Json<DeviceInfo> { ) -> Json<DeviceInfo> {
let mut addr = addr; let mut addr = addr;
addr.set_port(device.port); addr.set_port(state.device.port);
peers state
.peers
.lock() .lock()
.await .await
.insert(device.fingerprint.clone(), (addr, device.clone())); .insert(device.fingerprint.clone(), (addr, device.clone()));
Json(client) Json(device)
} }

View file

@ -1,6 +1,6 @@
use std::net::SocketAddr; use std::net::SocketAddr;
use crate::{models::device::DeviceInfo, Client}; use crate::{Client, models::device::DeviceInfo};
pub mod http; pub mod http;
pub mod multicast; pub mod multicast;
@ -28,7 +28,8 @@ impl Client {
return; return;
} }
// Announce in return upon receiving a valid device message and it wants announcements // Announce in return upon receiving a valid device message and it wants
// announcements
if let Err(e) = self.announce_multicast().await { if let Err(e) = self.announce_multicast().await {
eprintln!("Error during multicast announcement: {}", e); eprintln!("Error during multicast announcement: {}", e);
} }

View file

@ -4,15 +4,17 @@ pub mod models;
pub mod server; pub mod server;
pub mod transfer; pub mod transfer;
use crate::models::device::DeviceInfo; use std::{
use std::collections::HashMap; collections::HashMap,
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; net::{Ipv4Addr, SocketAddr, SocketAddrV4},
use std::sync::Arc; sync::Arc,
use tokio::net::UdpSocket; };
use tokio::sync::Mutex;
use tokio::task::JoinHandle; use tokio::{net::UdpSocket, sync::Mutex, task::JoinHandle};
use transfer::session::Session; use transfer::session::Session;
use crate::models::device::DeviceInfo;
#[derive(Clone)] #[derive(Clone)]
pub struct Client { pub struct Client {
pub device: DeviceInfo, pub device: DeviceInfo,
@ -25,6 +27,13 @@ pub struct Client {
pub download_dir: String, pub download_dir: String,
} }
#[derive(Clone)]
pub struct JoecalState {
pub device: DeviceInfo,
pub peers: Arc<Mutex<HashMap<String, (SocketAddr, DeviceInfo)>>>,
pub sessions: Arc<Mutex<HashMap<String, Session>>>, // Session ID to Session
}
impl Client { impl Client {
pub async fn default() -> crate::error::Result<Self> { pub async fn default() -> crate::error::Result<Self> {
let device = DeviceInfo::default(); let device = DeviceInfo::default();

View file

@ -1,17 +1,16 @@
use joecalsend::{models::device::DeviceInfo, Client}; use joecalsend::{Client, models::device::DeviceInfo};
use tokio::io;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() -> io::Result<()> {
let device = DeviceInfo::default(); let device = DeviceInfo::default();
dbg!(device); dbg!(&device);
let client = Client::with_config( let client = Client::with_config(device, 53317, "/home/ardent/joecalsend".into())
DeviceInfo::default(),
53317,
"/home/ardent/joecalsend".into(),
)
.await .await
.unwrap(); .unwrap();
let (h1, h2, h3) = client.start().await.unwrap(); let (h1, h2, h3) = client.start().await.unwrap();
tokio::join!(h1, h2, h3); let _ = tokio::join!(h1, h2, h3);
Ok(())
} }

View file

@ -1,8 +1,9 @@
use crate::error::LocalSendError; use std::{path::Path, time::SystemTime};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::Path;
use std::time::SystemTime; use crate::error::LocalSendError;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]

View file

@ -1,16 +1,17 @@
use std::{net::SocketAddr, sync::Arc};
use axum::{ use axum::{
Extension, Json, Router,
extract::DefaultBodyLimit, extract::DefaultBodyLimit,
routing::{get, post}, routing::{get, post},
Extension, Json, Router,
}; };
use std::net::SocketAddr;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tower_http::limit::RequestBodyLimitLayer; use tower_http::limit::RequestBodyLimitLayer;
use crate::{ use crate::{
Client, JoecalState,
discovery::http::register_device, discovery::http::register_device,
transfer::upload::{register_prepare_upload, register_upload}, transfer::upload::{register_prepare_upload, register_upload},
Client,
}; };
impl Client { impl Client {
@ -19,7 +20,7 @@ impl Client {
let addr = SocketAddr::from(([0, 0, 0, 0], self.port)); let addr = SocketAddr::from(([0, 0, 0, 0], self.port));
let listener = TcpListener::bind(&addr).await?; let listener = TcpListener::bind(&addr).await?;
println!("HTTP server listening on {}", addr); println!("HTTP server listening on {addr}");
axum::serve( axum::serve(
listener, listener,
@ -32,6 +33,11 @@ impl Client {
fn create_router(&self) -> Router { fn create_router(&self) -> Router {
let peers = self.peers.clone(); let peers = self.peers.clone();
let device = self.device.clone(); let device = self.device.clone();
let state = Arc::new(JoecalState {
peers,
device: device.clone(),
sessions: self.sessions.clone(),
});
Router::new() Router::new()
.route("/api/localsend/v2/register", post(register_device)) .route("/api/localsend/v2/register", post(register_device))
@ -49,9 +55,7 @@ impl Client {
.route("/api/localsend/v2/upload", post(register_upload)) .route("/api/localsend/v2/upload", post(register_upload))
.layer(DefaultBodyLimit::disable()) .layer(DefaultBodyLimit::disable())
.layer(RequestBodyLimitLayer::new(1024 * 1024 * 1024)) .layer(RequestBodyLimitLayer::new(1024 * 1024 * 1024))
.layer(Extension(self.device.clone()))
.layer(Extension(self.sessions.clone()))
.layer(Extension(self.download_dir.clone())) .layer(Extension(self.download_dir.clone()))
.with_state(peers) .with_state(state)
} }
} }

View file

@ -1,25 +1,24 @@
use std::collections::HashMap; use std::{collections::HashMap, net::SocketAddr, path::PathBuf, sync::Arc};
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use axum::body::Bytes; use axum::{
use axum::extract::{ConnectInfo, Query}; Extension, Json,
use axum::http::StatusCode; body::Bytes,
use axum::Extension; extract::{ConnectInfo, Query, State},
use axum::{response::IntoResponse, Json}; http::StatusCode,
response::IntoResponse,
use crate::error::{LocalSendError, Result};
use crate::transfer::session::{Session, SessionStatus};
use crate::{
models::{device::DeviceInfo, file::FileMetadata},
Client,
}; };
use julid::Julid; use julid::Julid;
use native_dialog::MessageDialogBuilder; use native_dialog::MessageDialogBuilder;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::{
Client, JoecalState,
error::{LocalSendError, Result},
models::{device::DeviceInfo, file::FileMetadata},
transfer::session::{Session, SessionStatus},
};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PrepareUploadResponse { pub struct PrepareUploadResponse {
@ -176,8 +175,7 @@ impl Client {
} }
pub async fn register_prepare_upload( pub async fn register_prepare_upload(
Extension(client): Extension<DeviceInfo>, State(state): State<Arc<JoecalState>>,
Extension(sessions): Extension<Arc<Mutex<HashMap<String, Session>>>>,
ConnectInfo(addr): ConnectInfo<SocketAddr>, ConnectInfo(addr): ConnectInfo<SocketAddr>,
Json(req): Json<PrepareUploadRequest>, Json(req): Json<PrepareUploadRequest>,
) -> impl IntoResponse { ) -> impl IntoResponse {
@ -203,13 +201,17 @@ pub async fn register_prepare_upload(
session_id: session_id.clone(), session_id: session_id.clone(),
files: req.files.clone(), files: req.files.clone(),
file_tokens: file_tokens.clone(), file_tokens: file_tokens.clone(),
receiver: client.clone(), receiver: state.device.clone(),
sender: req.info.clone(), sender: req.info.clone(),
status: SessionStatus::Active, status: SessionStatus::Active,
addr, addr,
}; };
sessions.lock().await.insert(session_id.clone(), session); state
.sessions
.lock()
.await
.insert(session_id.clone(), session);
return ( return (
StatusCode::OK, StatusCode::OK,
@ -226,7 +228,7 @@ pub async fn register_prepare_upload(
pub async fn register_upload( pub async fn register_upload(
Query(params): Query<UploadParams>, Query(params): Query<UploadParams>,
Extension(sessions): Extension<Arc<Mutex<HashMap<String, Session>>>>, State(state): State<Arc<JoecalState>>,
Extension(download_dir): Extension<String>, Extension(download_dir): Extension<String>,
body: Bytes, body: Bytes,
) -> impl IntoResponse { ) -> impl IntoResponse {
@ -236,7 +238,7 @@ pub async fn register_upload(
let token = &params.token; let token = &params.token;
// Get session and validate // Get session and validate
let mut sessions_lock = sessions.lock().await; let mut sessions_lock = state.sessions.lock().await;
let session = match sessions_lock.get_mut(session_id) { let session = match sessions_lock.get_mut(session_id) {
Some(session) => session, Some(session) => session,
None => return StatusCode::BAD_REQUEST.into_response(), None => return StatusCode::BAD_REQUEST.into_response(),
@ -259,7 +261,7 @@ pub async fn register_upload(
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
"File not found".to_string(), "File not found".to_string(),
) )
.into_response() .into_response();
} }
}; };