Compare commits
14 Commits
51118fee63
...
v2
| Author | SHA1 | Date | |
|---|---|---|---|
| 38ed2842ff | |||
| 3e20347726 | |||
| dfb6efef60 | |||
| a6626571fa | |||
| 04c2750f0b | |||
| b8c797a2fc | |||
| 8bddcc1b01 | |||
| 39d1d9a2b7 | |||
| d691c1d944 | |||
| 5dbf0aacab | |||
| be761a8c46 | |||
| 62dc6deb79 | |||
| 9b8461314f | |||
| 0e9c2b08d6 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,4 @@
|
||||
/target
|
||||
.env
|
||||
db.sqlite
|
||||
.idea/workspace.xml
|
||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
2153
Cargo.lock
generated
2153
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
25
Cargo.toml
25
Cargo.toml
@@ -14,12 +14,21 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
log = "0.4"
|
||||
env_logger = "0.11"
|
||||
tokio = { version = "1.47", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
arc-swap = "1.7"
|
||||
dotenvy = "0.15"
|
||||
envy = "0.4"
|
||||
socket2 = "0.6"
|
||||
parking_lot = "0.12"
|
||||
tokio = { version = "1.46", features = ["full"] }
|
||||
strum = {version = "0.27", features = ["derive"] }
|
||||
uuid = {version = "1.17", features = ["v4", "serde"] }
|
||||
event-listener = "5.4"
|
||||
dashmap = "6.1"
|
||||
bytes = "1.10"
|
||||
axum = "0.8"
|
||||
tower = "0.5"
|
||||
tower-http = "0.6"
|
||||
hyper = "1.7"
|
||||
|
||||
sqlx = { version = "0.8", features = ["runtime-tokio", "tls-rustls", "sqlite", "postgres", "mysql", "uuid", "chrono", "migrate"] }
|
||||
uuid = { version = "1.18", features = ["v4", "v7", "serde"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
15
README.md
Normal file
15
README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
L'idée est d'avoir une séparation avec une gestion à la "Qt". En gros, avoir des composants "indépendants" qui connaisse leur propre état
|
||||
et mettre à dispo une succession de méthodes pour interagir avec ces composants dans "App"
|
||||
|
||||
## Modules :
|
||||
- App : Execution générale et manipulation des autres composant en "haut niveau", interconnexion entre les app
|
||||
- Network :
|
||||
- Http : API
|
||||
- WebSocket : Un server websocket avec ses clients (pour la gestion haut niveau)
|
||||
- UDP : Un server UDP avec ses clients (pour la gestion haut niveau)
|
||||
|
||||
créer un fichier de migration sqlx :
|
||||
```shell
|
||||
sqlx migrate add --source src/store/migrations migration_name
|
||||
```
|
||||
|
||||
105
src/app/app.rs
105
src/app/app.rs
@@ -1,66 +1,61 @@
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::interval;
|
||||
use crate::domain::client::ClientManager;
|
||||
use crate::domain::event::{Event, EventBus};
|
||||
use crate::network::udp::UdpServer;
|
||||
use crate::runtime::dispatcher::Dispatcher;
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
use std::net::SocketAddr;
|
||||
use crate::app::http::router::configure_routes;
|
||||
use crate::network::http::server::HttpServer;
|
||||
use crate::network::udp::server::UdpServer;
|
||||
use crate::utils::config::Config;
|
||||
use crate::utils::logger::ContextLogger;
|
||||
|
||||
pub struct App {
|
||||
// Communication inter-components
|
||||
event_bus: EventBus,
|
||||
dispatcher: Dispatcher,
|
||||
event_rx: Option<mpsc::Receiver<Event>>,
|
||||
|
||||
// Network
|
||||
udp_server: UdpServer,
|
||||
|
||||
// Clients
|
||||
client_manager: ClientManager,
|
||||
config: Config,
|
||||
context: Arc<Context>,
|
||||
|
||||
logger: ContextLogger,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub async fn new() -> Self {
|
||||
let (event_bus, event_rx) = EventBus::new();
|
||||
pub async fn new(config: Config) -> io::Result<Self> {
|
||||
let logger = ContextLogger::new("APP");
|
||||
|
||||
let udp_server = UdpServer::new(event_bus.clone(), "0.0.0.0:5000").await;
|
||||
let client_manager = ClientManager::new();
|
||||
let dispatcher = Dispatcher::new(event_bus.clone(), udp_server.clone(), client_manager.clone()).await;
|
||||
|
||||
|
||||
Self {
|
||||
event_bus,
|
||||
dispatcher,
|
||||
event_rx: Some(event_rx),
|
||||
udp_server,
|
||||
client_manager
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start(&mut self) {
|
||||
if let Some(event_rx) = self.event_rx.take() {
|
||||
let dispatcher = self.dispatcher.clone();
|
||||
tokio::spawn(async move {
|
||||
dispatcher.start(event_rx).await;
|
||||
});
|
||||
}
|
||||
|
||||
let _ = self.udp_server.start().await;
|
||||
let _ = self.tick_tasks().await;
|
||||
println!("App started");
|
||||
}
|
||||
|
||||
async fn tick_tasks(&self) {
|
||||
let event_bus = self.event_bus.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut interval = interval(Duration::from_secs(1));
|
||||
loop {
|
||||
// println!("Tick");
|
||||
interval.tick().await;
|
||||
let _ = event_bus.emit(Event::TickSeconds).await;
|
||||
// initialise context
|
||||
let udp_server = match UdpServer::new("127.0.0.1:8080") {
|
||||
Ok(udp_server) => udp_server,
|
||||
Err(e) => {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, format!("Failed to create UDP server: {}", e)))
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let http_server = match HttpServer::new("127.0.0.1:8080") {
|
||||
Ok(http_server) => http_server,
|
||||
Err(e) => {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, format!("Failed to create HTTP server: {}", e)))
|
||||
}
|
||||
};
|
||||
|
||||
let context = Context {
|
||||
udp_server,
|
||||
http_server,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
config,
|
||||
context: Arc::new(context),
|
||||
logger
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn run(self) -> io::Result<()> {
|
||||
self.logger.info("Starting application");
|
||||
self.context.udp_server.run().await?;
|
||||
|
||||
self.context.http_server.run().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct Context {
|
||||
udp_server: UdpServer,
|
||||
http_server: HttpServer
|
||||
}
|
||||
28
src/app/http/api/core.rs
Normal file
28
src/app/http/api/core.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use axum::http::StatusCode;
|
||||
use axum::{Json, Router};
|
||||
use axum::routing::get;
|
||||
use serde_json::json;
|
||||
|
||||
pub fn routes() -> Router {
|
||||
Router::new()
|
||||
.route("/", get(root))
|
||||
.route("/health", get(health))
|
||||
.route("/status", get(status))
|
||||
}
|
||||
|
||||
async fn root() -> &'static str {
|
||||
"OX Speak Server - HTTP API"
|
||||
}
|
||||
|
||||
async fn health() -> StatusCode {
|
||||
StatusCode::OK
|
||||
}
|
||||
|
||||
async fn status() -> Json<serde_json::Value> {
|
||||
Json(json!({
|
||||
"status": "ok",
|
||||
"service": "ox-speak-server",
|
||||
"version": env!("CARGO_PKG_VERSION"),
|
||||
"workers": tokio::runtime::Handle::current().metrics().num_workers()
|
||||
}))
|
||||
}
|
||||
6
src/app/http/api/message.rs
Normal file
6
src/app/http/api/message.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
use axum::Router;
|
||||
|
||||
pub fn routes() -> Router {
|
||||
Router::new()
|
||||
.nest("/message", Router::new())
|
||||
}
|
||||
3
src/app/http/api/mod.rs
Normal file
3
src/app/http/api/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod core;
|
||||
pub mod message;
|
||||
pub mod server;
|
||||
3
src/app/http/mod.rs
Normal file
3
src/app/http/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod router;
|
||||
|
||||
pub mod api;
|
||||
13
src/app/http/router.rs
Normal file
13
src/app/http/router.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use axum::Router;
|
||||
use crate::app::http::api;
|
||||
use crate::app::http::api::core;
|
||||
|
||||
pub fn configure_routes() -> Router {
|
||||
let api_router = Router::new();
|
||||
|
||||
|
||||
Router::new()
|
||||
.merge(core::routes())
|
||||
.nest("/api", api_router )
|
||||
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod app;
|
||||
pub mod http;
|
||||
57
src/db/context.rs
Normal file
57
src/db/context.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use std::sync::Arc;
|
||||
use sqlx::SqlitePool;
|
||||
use crate::db::{AttachmentRepository, CategoryRepository, ChannelRepository, ChannelUser, ChannelUserRepository, MessageRepository, ServerRepository, ServerUser, ServerUserRepository, UserRepository};
|
||||
use crate::utils::logger::ContextLogger;
|
||||
|
||||
struct Repositories {
|
||||
server_repository: ServerRepository,
|
||||
category_repository: CategoryRepository,
|
||||
channel_repository: ChannelRepository,
|
||||
user_repository: UserRepository,
|
||||
message_repository: MessageRepository,
|
||||
attachment_repository: AttachmentRepository,
|
||||
|
||||
server_user: ServerUserRepository,
|
||||
channel_user: ChannelUserRepository
|
||||
}
|
||||
|
||||
pub struct DbContext {
|
||||
pool: Arc<SqlitePool>,
|
||||
|
||||
repositories: Arc<Repositories>,
|
||||
|
||||
// logger
|
||||
logger: ContextLogger,
|
||||
}
|
||||
|
||||
impl DbContext {
|
||||
pub async fn new(database_url: &str) -> Result<Self, sqlx::Error> {
|
||||
let logger = ContextLogger::new("DB");
|
||||
|
||||
logger.info(&format!("Creating DB context on {}", database_url));
|
||||
let pool = SqlitePool::connect(database_url).await?;
|
||||
logger.info("DB context created");
|
||||
let pool = Arc::new(pool);
|
||||
|
||||
let repositories = Arc::new(Repositories {
|
||||
server_repository: ServerRepository::new(pool.clone()),
|
||||
category_repository: CategoryRepository::new(pool.clone()),
|
||||
channel_repository: ChannelRepository::new(pool.clone()),
|
||||
user_repository: UserRepository::new(pool.clone()),
|
||||
message_repository: MessageRepository::new(pool.clone()),
|
||||
attachment_repository: AttachmentRepository::new(pool.clone()),
|
||||
server_user: ServerUserRepository::new(pool.clone()),
|
||||
channel_user: ChannelUserRepository::new(pool.clone())
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
pool,
|
||||
repositories,
|
||||
logger,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn repositories(&self) -> Arc<Repositories> {
|
||||
self.repositories.clone()
|
||||
}
|
||||
}
|
||||
6
src/db/mod.rs
Normal file
6
src/db/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod models;
|
||||
pub mod repositories;
|
||||
mod context;
|
||||
|
||||
pub use models::*;
|
||||
pub use repositories::*;
|
||||
29
src/db/models/_channel_user.rs
Normal file
29
src/db/models/_channel_user.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||
pub struct ChannelUser {
|
||||
pub id: Uuid,
|
||||
pub channel_id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
pub username: Option<String>,
|
||||
pub joined_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub last_read_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl ChannelUser {
|
||||
pub fn new(channel_id: Uuid, user_id: Uuid) -> Self {
|
||||
Self {
|
||||
id: Uuid::now_v7(),
|
||||
channel_id,
|
||||
user_id,
|
||||
username: None,
|
||||
joined_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
last_read_at: Utc::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/db/models/_server_user.rs
Normal file
28
src/db/models/_server_user.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||
pub struct ServerUser {
|
||||
pub id: Uuid,
|
||||
pub server_id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
#[sqlx(default)]
|
||||
pub username: Option<String>,
|
||||
pub joined_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl ServerUser {
|
||||
pub fn new(server_id: Uuid, user_id: Uuid) -> Self {
|
||||
Self {
|
||||
id: Uuid::now_v7(),
|
||||
server_id,
|
||||
user_id,
|
||||
username: None,
|
||||
joined_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/db/models/attachment.rs
Normal file
27
src/db/models/attachment.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||
pub struct Attachment {
|
||||
pub id: Uuid,
|
||||
pub message_id: Uuid,
|
||||
pub filename: String,
|
||||
pub file_size: i64,
|
||||
pub mime_type: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Attachment {
|
||||
pub fn new(message_id: Uuid, filename: String, file_size: i64, mime_type: String) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
message_id,
|
||||
filename,
|
||||
file_size,
|
||||
mime_type,
|
||||
created_at: Utc::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/db/models/category.rs
Normal file
25
src/db/models/category.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||
pub struct Category {
|
||||
pub id: Uuid,
|
||||
pub server_id: Uuid,
|
||||
pub name: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Category {
|
||||
pub fn new(server_id: Uuid, name: String) -> Self {
|
||||
Self {
|
||||
id: Uuid::now_v7(),
|
||||
server_id,
|
||||
name,
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src/db/models/channel.rs
Normal file
56
src/db/models/channel.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, sqlx::FromRow, Serialize, Deserialize)]
|
||||
pub struct Channel {
|
||||
pub id: Uuid, // Blob(16) sqlite
|
||||
#[sqlx(default)]
|
||||
pub server_id: Option<Uuid>,
|
||||
#[sqlx(default)]
|
||||
pub category_id: Option<Uuid>,
|
||||
#[sqlx(default)]
|
||||
pub position: i32,
|
||||
#[sqlx(rename = "type")]
|
||||
pub channel_type: ChannelType,
|
||||
pub name: Option<String>, // Not necessary for DMs
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type)]
|
||||
#[sqlx(type_name = "text")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ChannelType {
|
||||
Text,
|
||||
Voice,
|
||||
Dm
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
pub fn new_server_channel(server_id: Uuid, name: String, channel_type: ChannelType) -> Self {
|
||||
Self {
|
||||
id: Uuid::now_v7(),
|
||||
server_id: Some(server_id),
|
||||
category_id: None,
|
||||
position: 0,
|
||||
channel_type,
|
||||
name: Some(name),
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_dm_channel() -> Self {
|
||||
Self {
|
||||
id: Uuid::now_v7(),
|
||||
server_id: None,
|
||||
category_id: None,
|
||||
channel_type: ChannelType::Dm,
|
||||
name: None,
|
||||
position: 0,
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/db/models/message.rs
Normal file
41
src/db/models/message.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||
pub struct Message {
|
||||
pub id: Uuid,
|
||||
pub channel_id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
pub content: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub edited_at: DateTime<Utc>,
|
||||
pub reply_to_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
pub fn new(channel_id: Uuid, user_id: Uuid, content: String) -> Self {
|
||||
Self {
|
||||
id: Uuid::now_v7(),
|
||||
channel_id,
|
||||
user_id,
|
||||
content,
|
||||
created_at: Utc::now(),
|
||||
edited_at: Utc::now(),
|
||||
reply_to_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_reply(channel_id: Uuid, user_id: Uuid, content: String, reply_to_id: Uuid) -> Self {
|
||||
Self {
|
||||
id: Uuid::now_v7(),
|
||||
channel_id,
|
||||
user_id,
|
||||
content,
|
||||
created_at: Utc::now(),
|
||||
edited_at: Utc::now(),
|
||||
reply_to_id: Some(reply_to_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/db/models/mod.rs
Normal file
17
src/db/models/mod.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
mod user;
|
||||
mod server;
|
||||
mod _server_user;
|
||||
mod channel;
|
||||
mod category;
|
||||
mod _channel_user;
|
||||
mod message;
|
||||
mod attachment;
|
||||
|
||||
pub use user::*;
|
||||
pub use server::*;
|
||||
pub use channel::*;
|
||||
pub use category::*;
|
||||
pub use message::*;
|
||||
pub use attachment::*;
|
||||
pub use _server_user::*;
|
||||
pub use _channel_user::*;
|
||||
25
src/db/models/server.rs
Normal file
25
src/db/models/server.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, FromRow, Serialize, Deserialize)]
|
||||
pub struct Server {
|
||||
pub id: Uuid, // Blob(16) sqlite
|
||||
pub username: String,
|
||||
pub password: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn new(username: String, password: Option<String>) -> Self {
|
||||
Self {
|
||||
id: Uuid::now_v7(),
|
||||
username,
|
||||
password,
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/db/models/user.rs
Normal file
25
src/db/models/user.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, FromRow, Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
pub id: Uuid, // Blob(16) sqlite
|
||||
pub username: String,
|
||||
pub pub_key: String, // TEXT
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn new(username: String, pub_key: String) -> Self {
|
||||
Self {
|
||||
id: Uuid::now_v7(),
|
||||
username,
|
||||
pub_key,
|
||||
created_at: Utc::now(),
|
||||
updated_at: Utc::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/db/repositories/_channel_user_repository.rs
Normal file
14
src/db/repositories/_channel_user_repository.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use std::sync::Arc;
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
pub struct ChannelUserRepository {
|
||||
pool: Arc<SqlitePool>
|
||||
}
|
||||
|
||||
impl ChannelUserRepository {
|
||||
pub fn new(pool: Arc<SqlitePool>) -> Self {
|
||||
Self {
|
||||
pool
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/db/repositories/_server_user_repository.rs
Normal file
14
src/db/repositories/_server_user_repository.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use std::sync::Arc;
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
pub struct ServerUserRepository {
|
||||
pool: Arc<SqlitePool>
|
||||
}
|
||||
|
||||
impl ServerUserRepository {
|
||||
pub fn new(pool: Arc<SqlitePool>) -> Self {
|
||||
Self {
|
||||
pool
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/db/repositories/attachment_repository.rs
Normal file
14
src/db/repositories/attachment_repository.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use std::sync::Arc;
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
pub struct AttachmentRepository {
|
||||
pool: Arc<SqlitePool>
|
||||
}
|
||||
|
||||
impl AttachmentRepository {
|
||||
pub fn new(pool: Arc<SqlitePool>) -> Self {
|
||||
Self {
|
||||
pool
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/db/repositories/category_repository.rs
Normal file
14
src/db/repositories/category_repository.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use std::sync::Arc;
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
pub struct CategoryRepository{
|
||||
pool: Arc<SqlitePool>
|
||||
}
|
||||
|
||||
impl CategoryRepository {
|
||||
pub fn new(pool: Arc<SqlitePool>) -> Self {
|
||||
Self {
|
||||
pool
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/db/repositories/channel_repository.rs
Normal file
15
src/db/repositories/channel_repository.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use std::sync::Arc;
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
pub struct ChannelRepository {
|
||||
pool: Arc<SqlitePool>
|
||||
}
|
||||
|
||||
|
||||
impl ChannelRepository {
|
||||
pub fn new(pool: Arc<SqlitePool>) -> Self {
|
||||
Self {
|
||||
pool
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/db/repositories/message_repository.rs
Normal file
15
src/db/repositories/message_repository.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use std::sync::Arc;
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
pub struct MessageRepository{
|
||||
pool: Arc<SqlitePool>
|
||||
}
|
||||
|
||||
|
||||
impl MessageRepository {
|
||||
pub fn new(pool: Arc<SqlitePool>) -> Self {
|
||||
Self {
|
||||
pool
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/db/repositories/mod.rs
Normal file
17
src/db/repositories/mod.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
mod _channel_user_repository;
|
||||
mod _server_user_repository;
|
||||
mod user_repository;
|
||||
mod server_repository;
|
||||
mod channel_repository;
|
||||
mod category_repository;
|
||||
mod message_repository;
|
||||
mod attachment_repository;
|
||||
|
||||
pub use user_repository::*;
|
||||
pub use server_repository::*;
|
||||
pub use channel_repository::*;
|
||||
pub use category_repository::*;
|
||||
pub use message_repository::*;
|
||||
pub use attachment_repository::*;
|
||||
pub use _server_user_repository::*;
|
||||
pub use _channel_user_repository::*;
|
||||
15
src/db/repositories/server_repository.rs
Normal file
15
src/db/repositories/server_repository.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use std::sync::Arc;
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
pub struct ServerRepository{
|
||||
pool: Arc<SqlitePool>
|
||||
}
|
||||
|
||||
|
||||
impl ServerRepository {
|
||||
pub fn new(pool: Arc<SqlitePool>) -> Self {
|
||||
Self{
|
||||
pool
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/db/repositories/user_repository.rs
Normal file
14
src/db/repositories/user_repository.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use std::sync::Arc;
|
||||
use sqlx::{SqlitePool};
|
||||
|
||||
pub struct UserRepository {
|
||||
pool: Arc<SqlitePool>
|
||||
}
|
||||
|
||||
impl UserRepository {
|
||||
pub fn new(pool: Arc<SqlitePool>) -> Self {
|
||||
Self {
|
||||
pool
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
//! Gestion des clients pour les connexions UDP
|
||||
//!
|
||||
//! Ce module fournit les structures et méthodes pour gérer les clients
|
||||
//! connectés au serveur UDP, incluant leur tracking et leurs modifications.
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use dashmap::DashMap;
|
||||
use tokio::time::Instant;
|
||||
use uuid::Uuid;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::time::Duration;
|
||||
|
||||
/// Représente un client connecté au serveur UDP
|
||||
///
|
||||
/// Chaque client est identifié par un UUID unique et contient
|
||||
/// son adresse réseau ainsi que l'heure de sa dernière activité.
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
id: Uuid,
|
||||
address: SocketAddr,
|
||||
last_seen: Instant,
|
||||
}
|
||||
|
||||
/// Gestionnaire threadsafe pour les clients connectés
|
||||
///
|
||||
/// Utilise `DashMap` pour permettre un accès concurrent sécurisé
|
||||
/// aux clients depuis plusieurs threads.
|
||||
#[derive(Clone)]
|
||||
pub struct ClientManager {
|
||||
clients: Arc<DashMap<SocketAddr, Client>>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Crée un nouveau client avec un UUID généré automatiquement
|
||||
pub fn new(address: SocketAddr) -> Self {
|
||||
let id = Uuid::new_v4();
|
||||
Self {
|
||||
id,
|
||||
address,
|
||||
last_seen: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Retourne le UUID unique du client
|
||||
pub fn id(&self) -> Uuid {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Retourne l'adresse socket du client
|
||||
pub fn address(&self) -> SocketAddr {
|
||||
self.address
|
||||
}
|
||||
|
||||
/// Retourne l'instant de la dernière activité du client
|
||||
pub fn last_seen(&self) -> Instant {
|
||||
self.last_seen
|
||||
}
|
||||
|
||||
/// Met à jour l'heure de dernière activité du client à maintenant
|
||||
pub fn update_last_seen(&mut self) {
|
||||
self.last_seen = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Client {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Client {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Client {}
|
||||
|
||||
impl ClientManager {
|
||||
/// Crée un nouveau gestionnaire de clients vide
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
clients: Arc::new(DashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Ajoute un client au gestionnaire
|
||||
pub fn add(&self, client: Client) {
|
||||
self.clients.insert(client.address(), client);
|
||||
}
|
||||
|
||||
/// Supprime un client du gestionnaire
|
||||
pub fn remove(&self, client: Client) {
|
||||
self.clients.remove(&client.address());
|
||||
}
|
||||
|
||||
/// Vérifie si un client existe pour une adresse donnée
|
||||
pub fn client_exists(&self, address: SocketAddr) -> bool {
|
||||
self.clients.contains_key(&address)
|
||||
}
|
||||
|
||||
/// Récupère une référence vers un client par son adresse
|
||||
pub fn get_client_by_address(&self, address: SocketAddr) -> Option<dashmap::mapref::one::Ref<SocketAddr, Client>> {
|
||||
self.clients.get(&address)
|
||||
}
|
||||
|
||||
/// Récupère toutes les adresses des clients connectés
|
||||
pub fn get_all_adresses(&self) -> Vec<SocketAddr> {
|
||||
self.clients.iter().map(|entry| *entry.key()).collect()
|
||||
}
|
||||
|
||||
/// Met à jour l'heure de dernière activité d'un client
|
||||
pub fn update_client_last_seen(&self, address: SocketAddr) {
|
||||
if let Some(mut client) = self.clients.get_mut(&address) {
|
||||
client.update_last_seen();
|
||||
}
|
||||
}
|
||||
|
||||
/// Supprimer les clients trop vieux
|
||||
pub fn cleanup(&self, max_age: Duration) {
|
||||
let now = Instant::now();
|
||||
self.clients.retain(|_, client| now - client.last_seen() < max_age);
|
||||
}
|
||||
|
||||
/// Modifie un client via une closure
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `address` - L'adresse du client à modifier
|
||||
/// * `f` - La closure qui recevra une référence mutable vers le client
|
||||
///
|
||||
/// # Returns
|
||||
/// `true` si le client a été trouvé et modifié, `false` sinon
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// let client_manager = ClientManager::new();
|
||||
/// let addr = "127.0.0.1:8080".parse().unwrap();
|
||||
///
|
||||
/// // Mise à jour simple
|
||||
/// client_manager.modify_client(addr, |client| {
|
||||
/// client.update_last_seen();
|
||||
/// });
|
||||
///
|
||||
/// // Modifications multiples
|
||||
/// let success = client_manager.modify_client(addr, |client| {
|
||||
/// client.update_last_seen();
|
||||
/// // autres modifications...
|
||||
/// });
|
||||
/// ```
|
||||
pub fn modify_client<F>(&self, address: SocketAddr, f: F) -> bool
|
||||
where
|
||||
F: FnOnce(&mut Client),
|
||||
{
|
||||
if let Some(mut client) = self.clients.get_mut(&address) {
|
||||
f(&mut *client);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
use tokio::sync::mpsc;
|
||||
use crate::network::protocol::{UDPMessage};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Event {
|
||||
AppStarted,
|
||||
AppStopped,
|
||||
|
||||
UdpStarted,
|
||||
UdpStopped,
|
||||
UdpIn(UDPMessage),
|
||||
UdpOut(UDPMessage),
|
||||
|
||||
TickSeconds
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EventBus {
|
||||
pub sender: mpsc::Sender<Event>,
|
||||
}
|
||||
|
||||
impl EventBus {
|
||||
pub fn new() -> (Self, mpsc::Receiver<Event>) {
|
||||
let (sender, receiver) = mpsc::channel(10000);
|
||||
(Self { sender }, receiver)
|
||||
}
|
||||
|
||||
pub async fn emit(&self, event: Event) {
|
||||
let _ = self.sender.send(event).await;
|
||||
}
|
||||
|
||||
pub fn emit_sync(&self, event: Event) {
|
||||
let _ = self.sender.try_send(event);
|
||||
}
|
||||
|
||||
pub fn clone_sender(&self) -> mpsc::Sender<Event> {
|
||||
self.sender.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod event;
|
||||
pub mod user;
|
||||
pub mod client;
|
||||
@@ -1,47 +0,0 @@
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use dashmap::DashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct User {
|
||||
id: Uuid,
|
||||
udp_addr: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UserManager {
|
||||
users: Arc<DashMap<Uuid, User>>,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn new(id: Uuid) -> Self {
|
||||
Self {
|
||||
id,
|
||||
udp_addr: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default() -> Self {
|
||||
Self::new(Uuid::new_v4())
|
||||
}
|
||||
|
||||
pub fn set_udp_addr(&mut self, udp_addr: SocketAddr) {
|
||||
self.udp_addr = Some(udp_addr);
|
||||
}
|
||||
}
|
||||
|
||||
impl UserManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
users: Arc::new(DashMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_user(&self, user: User) {
|
||||
self.users.insert(user.id, user);
|
||||
}
|
||||
|
||||
pub fn delete_user(&self, user: User) {
|
||||
self.users.remove(&user.id);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
pub mod app;
|
||||
pub mod core;
|
||||
pub mod domain;
|
||||
pub mod network;
|
||||
pub mod runtime;
|
||||
|
||||
pub mod utils;
|
||||
pub mod network;
|
||||
pub mod app;
|
||||
pub mod db;
|
||||
24
src/main.rs
24
src/main.rs
@@ -1,10 +1,29 @@
|
||||
use std::sync::Arc;
|
||||
use parking_lot::RwLock;
|
||||
use tokio::signal;
|
||||
use ox_speak_server_lib::utils::config::Config;
|
||||
use ox_speak_server_lib::app::app::App;
|
||||
use ox_speak_server_lib::utils::logger::init_logger;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let mut app = App::new().await;
|
||||
app.start().await;
|
||||
|
||||
init_logger("debug");
|
||||
|
||||
// Charger le .env
|
||||
let config = match Config::load() {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
eprintln!("Failed to load configuration: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Initialiser le logger
|
||||
// init_logger(&config.log_level);
|
||||
|
||||
let app = App::new(config).await;
|
||||
// app.start().await;
|
||||
|
||||
// Attendre le signal Ctrl+C
|
||||
match signal::ctrl_c().await {
|
||||
@@ -15,5 +34,4 @@ async fn main() {
|
||||
eprintln!("Erreur lors de l'écoute du signal: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
1
src/network/http/mod.rs
Normal file
1
src/network/http/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod server;
|
||||
32
src/network/http/server.rs
Normal file
32
src/network/http/server.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use axum::{Router};
|
||||
use tokio::net::TcpListener;
|
||||
use crate::utils::logger::ContextLogger;
|
||||
|
||||
pub struct HttpServer {
|
||||
bind_addr: SocketAddr,
|
||||
logger: ContextLogger,
|
||||
}
|
||||
|
||||
impl HttpServer {
|
||||
pub fn new(bind_addr: &str) -> io::Result<Self> {
|
||||
let bind_addr = bind_addr.parse::<SocketAddr>()
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
|
||||
|
||||
let logger = ContextLogger::new("HTTP");
|
||||
logger.info(&format!("Creating HTTP server on {}", bind_addr));
|
||||
|
||||
Ok(Self { bind_addr, logger })
|
||||
}
|
||||
|
||||
pub async fn run(self, router: Router) -> io::Result<()> {
|
||||
self.logger.info(&format!("Starting HTTP server on {}", self.bind_addr));
|
||||
|
||||
let listener = TcpListener::bind(self.bind_addr).await?;
|
||||
|
||||
axum::serve(listener, router)
|
||||
.await
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
pub mod protocol;
|
||||
pub mod udp;
|
||||
pub mod http;
|
||||
@@ -1,246 +0,0 @@
|
||||
use std::collections::HashSet;
|
||||
use bytes::{Bytes, BytesMut, Buf, BufMut};
|
||||
use std::net::SocketAddr;
|
||||
use uuid::Uuid;
|
||||
use strum::{EnumIter, FromRepr};
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, EnumIter, FromRepr)]
|
||||
pub enum UDPMessageType {
|
||||
Ping = 0,
|
||||
Audio = 1,
|
||||
// Futurs types ici...
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum UDPMessageData {
|
||||
// Client messages - Zero-copy avec Bytes
|
||||
ClientPing { message_id: Uuid },
|
||||
ClientAudio { sequence: u16, data: Bytes },
|
||||
|
||||
// Server messages
|
||||
ServerPing { message_id: Uuid },
|
||||
ServerAudio { user: Uuid, sequence: u16, data: Bytes },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct UDPMessage {
|
||||
pub data: UDPMessageData,
|
||||
pub address: SocketAddr,
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct UdpBroadcastMessage {
|
||||
pub data: UDPMessageData,
|
||||
pub addresses: HashSet<SocketAddr>, // ou Vec<SocketAddr> selon besoins
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ParseError {
|
||||
EmptyData,
|
||||
InvalidData,
|
||||
InvalidMessageType,
|
||||
InvalidUuid,
|
||||
}
|
||||
|
||||
impl From<uuid::Error> for ParseError {
|
||||
fn from(_: uuid::Error) -> Self {
|
||||
ParseError::InvalidUuid
|
||||
}
|
||||
}
|
||||
|
||||
impl UDPMessageData {
|
||||
// Parsing zero-copy depuis Bytes
|
||||
pub fn from_client_bytes(mut data: Bytes) -> Result<Self, ParseError> {
|
||||
if data.is_empty() {
|
||||
return Err(ParseError::EmptyData);
|
||||
}
|
||||
|
||||
let msg_type = data.get_u8(); // Consomme 1 byte
|
||||
|
||||
match msg_type {
|
||||
0 => { // Ping
|
||||
if data.remaining() < 16 {
|
||||
return Err(ParseError::InvalidData);
|
||||
}
|
||||
let uuid_bytes = data.split_to(16); // Zero-copy split
|
||||
let message_id = Uuid::from_slice(&uuid_bytes)?;
|
||||
Ok(Self::ClientPing { message_id })
|
||||
}
|
||||
1 => { // Audio
|
||||
if data.remaining() < 2 {
|
||||
return Err(ParseError::InvalidData);
|
||||
}
|
||||
let sequence = data.get_u16(); // Big-endian par défaut
|
||||
let audio_data = data; // Le reste pour l'audio
|
||||
Ok(Self::ClientAudio { sequence, data: audio_data })
|
||||
}
|
||||
_ => Err(ParseError::InvalidMessageType),
|
||||
}
|
||||
}
|
||||
|
||||
// Constructeurs server
|
||||
pub fn server_ping(message_id: Uuid) -> Self {
|
||||
Self::ServerPing { message_id }
|
||||
}
|
||||
|
||||
pub fn server_audio(user: Uuid, sequence: u16, data: Bytes) -> Self {
|
||||
Self::ServerAudio { user, sequence, data }
|
||||
}
|
||||
|
||||
// Sérialisation optimisée avec BytesMut
|
||||
pub fn to_bytes(&self) -> Bytes {
|
||||
match self {
|
||||
Self::ServerPing { message_id } => {
|
||||
let mut buf = BytesMut::with_capacity(17);
|
||||
buf.put_u8(0); // Message type
|
||||
buf.put_slice(message_id.as_bytes());
|
||||
buf.freeze()
|
||||
}
|
||||
Self::ServerAudio { user, sequence, data } => {
|
||||
let mut buf = BytesMut::with_capacity(19 + data.len());
|
||||
buf.put_u8(1); // Message type
|
||||
buf.put_slice(user.as_bytes());
|
||||
buf.put_u16(*sequence);
|
||||
buf.put_slice(data);
|
||||
buf.freeze()
|
||||
}
|
||||
_ => panic!("Client messages cannot be serialized"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
// pas très optimisé
|
||||
self.to_bytes().to_vec()
|
||||
}
|
||||
|
||||
pub fn message_type(&self) -> UDPMessageType {
|
||||
match self {
|
||||
Self::ClientPing { .. } | Self::ServerPing { .. } => UDPMessageType::Ping,
|
||||
Self::ClientAudio { .. } | Self::ServerAudio { .. } => UDPMessageType::Audio,
|
||||
}
|
||||
}
|
||||
|
||||
// Calcule la taille du message sérialisé
|
||||
pub fn size(&self) -> usize {
|
||||
match self {
|
||||
Self::ClientPing { .. } | Self::ServerPing { .. } => 17, // 1 + 16 (UUID)
|
||||
Self::ClientAudio { data, .. } => 3 + data.len(), // 1 + 2 + audio_data
|
||||
Self::ServerAudio { data, .. } => 19 + data.len(), // 1 + 16 + 2 + audio_data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UDPMessage {
|
||||
// Parsing depuis slice -> Bytes (zero-copy si possible)
|
||||
pub fn from_client_bytes(address: SocketAddr, data: &[u8]) -> Result<Self, ParseError> {
|
||||
let original_size = data.len();
|
||||
let bytes = Bytes::copy_from_slice(data); // Seule allocation
|
||||
let data = UDPMessageData::from_client_bytes(bytes)?;
|
||||
Ok(Self {
|
||||
data,
|
||||
address,
|
||||
size: original_size
|
||||
})
|
||||
}
|
||||
|
||||
// Constructeurs server
|
||||
pub fn server_ping(address: SocketAddr, message_id: Uuid) -> Self {
|
||||
let data = UDPMessageData::server_ping(message_id);
|
||||
let size = data.size();
|
||||
Self { data, address, size }
|
||||
}
|
||||
|
||||
pub fn server_audio(address: SocketAddr, user: Uuid, sequence: u16, data: Bytes) -> Self {
|
||||
let msg_data = UDPMessageData::server_audio(user, sequence, data);
|
||||
let size = msg_data.size();
|
||||
Self { data: msg_data, address, size }
|
||||
}
|
||||
|
||||
// Helpers
|
||||
pub fn to_bytes(&self) -> Bytes {
|
||||
self.data.to_bytes()
|
||||
}
|
||||
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
self.data.to_vec()
|
||||
}
|
||||
|
||||
pub fn message_type(&self) -> UDPMessageType {
|
||||
self.data.message_type()
|
||||
}
|
||||
|
||||
// Getters
|
||||
pub fn size(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
|
||||
pub fn address(&self) -> SocketAddr {
|
||||
self.address
|
||||
}
|
||||
}
|
||||
|
||||
impl UDPMessage {
|
||||
// Helpers pour récupérer certain éléments des messages
|
||||
pub fn get_message_id(&self) -> Option<Uuid> {
|
||||
match &self.data {
|
||||
UDPMessageData::ClientPing { message_id } => Some(*message_id),
|
||||
UDPMessageData::ServerPing { message_id } => Some(*message_id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper pour compatibilité avec UDPMessageType
|
||||
impl UDPMessageType {
|
||||
pub fn from_message(data: &[u8]) -> Option<Self> {
|
||||
if data.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Self::from_repr(data[0])
|
||||
}
|
||||
}
|
||||
|
||||
impl UdpBroadcastMessage {
|
||||
// Constructeurs server
|
||||
pub fn server_ping(addresses: HashSet<SocketAddr>, message_id: Uuid) -> Self {
|
||||
let data = UDPMessageData::server_ping(message_id);
|
||||
let size = data.size();
|
||||
Self { data, addresses, size }
|
||||
}
|
||||
|
||||
pub fn server_audio(addresses: HashSet<SocketAddr>, user: Uuid, sequence: u16, data: Bytes) -> Self {
|
||||
let msg_data = UDPMessageData::server_audio(user, sequence, data);
|
||||
let size = msg_data.size();
|
||||
Self { data: msg_data, addresses, size }
|
||||
}
|
||||
|
||||
// Conversion vers messages individuels (pour compatibilité)
|
||||
pub fn to_individual_messages(&self) -> Vec<UDPMessage> {
|
||||
self.addresses.iter().map(|&addr| {
|
||||
UDPMessage {
|
||||
data: self.data.clone(),
|
||||
address: addr,
|
||||
size: self.size,
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
// Helpers
|
||||
pub fn to_bytes(&self) -> Bytes {
|
||||
self.data.to_bytes()
|
||||
}
|
||||
|
||||
pub fn addresses(&self) -> &HashSet<SocketAddr> {
|
||||
&self.addresses
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
use tokio::net::UdpSocket;
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
use tokio::task::AbortHandle;
|
||||
use crate::domain::event::{Event, EventBus};
|
||||
use crate::network::protocol::{UDPMessage, UdpBroadcastMessage};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UdpServer {
|
||||
event_bus: EventBus,
|
||||
socket: Arc<UdpSocket>,
|
||||
abort_handle: Option<AbortHandle>,
|
||||
}
|
||||
|
||||
impl UdpServer {
|
||||
pub async fn new(event_bus: EventBus, addr: &str) -> Self {
|
||||
let socket = UdpSocket::bind(addr).await.unwrap();
|
||||
let addr = socket.local_addr().unwrap();
|
||||
println!("Socket UDP lié avec succès on {}", addr);
|
||||
|
||||
Self {
|
||||
event_bus,
|
||||
socket: Arc::new(socket),
|
||||
abort_handle: None
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
println!("Démarrage du serveur UDP...");
|
||||
let event_bus = self.event_bus.clone();
|
||||
let socket = self.socket.clone();
|
||||
|
||||
let recv_task = tokio::spawn(async move {
|
||||
// Buffer réutilisable pour éviter les allocations
|
||||
let mut buf = vec![0u8; 1500];
|
||||
|
||||
loop {
|
||||
match socket.recv_from(&mut buf).await {
|
||||
Ok((size, address)) => {
|
||||
// Slice du buffer pour éviter de copier des données inutiles
|
||||
if let Ok(message) = UDPMessage::from_client_bytes(address, &buf[..size]) {
|
||||
event_bus.emit(Event::UdpIn(message)).await;
|
||||
}
|
||||
// Sinon, on ignore silencieusement le message malformé
|
||||
}
|
||||
Err(e) => {
|
||||
match e.kind() {
|
||||
std::io::ErrorKind::ConnectionReset |
|
||||
std::io::ErrorKind::ConnectionAborted => {
|
||||
// Silencieux pour les déconnexions normales
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
println!("Erreur UDP: {}", e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.abort_handle = Some(recv_task.abort_handle());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_udp_message(&self, message: &UDPMessage) -> bool {
|
||||
match self.socket.send_to(&message.to_bytes(), message.address()).await {
|
||||
Ok(_size) => {
|
||||
self.event_bus.emit(Event::UdpOut(message.clone())).await;
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Erreur lors de l'envoi du message à {}: {}", message.address(), e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn broadcast_udp_message(&self, message: &UdpBroadcastMessage) -> bool {
|
||||
let bytes = message.to_bytes();
|
||||
|
||||
for &address in message.addresses() {
|
||||
match self.socket.send_to(&bytes, address).await {
|
||||
Ok(_) => {
|
||||
// Emit individual event pour tracking
|
||||
let individual_msg = UDPMessage {
|
||||
data: message.data.clone(),
|
||||
address,
|
||||
size: message.size,
|
||||
};
|
||||
self.event_bus.emit(Event::UdpOut(individual_msg)).await;
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Erreur broadcast vers {}: {}", address, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
1
src/network/udp/mod.rs
Normal file
1
src/network/udp/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod server;
|
||||
160
src/network/udp/server.rs
Normal file
160
src/network/udp/server.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
Utilisation de UDP pour la communication entre le client et le serveur.
|
||||
L'idée est d'utilise SO_REUSEPORT et de router les paquets aux clients dans le channel correspondant.
|
||||
Il est donc important de garder une table de routage entre les channels et les sockets, comme ça le serveur sait à qui re-router le paquet.
|
||||
Dans l'ancienne version on utilisait un mpsc, mais celà casse l'intérêt du SO_REUSEPORT.
|
||||
Avec cette logique, on évite de transporter le paquet de thread en thread et potentiellement de devoir faire des copies.
|
||||
*/
|
||||
|
||||
use std::io;
|
||||
use std::net::{SocketAddr};
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task;
|
||||
use crate::utils::shared_store::{SharedArcMap, SharedArcVec};
|
||||
use crate::utils::toolbox::number_of_cpus;
|
||||
use crate::utils::logger::ContextLogger;
|
||||
use crate::{log_info, log_warn, log_error, log_debug};
|
||||
|
||||
|
||||
pub struct UdpServer {
|
||||
// table de routage channel -> socket
|
||||
table_router: SharedArcMap<String, SharedArcVec<SocketAddr>>,
|
||||
|
||||
// binds
|
||||
bind_addr: SocketAddr,
|
||||
workers: usize,
|
||||
buf_size: usize,
|
||||
|
||||
// logger
|
||||
logger: ContextLogger,
|
||||
}
|
||||
|
||||
impl UdpServer {
|
||||
pub fn new(bind_addr: &str) -> io::Result<Self> {
|
||||
// convert bind_addr
|
||||
let bind_addr = match bind_addr.parse::<SocketAddr>() {
|
||||
Ok(addr) => addr,
|
||||
Err(e) => {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidInput, format!("Invalid bind address '{}': {}", bind_addr, e)))
|
||||
},
|
||||
};
|
||||
|
||||
let logger = ContextLogger::new("UDP");
|
||||
logger.info(&format!("Creating UDP server on {}", bind_addr));
|
||||
|
||||
Ok(Self {
|
||||
table_router: SharedArcMap::new(),
|
||||
bind_addr,
|
||||
workers: number_of_cpus(),
|
||||
buf_size: 1500,
|
||||
logger,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn run(self) -> io::Result<()> {
|
||||
// S'assure qu'on est sur une runtime (utile si appelé hors #[tokio::main])
|
||||
let _ = Handle::try_current().map_err(|e| {
|
||||
self.logger.error(&format!("Runtime Tokio not available: {}", e));
|
||||
io::Error::new(io::ErrorKind::Other, e)
|
||||
})?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
self.run_unix().await
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
self.run_windows().await
|
||||
}
|
||||
#[cfg(not(any(unix, windows)))]
|
||||
{
|
||||
log_error!(self.logger, "OS not supported");
|
||||
Err(io::Error::new(io::ErrorKind::Other, "OS non supporté"))
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// Impl Linux/macOS (SO_REUSEPORT)
|
||||
// -------------------------
|
||||
#[cfg(unix)]
|
||||
async fn run_unix(self) -> io::Result<()> {
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
|
||||
self.logger.info(&format!("Starting UDP server on {} with {} workers - Unix mode", self.bind_addr, self.workers));
|
||||
|
||||
let mut handles = Vec::with_capacity(self.workers);
|
||||
for id in 0..self.workers {
|
||||
let logger = self.logger.with_sub_context(&format!("WORKER_{}", id));
|
||||
let bind_addr = self.bind_addr;
|
||||
|
||||
// Create a socket UDP with SO_REUSEPORT
|
||||
let domain = match bind_addr {
|
||||
SocketAddr::V4(_) => Domain::IPV4,
|
||||
SocketAddr::V6(_) => Domain::IPV6,
|
||||
};
|
||||
let sock = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP))?;
|
||||
sock.set_reuse_address(true)?;
|
||||
sock.set_reuse_port(true)?;
|
||||
sock.bind(&bind_addr.into())?;
|
||||
|
||||
let std_sock = std::net::UdpSocket::from(sock);
|
||||
std_sock.set_nonblocking(true)?;
|
||||
let udp = UdpSocket::from_std(std_sock)?;
|
||||
|
||||
let buf_size = self.buf_size;
|
||||
let h = task::spawn(async move {
|
||||
if let Err(e) = Self::worker_loop(id, udp, buf_size, logger).await {
|
||||
eprintln!("[worker {id}] erreur: {e}");
|
||||
}
|
||||
});
|
||||
handles.push(h);
|
||||
}
|
||||
|
||||
for h in handles {
|
||||
let _ = h.await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// Impl Windows (1 socket partagé, N tâches concurrentes)
|
||||
// -------------------------
|
||||
#[cfg(windows)]
|
||||
async fn run_windows(self) -> io::Result<()> {
|
||||
self.logger.info(&format!("Starting UDP server on {} with {} workers - Win mode", self.bind_addr, self.workers));
|
||||
|
||||
let udp = UdpSocket::bind(self.bind_addr).await?;
|
||||
let udp = Arc::new(udp);
|
||||
|
||||
let mut handles = Vec::with_capacity(self.workers);
|
||||
for id in 0..self.workers {
|
||||
let logger = self.logger.with_sub_context(&format!("WORKER_{}", id));
|
||||
let sock = udp.clone();
|
||||
let buf_size = self.buf_size;
|
||||
let h = task::spawn(async move {
|
||||
if let Err(e) = worker_loop(id, (*sock).clone(), buf_size, logger).await {
|
||||
eprintln!("[worker {id}] erreur: {e}");
|
||||
}
|
||||
});
|
||||
handles.push(h);
|
||||
}
|
||||
|
||||
for h in handles {
|
||||
let _ = h.await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn worker_loop(id: usize, socket: UdpSocket, buf_size: usize, logger: ContextLogger) -> io::Result<()> {
|
||||
let mut buf = vec![0u8; buf_size];
|
||||
logger.info("listening");
|
||||
loop {
|
||||
let (n, peer) = socket.recv_from(&mut buf).await?;
|
||||
// Traitement: ici, simple echo
|
||||
// Remplacez par votre logique (routage canal -> client, etc.)
|
||||
eprintln!("[worker {id}] reçu {n}o de {peer}");
|
||||
socket.send_to(&buf[..n], &peer).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
use tokio::net::UdpSocket;
|
||||
use tokio::sync::RwLock;
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use dashmap::DashMap;
|
||||
use tokio::task::AbortHandle;
|
||||
use tokio::time::{sleep, Instant};
|
||||
use crate::domain::client::Client;
|
||||
use crate::domain::event::{Event, EventBus};
|
||||
use crate::network::protocol::{UdpClientMessage, UdpServerMessage};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UdpServer {
|
||||
event_bus: EventBus,
|
||||
socket: Arc<UdpSocket>,
|
||||
abort_handle: Option<AbortHandle>,
|
||||
clients: Arc<DashMap<SocketAddr, Client>>,
|
||||
}
|
||||
|
||||
impl UdpServer {
|
||||
pub async fn new(event_bus: EventBus, addr: &str) -> Self {
|
||||
let socket = UdpSocket::bind(addr).await.unwrap();
|
||||
let addr = socket.local_addr().unwrap();
|
||||
println!("Socket UDP lié avec succès on {}", addr);
|
||||
|
||||
Self {
|
||||
event_bus,
|
||||
socket: Arc::new(socket),
|
||||
abort_handle: None,
|
||||
clients: Arc::new(DashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
println!("Démarrage du serveur UDP...");
|
||||
let event_bus = self.event_bus.clone();
|
||||
let socket = self.socket.clone();
|
||||
let clients = self.clients.clone();
|
||||
|
||||
let recv_task = tokio::spawn(async move {
|
||||
let mut buf = [0u8; 1500];
|
||||
loop {
|
||||
match socket.recv_from(&mut buf).await {
|
||||
Ok((size, address)) => {
|
||||
// Ajouter le client à la liste
|
||||
// todo : solution vraiment pas idéal, il faudrait vraiment la repenser avec un système helo/bye
|
||||
if !clients.contains_key(&address) {
|
||||
let client = Client::new(address);
|
||||
clients.insert(address, client);
|
||||
println!("Nouveau client connecté: {}", address);
|
||||
}else {
|
||||
let mut client = clients.get_mut(&address).unwrap();
|
||||
client.update_last_seen();
|
||||
}
|
||||
|
||||
if let Ok(message) = UdpClientMessage::from_bytes(&buf[..size]) {
|
||||
let event = Event::UdpIn { address, size, message };
|
||||
event_bus.emit(event).await;
|
||||
} else {
|
||||
println!("Erreur lors du parsing du message de {}: {:?}", address, &buf[..size]);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
match e.kind() {
|
||||
std::io::ErrorKind::ConnectionReset |
|
||||
std::io::ErrorKind::ConnectionAborted => {
|
||||
// Silencieux pour les déconnexions normales
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
println!("Erreur UDP: {}", e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.abort_handle = Some(recv_task.abort_handle());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send(&self, address: SocketAddr, message: UdpServerMessage) -> bool {
|
||||
let event_bus = self.event_bus.clone();
|
||||
match self.socket.send_to(&message.to_byte(), address).await {
|
||||
Ok(size) => {
|
||||
event_bus.emit(Event::UdpOut { address, size, message }).await;
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Erreur lors de l'envoi du message à {}: {}", address, e);
|
||||
// Optionnel : retirer le client si l'adresse est invalide
|
||||
self.remove_client(address).await;
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn group_send(&self, addr_list: Vec<SocketAddr>, message: UdpServerMessage) -> bool {
|
||||
if addr_list.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let socket = self.socket.clone();
|
||||
let clients = self.clients.clone();
|
||||
|
||||
let send_tasks: Vec<_> = addr_list.into_iter().map(|address| {
|
||||
let event_bus = self.event_bus.clone();
|
||||
let message_clone = message.clone();
|
||||
let socket_clone = socket.clone();
|
||||
let clients_clone = clients.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
match socket_clone.send_to(&message_clone.to_byte(), address).await {
|
||||
Ok(size) => {
|
||||
event_bus.emit(Event::UdpOut { address, size, message: message_clone }).await;
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Erreur lors de l'envoi du message à {}: {}", address, e);
|
||||
// Optionnel : retirer le client si l'adresse est invalide
|
||||
if clients_clone.contains_key(&address) {
|
||||
clients_clone.remove(&address);
|
||||
println!("Client {} retiré de la liste", address);
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
})
|
||||
}).collect();
|
||||
|
||||
let mut all_success = true;
|
||||
for task in send_tasks {
|
||||
match task.await {
|
||||
Ok(success) => {
|
||||
if !success {
|
||||
all_success = false;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
all_success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
all_success
|
||||
}
|
||||
|
||||
pub async fn all_send(&self, message: UdpServerMessage) -> bool {
|
||||
let client_addresses = self.get_clients().await;
|
||||
self.group_send(client_addresses, message).await
|
||||
}
|
||||
|
||||
pub async fn get_clients(&self) -> Vec<SocketAddr> {
|
||||
self.clients.iter()
|
||||
.map(|entry| *entry.key())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Nouvelle méthode pour nettoyer les clients déconnectés
|
||||
async fn remove_client(&self, address: SocketAddr) {
|
||||
if self.clients.contains_key(&address){
|
||||
self.clients.remove(&address);
|
||||
println!("Client {} retiré de la liste", address);
|
||||
}else {
|
||||
println!("Client {} n'est pas dans la liste", address);
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode pour nettoyer les clients inactifs
|
||||
pub async fn cleanup_inactive_clients(&self) {
|
||||
let timeout = Duration::from_secs(10);
|
||||
let now = Instant::now();
|
||||
let mut to_remove = Vec::new();
|
||||
|
||||
for entry in self.clients.iter() {
|
||||
let address = *entry.key();
|
||||
let client = entry.value();
|
||||
|
||||
if now.duration_since(client.last_seen()) > timeout {
|
||||
to_remove.push(address);
|
||||
}
|
||||
}
|
||||
|
||||
for address in &to_remove {
|
||||
println!("Suppression du client {}", address);
|
||||
self.clients.remove(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::task::AbortHandle;
|
||||
use crate::domain::client::ClientManager;
|
||||
use crate::domain::event::{Event, EventBus};
|
||||
use crate::network::protocol::{UDPMessageType, UDPMessage};
|
||||
use crate::network::udp::UdpServer;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Dispatcher {
|
||||
event_bus: EventBus,
|
||||
|
||||
udp_server: UdpServer,
|
||||
client_manager: ClientManager
|
||||
}
|
||||
|
||||
impl Dispatcher {
|
||||
pub async fn new(event_bus: EventBus, udp_server: UdpServer, client_manager: ClientManager) -> Self {
|
||||
Self {
|
||||
event_bus,
|
||||
udp_server,
|
||||
client_manager,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start(&self, mut receiver: mpsc::Receiver<Event>) {
|
||||
let (_udp_in_abort_handle, udp_in_sender) = self.udp_in_handler().await;
|
||||
|
||||
while let Some(event) = receiver.recv().await {
|
||||
match event {
|
||||
Event::UdpIn(message) => {
|
||||
let _ = udp_in_sender.send(message).await;
|
||||
// // println!("Message reçu de {}: {:?}", address, message);
|
||||
// let udp_server = self.udp_server.clone();
|
||||
// tokio::spawn(async move {
|
||||
// match message {
|
||||
// UdpClientMessage::Ping {message_id} => {
|
||||
// let send = UdpServerMessage::ping(message_id);
|
||||
// let _ = udp_server.all_send(send);
|
||||
// }
|
||||
// UdpClientMessage::Audio {sequence, data} => {
|
||||
// let tmp_user_id = Uuid::new_v4();
|
||||
// let send = UdpServerMessage::audio(tmp_user_id, sequence, data);
|
||||
// let _ = udp_server.all_send(send).await;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
}
|
||||
Event::UdpOut(message) => {
|
||||
// println!("Message envoyé à {}: {:?}", address, message);
|
||||
}
|
||||
Event::TickSeconds => {
|
||||
self.client_manager.cleanup(Duration::from_secs(10));
|
||||
}
|
||||
_ => {
|
||||
println!("Event non prit en charge : {:?}", event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn udp_in_handler(&self) -> (AbortHandle, mpsc::Sender<UDPMessage>) {
|
||||
let (sender, mut consumer) = mpsc::channel::<UDPMessage>(1024);
|
||||
let udp_server = self.udp_server.clone();
|
||||
|
||||
let task = tokio::spawn(async move {
|
||||
while let Some(message) = consumer.recv().await {
|
||||
// Traitement direct du message sans double parsing
|
||||
match message.message_type() {
|
||||
UDPMessageType::Ping => {
|
||||
let response_message = UDPMessage::server_ping(message.address, message.get_message_id().unwrap());
|
||||
let _ = udp_server.send_udp_message(&response_message);
|
||||
}
|
||||
UDPMessageType::Audio => {
|
||||
// Traiter l'audio
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
(task.abort_handle(), sender)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod dispatcher;
|
||||
@@ -1,398 +0,0 @@
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Helpers pour la manipulation de bytes - idéal pour les protocoles binaires
|
||||
///
|
||||
/// Cette structure permet de lire séquentiellement des données binaires
|
||||
/// en maintenant une position de lecture interne.
|
||||
pub struct ByteReader<'a> {
|
||||
/// Référence vers les données à lire
|
||||
data: &'a [u8],
|
||||
/// Position actuelle dans le buffer de lecture
|
||||
position: usize,
|
||||
}
|
||||
|
||||
impl<'a> ByteReader<'a> {
|
||||
/// Crée un nouveau lecteur de bytes à partir d'un slice
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `data` - Le slice de bytes à lire
|
||||
///
|
||||
/// # Example
|
||||
/// ```text
|
||||
/// let data = &[0x01, 0x02, 0x03, 0x04];
|
||||
/// let reader = ByteReader::new(data);
|
||||
/// ```
|
||||
pub fn new(data: &'a [u8]) -> Self {
|
||||
Self { data, position: 0 }
|
||||
}
|
||||
|
||||
/// Retourne le nombre de bytes restants à lire
|
||||
///
|
||||
/// Utilise `saturating_sub` pour éviter les débordements
|
||||
/// si la position dépasse la taille des données
|
||||
pub fn remaining(&self) -> usize {
|
||||
self.data.len().saturating_sub(self.position)
|
||||
}
|
||||
|
||||
/// Vérifie si tous les bytes ont été lus
|
||||
///
|
||||
/// # Returns
|
||||
/// `true` si il n'y a plus de bytes à lire
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.remaining() == 0
|
||||
}
|
||||
|
||||
/// Retourne la position actuelle de lecture
|
||||
pub fn position(&self) -> usize {
|
||||
self.position
|
||||
}
|
||||
|
||||
/// Déplace la position de lecture à l'index spécifié
|
||||
///
|
||||
/// La position est automatiquement limitée à la taille des données
|
||||
/// pour éviter les débordements
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `position` - Nouvelle position de lecture
|
||||
pub fn seek(&mut self, position: usize) {
|
||||
self.position = position.min(self.data.len());
|
||||
}
|
||||
|
||||
/// Lit un byte (u8) à la position actuelle
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(u8)` - La valeur lue si disponible
|
||||
/// * `Err(&'static str)` - Si la fin du buffer est atteinte
|
||||
pub fn read_u8(&mut self) -> Result<u8, &'static str> {
|
||||
if self.position < self.data.len() {
|
||||
let value = self.data[self.position];
|
||||
self.position += 1;
|
||||
Ok(value)
|
||||
} else {
|
||||
Err("Not enough data for u8")
|
||||
}
|
||||
}
|
||||
|
||||
/// Lit un entier 16-bit en big-endian
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(u16)` - La valeur lue si 2 bytes sont disponibles
|
||||
/// * `Err(&'static str)` - Si moins de 2 bytes sont disponibles
|
||||
pub fn read_u16_be(&mut self) -> Result<u16, &'static str> {
|
||||
if self.remaining() >= 2 {
|
||||
let value = u16::from_be_bytes([
|
||||
self.data[self.position],
|
||||
self.data[self.position + 1],
|
||||
]);
|
||||
self.position += 2;
|
||||
Ok(value)
|
||||
} else {
|
||||
Err("Not enough data for u16")
|
||||
}
|
||||
}
|
||||
|
||||
/// Lit un entier 32-bit en big-endian
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(u32)` - La valeur lue si 4 bytes sont disponibles
|
||||
/// * `Err(&'static str)` - Si moins de 4 bytes sont disponibles
|
||||
pub fn read_u32_be(&mut self) -> Result<u32, &'static str> {
|
||||
if self.remaining() >= 4 {
|
||||
let value = u32::from_be_bytes([
|
||||
self.data[self.position],
|
||||
self.data[self.position + 1],
|
||||
self.data[self.position + 2],
|
||||
self.data[self.position + 3],
|
||||
]);
|
||||
self.position += 4;
|
||||
Ok(value)
|
||||
} else {
|
||||
Err("Not enough data for u32")
|
||||
}
|
||||
}
|
||||
|
||||
/// Lit un entier 64-bit en big-endian
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(u64)` - La valeur lue si 8 bytes sont disponibles
|
||||
/// * `Err(&'static str)` - Si moins de 8 bytes sont disponibles
|
||||
pub fn read_u64_be(&mut self) -> Result<u64, &'static str> {
|
||||
if self.remaining() >= 8 {
|
||||
let value = u64::from_be_bytes([
|
||||
self.data[self.position],
|
||||
self.data[self.position + 1],
|
||||
self.data[self.position + 2],
|
||||
self.data[self.position + 3],
|
||||
self.data[self.position + 4],
|
||||
self.data[self.position + 5],
|
||||
self.data[self.position + 6],
|
||||
self.data[self.position + 7],
|
||||
]);
|
||||
self.position += 8;
|
||||
Ok(value)
|
||||
} else {
|
||||
Err("Not enough data for u64")
|
||||
}
|
||||
}
|
||||
|
||||
/// Lit un UUID (16 bytes) à la position actuelle
|
||||
///
|
||||
/// Les UUIDs sont stockés sous forme de 16 bytes consécutifs.
|
||||
/// Cette méthode lit ces 16 bytes et les convertit en UUID.
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(Uuid)` - L'UUID lu si 16 bytes sont disponibles
|
||||
/// * `Err(&'static str)` - Si moins de 16 bytes sont disponibles
|
||||
pub fn read_uuid(&mut self) -> Result<Uuid, &'static str> {
|
||||
if self.remaining() >= 16 {
|
||||
let uuid_bytes = self.read_fixed_bytes::<16>()?;
|
||||
Ok(Uuid::from_bytes(uuid_bytes))
|
||||
} else {
|
||||
Err("Not enough data for UUID")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Lit une séquence de bytes de longueur spécifiée
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `len` - Nombre de bytes à lire
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(&[u8])` - Slice des bytes lus si disponibles
|
||||
/// * `Err(&'static str)` - Si pas assez de bytes disponibles
|
||||
pub fn read_bytes(&mut self, len: usize) -> Result<&'a [u8], &'static str> {
|
||||
if self.remaining() >= len {
|
||||
let slice = &self.data[self.position..self.position + len];
|
||||
self.position += len;
|
||||
Ok(slice)
|
||||
} else {
|
||||
Err("Not enough data for bytes")
|
||||
}
|
||||
}
|
||||
|
||||
/// Lit un tableau de bytes de taille fixe définie à la compilation
|
||||
///
|
||||
/// Utilise les generics const pour définir la taille du tableau
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok([u8; N])` - Tableau de bytes lu si disponible
|
||||
/// * `Err(&'static str)` - Si pas assez de bytes disponibles
|
||||
pub fn read_fixed_bytes<const N: usize>(&mut self) -> Result<[u8; N], &'static str> {
|
||||
if self.remaining() >= N {
|
||||
let mut array = [0u8; N];
|
||||
array.copy_from_slice(&self.data[self.position..self.position + N]);
|
||||
self.position += N;
|
||||
Ok(array)
|
||||
} else {
|
||||
Err("Not enough data for fixed bytes")
|
||||
}
|
||||
}
|
||||
|
||||
/// Lit tous les bytes restants dans le buffer
|
||||
///
|
||||
/// Après cet appel, le reader sera vide (position = taille totale)
|
||||
///
|
||||
/// # Returns
|
||||
/// Slice contenant tous les bytes restants
|
||||
pub fn read_remaining(&mut self) -> &'a [u8] {
|
||||
let slice = &self.data[self.position..];
|
||||
self.position = self.data.len();
|
||||
slice
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure pour construire séquentiellement des données binaires
|
||||
///
|
||||
/// Contrairement à ByteReader, cette structure possède ses propres données
|
||||
/// et permet d'écrire des valeurs de différents types.
|
||||
pub struct ByteWriter {
|
||||
/// Buffer interne pour stocker les données écrites
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ByteWriter {
|
||||
/// Crée un nouveau writer avec un Vec vide
|
||||
pub fn new() -> Self {
|
||||
Self { data: Vec::new() }
|
||||
}
|
||||
|
||||
/// Crée un nouveau writer avec une capacité pré-allouée
|
||||
///
|
||||
/// Utile pour éviter les réallocations si la taille finale
|
||||
/// est approximativement connue
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `capacity` - Capacité initiale du buffer
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
data: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
/// Écrit un byte (u8) dans le buffer
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `value` - Valeur à écrire
|
||||
pub fn write_u8(&mut self, value: u8) {
|
||||
self.data.push(value);
|
||||
}
|
||||
|
||||
/// Écrit un entier 16-bit en big-endian
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `value` - Valeur à écrire
|
||||
pub fn write_u16_be(&mut self, value: u16) {
|
||||
self.data.extend_from_slice(&value.to_be_bytes());
|
||||
}
|
||||
|
||||
/// Écrit un entier 32-bit en big-endian
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `value` - Valeur à écrire
|
||||
pub fn write_u32_be(&mut self, value: u32) {
|
||||
self.data.extend_from_slice(&value.to_be_bytes());
|
||||
}
|
||||
|
||||
/// Écrit un entier 64-bit en big-endian
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `value` - Valeur à écrire
|
||||
pub fn write_u64_be(&mut self, value: u64) {
|
||||
self.data.extend_from_slice(&value.to_be_bytes());
|
||||
}
|
||||
|
||||
/// Écrit une séquence de bytes dans le buffer
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `bytes` - Slice de bytes à écrire
|
||||
pub fn write_bytes(&mut self, bytes: &[u8]) {
|
||||
self.data.extend_from_slice(bytes);
|
||||
}
|
||||
|
||||
/// Écrit un tableau de bytes de taille fixe
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `bytes` - Tableau de bytes à écrire
|
||||
pub fn write_fixed_bytes<const N: usize>(&mut self, bytes: [u8; N]) {
|
||||
self.data.extend_from_slice(&bytes);
|
||||
}
|
||||
|
||||
/// Consomme le writer et retourne le Vec contenant les données
|
||||
///
|
||||
/// # Returns
|
||||
/// Vec<u8> contenant toutes les données écrites
|
||||
pub fn into_vec(self) -> Vec<u8> {
|
||||
self.data
|
||||
}
|
||||
|
||||
/// Retourne une référence vers les données sous forme de slice
|
||||
///
|
||||
/// # Returns
|
||||
/// Slice des données écrites
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
&self.data
|
||||
}
|
||||
|
||||
/// Retourne la taille actuelle du buffer
|
||||
pub fn len(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
/// Vérifie si le buffer est vide
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.data.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Implémentation du trait Default pour ByteWriter
|
||||
///
|
||||
/// Permet d'utiliser ByteWriter::default() comme équivalent de ByteWriter::new()
|
||||
impl Default for ByteWriter {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Fonctions utilitaires standalone pour la lecture directe sans état
|
||||
///
|
||||
/// Ces fonctions permettent de lire des valeurs à des offsets spécifiques
|
||||
/// sans avoir besoin de créer un ByteReader.
|
||||
|
||||
/// Lit un byte à l'offset spécifié
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `data` - Slice de données source
|
||||
/// * `offset` - Position de lecture
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Some(u8)` - Valeur lue si l'offset est valide
|
||||
/// * `None` - Si l'offset dépasse la taille des données
|
||||
pub fn read_u8_at(data: &[u8], offset: usize) -> Option<u8> {
|
||||
data.get(offset).copied()
|
||||
}
|
||||
|
||||
/// Lit un entier 16-bit big-endian à l'offset spécifié
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `data` - Slice de données source
|
||||
/// * `offset` - Position de lecture
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Some(u16)` - Valeur lue si 2 bytes sont disponibles à l'offset
|
||||
/// * `None` - Si pas assez de bytes disponibles
|
||||
pub fn read_u16_be_at(data: &[u8], offset: usize) -> Option<u16> {
|
||||
if data.len() >= offset + 2 {
|
||||
Some(u16::from_be_bytes([data[offset], data[offset + 1]]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Lit un entier 32-bit big-endian à l'offset spécifié
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `data` - Slice de données source
|
||||
/// * `offset` - Position de lecture
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Some(u32)` - Valeur lue si 4 bytes sont disponibles à l'offset
|
||||
/// * `None` - Si pas assez de bytes disponibles
|
||||
pub fn read_u32_be_at(data: &[u8], offset: usize) -> Option<u32> {
|
||||
if data.len() >= offset + 4 {
|
||||
Some(u32::from_be_bytes([
|
||||
data[offset],
|
||||
data[offset + 1],
|
||||
data[offset + 2],
|
||||
data[offset + 3],
|
||||
]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Lit un entier 64-bit big-endian à l'offset spécifié
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `data` - Slice de données source
|
||||
/// * `offset` - Position de lecture
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Some(u64)` - Valeur lue si 8 bytes sont disponibles à l'offset
|
||||
/// * `None` - Si pas assez de bytes disponibles
|
||||
pub fn read_u64_be_at(data: &[u8], offset: usize) -> Option<u64> {
|
||||
if data.len() >= offset + 8 {
|
||||
Some(u64::from_be_bytes([
|
||||
data[offset],
|
||||
data[offset + 1],
|
||||
data[offset + 2],
|
||||
data[offset + 3],
|
||||
data[offset + 4],
|
||||
data[offset + 5],
|
||||
data[offset + 6],
|
||||
data[offset + 7],
|
||||
]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
116
src/utils/config.rs
Normal file
116
src/utils/config.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
use serde::Deserialize;
|
||||
use crate::utils::logger::ContextLogger;
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct Config {
|
||||
#[serde(default = "default_server_host")]
|
||||
pub server_host: String,
|
||||
|
||||
#[serde(default = "default_server_port")]
|
||||
pub server_port: u16,
|
||||
|
||||
#[serde(default = "default_buffer_size")]
|
||||
pub buffer_size: usize,
|
||||
|
||||
#[serde(default = "default_log_level")]
|
||||
pub log_level: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug_mode: bool,
|
||||
|
||||
pub workers: Option<usize>,
|
||||
pub database_url: Option<String>,
|
||||
}
|
||||
|
||||
// Default values
|
||||
fn default_server_host() -> String { "127.0.0.1".to_string() }
|
||||
fn default_server_port() -> u16 { 8080 }
|
||||
fn default_buffer_size() -> usize { 1024 }
|
||||
fn default_log_level() -> String { "info".to_string() }
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ConfigError {
|
||||
EnvFile(dotenvy::Error),
|
||||
Parse(envy::Error),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ConfigError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ConfigError::EnvFile(e) => write!(f, "Error loading .env file: {}", e),
|
||||
ConfigError::Parse(e) => write!(f, "Error parsing configuration: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ConfigError {}
|
||||
|
||||
impl Config {
|
||||
pub fn load() -> Result<Config, ConfigError> {
|
||||
let logger = ContextLogger::new("CONFIG");
|
||||
|
||||
// Display current working directory
|
||||
match env::current_dir() {
|
||||
Ok(path) => {
|
||||
logger.info(&format!("Working directory: {}", path.display()));
|
||||
}
|
||||
Err(e) => {
|
||||
logger.error(&format!("Error getting directory: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
// Check if .env file exists and load it
|
||||
let env_path = Path::new(".env");
|
||||
if env_path.exists() {
|
||||
logger.info(".env found");
|
||||
|
||||
// Load the .env file
|
||||
match dotenvy::from_path(env_path) {
|
||||
Ok(_) => logger.info(".env file loaded successfully"),
|
||||
Err(e) => {
|
||||
logger.error(&format!("Error loading .env: {}", e));
|
||||
return Err(ConfigError::EnvFile(e));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.info(".env file does not exist - using defaults and system env vars");
|
||||
}
|
||||
|
||||
// Load configuration from environment variables
|
||||
logger.info("Loading configuration from environment variables");
|
||||
|
||||
let config = envy::from_env::<Config>().map_err(ConfigError::Parse)?;
|
||||
|
||||
logger.info("Configuration loaded successfully");
|
||||
config.print_summary();
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn socket_addr(&self) -> String {
|
||||
format!("{}:{}", self.server_host, self.server_port)
|
||||
}
|
||||
|
||||
pub fn workers_count(&self) -> usize {
|
||||
self.workers.unwrap_or_else(|| {
|
||||
crate::utils::toolbox::number_of_cpus()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn print_summary(&self) {
|
||||
let logger = ContextLogger::new("CONFIG");
|
||||
logger.info(&format!("Server address: {}", self.socket_addr()));
|
||||
logger.info(&format!("Workers: {}", self.workers_count()));
|
||||
logger.info(&format!("Buffer size: {} bytes", self.buffer_size));
|
||||
logger.info(&format!("Log level: {}", self.log_level));
|
||||
logger.info(&format!("Debug mode: {}", self.debug_mode));
|
||||
|
||||
if self.database_url.is_some() {
|
||||
logger.info("Database URL: [CONFIGURED]");
|
||||
} else {
|
||||
logger.info("Database URL: [NOT SET]");
|
||||
}
|
||||
}
|
||||
}
|
||||
88
src/utils/logger.rs
Normal file
88
src/utils/logger.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use log::{info, warn, error, debug, LevelFilter};
|
||||
use std::io::Write;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ContextLogger {
|
||||
context: String,
|
||||
}
|
||||
|
||||
impl ContextLogger {
|
||||
pub fn new(context: &str) -> Self {
|
||||
Self {
|
||||
context: context.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_sub_context(&self, sub_context: &str) -> Self {
|
||||
Self {
|
||||
context: format!("{}:{}", self.context, sub_context),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn info(&self, msg: &str) {
|
||||
info!("[{}] {}", self.context, msg);
|
||||
}
|
||||
|
||||
pub fn warn(&self, msg: &str) {
|
||||
warn!("[{}] {}", self.context, msg);
|
||||
}
|
||||
|
||||
pub fn error(&self, msg: &str) {
|
||||
error!("[{}] {}", self.context, msg);
|
||||
}
|
||||
|
||||
pub fn debug(&self, msg: &str) {
|
||||
debug!("[{}] {}", self.context, msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Macros pour simplifier l'usage avec formatage
|
||||
#[macro_export]
|
||||
macro_rules! log_info {
|
||||
($logger:expr, $($arg:tt)*) => {
|
||||
log::info!("[{}] {}", $logger.context, format_args!($($arg)*));
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log_warn {
|
||||
($logger:expr, $($arg:tt)*) => {
|
||||
log::warn!("[{}] {}", $logger.context, format_args!($($arg)*));
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log_error {
|
||||
($logger:expr, $($arg:tt)*) => {
|
||||
log::error!("[{}] {}", $logger.context, format_args!($($arg)*));
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log_debug {
|
||||
($logger:expr, $($arg:tt)*) => {
|
||||
log::debug!("[{}] {}", $logger.context, format_args!($($arg)*));
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init_logger(log_level: &str) {
|
||||
let level = match log_level.to_lowercase().as_str() {
|
||||
"debug" => LevelFilter::Debug,
|
||||
"info" => LevelFilter::Info,
|
||||
"warn" | "warning" => LevelFilter::Warn,
|
||||
"error" => LevelFilter::Error,
|
||||
_ => LevelFilter::Info,
|
||||
};
|
||||
|
||||
env_logger::Builder::from_default_env()
|
||||
.filter_level(level)
|
||||
.format_timestamp_secs()
|
||||
.format(|buf, record| {
|
||||
writeln!(buf, "{} | {} | {}",
|
||||
buf.timestamp(),
|
||||
record.level(),
|
||||
record.args()
|
||||
)
|
||||
})
|
||||
.init();
|
||||
}
|
||||
@@ -1 +1,4 @@
|
||||
pub mod byte_utils;
|
||||
pub mod shared_store;
|
||||
pub mod config;
|
||||
pub mod toolbox;
|
||||
pub mod logger;
|
||||
1323
src/utils/shared_store.rs
Normal file
1323
src/utils/shared_store.rs
Normal file
File diff suppressed because it is too large
Load Diff
9
src/utils/toolbox.rs
Normal file
9
src/utils/toolbox.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub fn number_of_cpus() -> usize {
|
||||
match std::thread::available_parallelism() {
|
||||
Ok(n) => n.get(),
|
||||
Err(_) => {
|
||||
eprintln!("Warning: Could not determine number of CPUs, defaulting to 1");
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user