#![deny(clippy::nursery, clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
use std::collections::HashMap;
use actix::{io::FramedWrite, Actor, Addr, AsyncContext};
use actix_rt::System;
use clap::Parser;
use irc_proto::IrcCodec;
use tokio::{net::TcpListener, time::Instant};
use tokio_util::codec::FramedRead;
use tracing::{error, info, info_span, Instrument};
use tracing_subscriber::EnvFilter;
use crate::{client::Client, config::Args, messages::UserConnected, server::Server};
pub mod channel;
pub mod client;
pub mod config;
pub mod connection;
pub mod messages;
pub mod server;
pub const SERVER_NAME: &str = "my.cool.server";
#[actix_rt::main]
async fn main() -> anyhow::Result<()> {
let opts: Args = Args::parse();
std::env::set_var(
"RUST_LOG",
match opts.verbose {
1 => "debug",
2 => "trace",
_ => "info",
},
);
let subscriber = tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.pretty();
subscriber.init();
let listen_address = opts.config.listen_address;
let server = Server {
channels: HashMap::default(),
clients: HashMap::default(),
config: opts.config,
}
.start();
let listener = TcpListener::bind(listen_address).await?;
actix_rt::spawn(start_tcp_acceptor_loop(listener, server));
info!("Server listening on {}", listen_address);
tokio::signal::ctrl_c().await?;
System::current().stop();
Ok(())
}
async fn start_tcp_acceptor_loop(listener: TcpListener, server: Addr<Server>) {
while let Ok((stream, addr)) = listener.accept().await {
let span = info_span!("connection", %addr);
let _entered = span.clone().entered();
info!("Accepted connection");
let server = server.clone();
actix_rt::spawn(async move {
let (read, writer) = tokio::io::split(stream);
let mut read = FramedRead::new(read, IrcCodec::new("utf8").unwrap());
let Some(connection) = connection::negotiate_client_connection(&mut read).await.unwrap() else {
error!("Failed to fully handshake with client, dropping connection");
return;
};
let handle = Client::create(|ctx| {
let writer = FramedWrite::new(writer, IrcCodec::new("utf8").unwrap(), ctx);
ctx.add_stream(read);
Client {
writer,
connection: connection.clone(),
server: server.clone(),
channels: HashMap::new(),
last_active: Instant::now(),
graceful_shutdown: false,
server_leave_reason: None,
span: span.clone(),
}
});
server.do_send(UserConnected { handle, connection, span });
}.instrument(info_span!("negotiation")));
}
}