feat: base project structure

This commit is contained in:
Ahmet Kaan Gümüş 2025-05-14 23:43:35 +03:00
parent e2043f1009
commit 1567a9c32a
15 changed files with 5568 additions and 1 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

5277
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

12
Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[workspace]
resolver = "3"
members = [
"client", "protocol", "server",
]
[workspace.dependencies]
chrono = { version = "0.4.41", features = ["serde"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
tokio = { version = "1.45.0", features = ["full"] }
s2n-quic = "1.58.0"

View file

@ -1,2 +1,2 @@
# tcid
# rust_communication

14
client/Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "client"
version = "0.1.0"
edition = "2024"
[dependencies]
protocol = { path = "../protocol" }
serde = { workspace = true }
serde_json = { workspace = true }
chrono = { workspace = true }
tokio = { workspace = true }
s2n-quic = { workspace = true }
iced = { features = ["tokio"], git = "https://github.com/iced-rs/iced", rev = "d39022432c778a8cda455f40b9c12245db86ce45" }
cpal = "0.15.3"

42
client/src/gui.rs Normal file
View file

@ -0,0 +1,42 @@
use iced::{
Alignment::Center,
Element, Task, Theme,
widget::{button, column, text},
};
use crate::{ClientConfig, stream::connect};
#[derive(Debug, Clone)]
pub enum Message {
JoinRoom,
LeaveRoom,
MuteMicrophone,
UnmuteMicrophone,
}
#[derive(Debug, Default)]
pub struct Data {
client_config: Option<ClientConfig>,
}
impl Data {
pub fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::JoinRoom => Task::none(),
Message::LeaveRoom => Task::none(),
Message::MuteMicrophone => Task::none(),
Message::UnmuteMicrophone => Task::none(),
}
}
pub fn view(&self) -> Element<'_, Message> {
column![button("Join Room"), text("Hello").size(50)]
.padding(10)
.align_x(Center)
.into()
}
pub fn theme(&self) -> Theme {
Theme::Dark
}
}

8
client/src/lib.rs Normal file
View file

@ -0,0 +1,8 @@
pub mod gui;
pub mod stream;
pub mod voice;
#[derive(Debug)]
struct ClientConfig {
server_address: String,
}

10
client/src/main.rs Normal file
View file

@ -0,0 +1,10 @@
use client::gui::Data;
fn main() {
println!("Hello, world!");
iced::application(Data::default, Data::update, Data::view)
.theme(Data::theme)
.run()
.unwrap()
}

65
client/src/stream.rs Normal file
View file

@ -0,0 +1,65 @@
use std::{io, net::SocketAddr, path::Path};
use protocol::BUFFER_LENGTH;
use s2n_quic::{Client, client::Connect};
use tokio::{
io::AsyncReadExt,
sync::{broadcast, oneshot},
};
use crate::{
ClientConfig,
voice::{play, record},
};
pub async fn connect(client_config: &ClientConfig) {
let client = Client::builder()
.with_io("0:0")
.unwrap()
.with_tls(Path::new("certificates/cert.pem"))
.unwrap()
.start()
.unwrap();
println!("Client Address = {}", client.local_addr().unwrap());
let connect = Connect::new(client_config.server_address.parse::<SocketAddr>().unwrap())
.with_server_name("localhost");
let mut connection = match client.connect(connect).await {
Ok(connection) => connection,
Err(err_val) => {
eprintln!("Error: Client Connection | {}", err_val);
return;
}
};
connection.keep_alive(true).unwrap();
let stream = connection.open_bidirectional_stream().await.unwrap();
let (mut receive_stream, mut send_stream) = stream.split();
let (microphone_sender, mut microphone_receiver) = broadcast::channel::<f32>(BUFFER_LENGTH);
let (speaker_sender, speaker_receiver) = broadcast::channel::<f32>(BUFFER_LENGTH);
let (microphone_stop_signal_sender, microphone_stop_signal_receiver) =
oneshot::channel::<bool>();
let (spearker_stop_signal_sender, speaker_stop_signal_receiver) = oneshot::channel::<bool>();
tokio::spawn(play(speaker_receiver, speaker_stop_signal_receiver));
tokio::spawn(record(microphone_sender, microphone_stop_signal_receiver));
tokio::spawn(async move {
while let Ok(data) = receive_stream.read_f32_le().await {
speaker_sender.send(data).unwrap();
}
});
tokio::spawn(async move {
while let Ok(data) = microphone_receiver.recv().await {
send_stream
.send(data.to_le_bytes().to_vec().into())
.await
.unwrap();
}
});
let mut read_buffer = String::default();
io::stdin().read_line(&mut read_buffer).unwrap();
microphone_stop_signal_sender.send(true).unwrap();
spearker_stop_signal_sender.send(true).unwrap();
}

72
client/src/voice.rs Normal file
View file

@ -0,0 +1,72 @@
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use tokio::sync::{broadcast, oneshot};
pub async fn record(
microphone_sender: broadcast::Sender<f32>,
stop_signal_receiver: oneshot::Receiver<bool>,
) {
let host = cpal::default_host();
let input_device = host.default_input_device().unwrap();
let config = input_device.default_input_config().unwrap().into();
let input = move |data: &[f32], _: &cpal::InputCallbackInfo| {
for &sample in data {
if microphone_sender.receiver_count() > 0 {
if let Err(err_val) = microphone_sender.send(sample) {
eprintln!("Error: Microphone Send | {}", err_val);
}
}
}
};
let input_stream = input_device
.build_input_stream(&config, input, voice_error, None)
.unwrap();
input_stream.play().unwrap();
println!("Recording Started");
tokio::task::block_in_place(|| {
let _ = stop_signal_receiver.blocking_recv();
});
input_stream.pause().unwrap();
}
pub async fn play(
mut speaker_receiver: broadcast::Receiver<f32>,
stop_signal_receiver: oneshot::Receiver<bool>,
) {
let host = cpal::default_host();
let output_device = host.default_output_device().unwrap();
let config = output_device.default_output_config().unwrap().into();
let output = move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
for sample in data {
if speaker_receiver.len() > 0 {
match speaker_receiver.blocking_recv() {
Ok(received_sample) => *sample = received_sample,
Err(err_val) => eprintln!("Error: Speaker Receive | {}", err_val),
}
} else {
*sample = 0.0;
}
}
};
let output_stream = output_device
.build_output_stream(&config, output, voice_error, None)
.unwrap();
output_stream.play().unwrap();
println!("Playing Started");
tokio::task::block_in_place(|| {
let _ = stop_signal_receiver.blocking_recv();
});
output_stream.pause().unwrap();
}
fn voice_error(err_val: cpal::StreamError) {
eprintln!("Error: Voice Error | {}", err_val);
}

8
protocol/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "protocol"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { workspace = true }
chrono = { workspace = true }

39
protocol/src/lib.rs Normal file
View file

@ -0,0 +1,39 @@
use std::{collections::VecDeque, fs::File, io::Read};
pub const BUFFER_LENGTH: usize = 1024;
#[derive(Debug, Clone)]
struct TOML {
header: String,
fields: VecDeque<String>,
}
fn naive_toml_parser(file_location: &str) -> TOML {
let mut toml_file = File::open(file_location).unwrap();
let mut toml_ingredients = String::default();
toml_file.read_to_string(&mut toml_ingredients).unwrap();
let mut toml_ingredients = toml_ingredients.lines().collect::<VecDeque<&str>>();
let header = toml_ingredients
.pop_front()
.unwrap()
.replace('[', "")
.replace(']', "")
.trim_end()
.to_string();
let fields = toml_ingredients
.iter()
.map(|ingredient| {
ingredient
.split_once('=')
.unwrap()
.1
.replace('"', "")
.trim()
.to_string()
})
.collect();
TOML { header, fields }
}

11
server/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "server"
version = "0.1.0"
edition = "2024"
[dependencies]
protocol = { path = "../protocol" }
serde = { workspace = true }
serde_json = { workspace = true }
chrono = { workspace = true }
tokio = { workspace = true }

4
server/src/lib.rs Normal file
View file

@ -0,0 +1,4 @@
#[derive(Debug)]
struct ServerConfig {
server_address: String,
}

4
server/src/main.rs Normal file
View file

@ -0,0 +1,4 @@
#[tokio::main]
async fn main() {
println!("Hello, world!");
}