first commit
This commit is contained in:
commit
087aa89e45
1013
Cargo.lock
generated
Normal file
1013
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
||||
# Cargo.toml в корне проекта
|
||||
[workspace]
|
||||
members = [
|
||||
"futriix-server",
|
||||
"futriix-cli",
|
||||
]
|
13
futriix-cli/Cargo.toml
Normal file
13
futriix-cli/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "futriix-cli"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
rmp-serde = "0.15"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
toml = "0.7"
|
||||
colored = "2.0"
|
||||
rustyline = "10.0"
|
161
futriix-cli/src/main.rs
Normal file
161
futriix-cli/src/main.rs
Normal file
@ -0,0 +1,161 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use std::error::Error;
|
||||
use tokio::net::TcpStream;
|
||||
use colored::Colorize;
|
||||
use rustyline::{Editor, error::ReadlineError};
|
||||
use std::fs;
|
||||
use toml::Value;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum Command {
|
||||
Insert { key: String, value: serde_json::Value },
|
||||
Get { key: String },
|
||||
Update { key: String, value: serde_json::Value },
|
||||
Delete { key: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum Response {
|
||||
Success(Option<serde_json::Value>),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
async fn send_command(cmd: Command) -> Result<Response, Box<dyn Error>> {
|
||||
let config_content = fs::read_to_string("../futriix.config.toml")?;
|
||||
let config: Value = toml::from_str(&config_content)?;
|
||||
|
||||
let ip = config["client"]["ip"].as_str().unwrap_or("127.0.0.1");
|
||||
let port = config["client"]["port"].as_integer().unwrap_or(8080) as u16;
|
||||
let addr = format!("{}:{}", ip, port);
|
||||
|
||||
let mut stream = TcpStream::connect(&addr).await?;
|
||||
|
||||
let cmd_bytes = rmp_serde::to_vec(&cmd)?;
|
||||
|
||||
// Send command length (4 bytes)
|
||||
let len = cmd_bytes.len() as u32;
|
||||
stream.write_all(&len.to_be_bytes()).await?;
|
||||
|
||||
// Send command
|
||||
stream.write_all(&cmd_bytes).await?;
|
||||
|
||||
// Read response length
|
||||
let mut len_buf = [0u8; 4];
|
||||
stream.read_exact(&mut len_buf).await?;
|
||||
let len = u32::from_be_bytes(len_buf) as usize;
|
||||
|
||||
// Read response
|
||||
let mut buf = vec![0u8; len];
|
||||
stream.read_exact(&mut buf).await?;
|
||||
|
||||
let response = rmp_serde::from_slice(&buf)?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("{}", "Futriix CLI Client".bright_blue());
|
||||
println!("Type 'help' for available commands");
|
||||
|
||||
let mut rl = Editor::<()>::new()?;
|
||||
if rl.load_history("futriix-cli-history.txt").is_err() {
|
||||
println!("No previous history.");
|
||||
}
|
||||
|
||||
loop {
|
||||
let readline = rl.readline("futriix> ");
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
rl.add_history_entry(&line);
|
||||
|
||||
if line.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||
match parts[0] {
|
||||
"insert" | "i" => {
|
||||
if parts.len() < 3 {
|
||||
println!("{}", "Usage: insert <key> <json_value>".red());
|
||||
continue;
|
||||
}
|
||||
let key = parts[1].to_string();
|
||||
let value_str = parts[2..].join(" ");
|
||||
match serde_json::from_str(&value_str) {
|
||||
Ok(value) => {
|
||||
match send_command(Command::Insert { key, value }).await {
|
||||
Ok(Response::Success(_)) => println!("Insert successful"),
|
||||
Ok(Response::Error(e)) => println!("{}: {}", "Error".red(), e),
|
||||
Err(e) => println!("{}: {}", "Connection error".red(), e),
|
||||
}
|
||||
},
|
||||
Err(e) => println!("{}: Invalid JSON: {}", "Error".red(), e),
|
||||
}
|
||||
},
|
||||
"get" | "g" => {
|
||||
if parts.len() != 2 {
|
||||
println!("{}", "Usage: get <key>".red());
|
||||
continue;
|
||||
}
|
||||
match send_command(Command::Get { key: parts[1].to_string() }).await {
|
||||
Ok(Response::Success(Some(value))) => println!("{}", serde_json::to_string_pretty(&value)?),
|
||||
Ok(Response::Success(None)) => println!("Key not found"),
|
||||
Ok(Response::Error(e)) => println!("{}: {}", "Error".red(), e),
|
||||
Err(e) => println!("{}: {}", "Connection error".red(), e),
|
||||
}
|
||||
},
|
||||
"update" | "u" => {
|
||||
if parts.len() < 3 {
|
||||
println!("{}", "Usage: update <key> <json_value>".red());
|
||||
continue;
|
||||
}
|
||||
let key = parts[1].to_string();
|
||||
let value_str = parts[2..].join(" ");
|
||||
match serde_json::from_str(&value_str) {
|
||||
Ok(value) => {
|
||||
match send_command(Command::Update { key, value }).await {
|
||||
Ok(Response::Success(_)) => println!("Update successful"),
|
||||
Ok(Response::Error(e)) => println!("{}: {}", "Error".red(), e),
|
||||
Err(e) => println!("{}: {}", "Connection error".red(), e),
|
||||
}
|
||||
},
|
||||
Err(e) => println!("{}: Invalid JSON: {}", "Error".red(), e),
|
||||
}
|
||||
},
|
||||
"delete" | "d" => {
|
||||
if parts.len() != 2 {
|
||||
println!("{}", "Usage: delete <key>".red());
|
||||
continue;
|
||||
}
|
||||
match send_command(Command::Delete { key: parts[1].to_string() }).await {
|
||||
Ok(Response::Success(_)) => println!("Delete successful"),
|
||||
Ok(Response::Error(e)) => println!("{}: {}", "Error".red(), e),
|
||||
Err(e) => println!("{}: {}", "Connection error".red(), e),
|
||||
}
|
||||
},
|
||||
"help" | "h" => {
|
||||
println!("Available commands:");
|
||||
println!(" insert|i <key> <json_value> - Insert document");
|
||||
println!(" get|g <key> - Get document");
|
||||
println!(" update|u <key> <json_value> - Update document");
|
||||
println!(" delete|d <key> - Delete document");
|
||||
println!(" help|h - Show this help");
|
||||
println!(" exit|quit|q - Exit the client");
|
||||
},
|
||||
"exit" | "quit" | "q" => break,
|
||||
_ => println!("{}: Unknown command. Type 'help' for available commands.", "Error".red()),
|
||||
}
|
||||
},
|
||||
Err(ReadlineError::Interrupted) => break,
|
||||
Err(ReadlineError::Eof) => break,
|
||||
Err(err) => {
|
||||
println!("{}: {:?}", "Error".red(), err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rl.save_history("futriix-cli-history.txt")?;
|
||||
Ok(())
|
||||
}
|
16
futriix-server/Cargo.toml
Normal file
16
futriix-server/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "futriix-server"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
rmp-serde = "0.15" # MessagePack
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
toml = "0.7"
|
||||
colored = "2.0"
|
||||
log = "0.4"
|
||||
simplelog = "0.12"
|
||||
crossbeam = "0.8"
|
||||
parking_lot = "0.12"
|
40
futriix-server/src/main.rs
Normal file
40
futriix-server/src/main.rs
Normal file
@ -0,0 +1,40 @@
|
||||
mod server;
|
||||
|
||||
use server::*;
|
||||
use std::fs;
|
||||
use toml::Value;
|
||||
use simplelog::*;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Read config
|
||||
let config_content = fs::read_to_string("futriix.config.toml")?;
|
||||
let config: Value = toml::from_str(&config_content)?;
|
||||
|
||||
let ip = config["server"]["ip"].as_str().unwrap_or("127.0.0.1");
|
||||
let port = config["server"]["port"].as_integer().unwrap_or(8080) as u16;
|
||||
let log_path = config["server"]["log_path"].as_str().unwrap_or("futriix.log");
|
||||
|
||||
// Initialize logger
|
||||
let log_file = fs::File::create(log_path)?;
|
||||
CombinedLogger::init(vec![
|
||||
TermLogger::new(
|
||||
LevelFilter::Info,
|
||||
Config::default(),
|
||||
TerminalMode::Mixed,
|
||||
ColorChoice::Auto,
|
||||
),
|
||||
WriteLogger::new(
|
||||
LevelFilter::Info,
|
||||
Config::default(),
|
||||
log_file,
|
||||
),
|
||||
])?;
|
||||
|
||||
// Start server
|
||||
let server = FutriixServer::new();
|
||||
let addr = format!("{}:{}", ip, port);
|
||||
server.run(&addr).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
123
futriix-server/src/server.rs
Normal file
123
futriix-server/src/server.rs
Normal file
@ -0,0 +1,123 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use parking_lot::RwLock;
|
||||
use crossbeam::queue::SegQueue;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use log::{info, error};
|
||||
use colored::Colorize;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum Command {
|
||||
Insert { key: String, value: serde_json::Value },
|
||||
Get { key: String },
|
||||
Update { key: String, value: serde_json::Value },
|
||||
Delete { key: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum Response {
|
||||
Success(Option<serde_json::Value>),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
pub struct FutriixServer {
|
||||
db: Arc<RwLock<HashMap<String, serde_json::Value>>>,
|
||||
command_queue: Arc<SegQueue<Command>>,
|
||||
}
|
||||
|
||||
impl FutriixServer {
|
||||
pub fn new() -> Self {
|
||||
FutriixServer {
|
||||
db: Arc::new(RwLock::new(HashMap::new())),
|
||||
command_queue: Arc::new(SegQueue::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&self, addr: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
|
||||
info!("{}", "Futriix Server started successfully!".bright_blue());
|
||||
info!("Listening on: {}", addr);
|
||||
|
||||
loop {
|
||||
let (socket, _) = listener.accept().await?;
|
||||
let db = self.db.clone();
|
||||
let queue = self.command_queue.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = Self::handle_connection(socket, db, queue).await {
|
||||
error!("Connection error: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_connection(
|
||||
mut socket: TcpStream,
|
||||
db: Arc<RwLock<HashMap<String, serde_json::Value>>>,
|
||||
queue: Arc<SegQueue<Command>>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
loop {
|
||||
// Read command length
|
||||
let mut len_buf = [0u8; 4];
|
||||
if let Err(e) = socket.read_exact(&mut len_buf).await {
|
||||
if e.kind() == std::io::ErrorKind::UnexpectedEof {
|
||||
break; // Client disconnected
|
||||
}
|
||||
return Err(e.into());
|
||||
}
|
||||
let len = u32::from_be_bytes(len_buf) as usize;
|
||||
|
||||
// Read command
|
||||
let mut buf = vec![0u8; len];
|
||||
socket.read_exact(&mut buf).await?;
|
||||
|
||||
let cmd: Command = rmp_serde::from_slice(&buf)?;
|
||||
queue.push(cmd);
|
||||
|
||||
while let Some(cmd) = queue.pop() {
|
||||
let response = match cmd {
|
||||
Command::Insert { key, value } => {
|
||||
let mut db = db.write();
|
||||
db.insert(key, value);
|
||||
Response::Success(None)
|
||||
}
|
||||
Command::Get { key } => {
|
||||
let db = db.read();
|
||||
match db.get(&key) {
|
||||
Some(value) => Response::Success(Some(value.clone())),
|
||||
None => Response::Error("Key not found".to_string()),
|
||||
}
|
||||
}
|
||||
Command::Update { key, value } => {
|
||||
let mut db = db.write();
|
||||
if db.contains_key(&key) {
|
||||
db.insert(key, value);
|
||||
Response::Success(None)
|
||||
} else {
|
||||
Response::Error("Key not found".to_string())
|
||||
}
|
||||
}
|
||||
Command::Delete { key } => {
|
||||
let mut db = db.write();
|
||||
if db.remove(&key).is_some() {
|
||||
Response::Success(None)
|
||||
} else {
|
||||
Response::Error("Key not found".to_string())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let response_bytes = rmp_serde::to_vec(&response)?;
|
||||
// Send response length
|
||||
let len = response_bytes.len() as u32;
|
||||
socket.write_all(&len.to_be_bytes()).await?;
|
||||
// Send response
|
||||
socket.write_all(&response_bytes).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
8
futriix.config.toml
Normal file
8
futriix.config.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[server]
|
||||
ip = "127.0.0.1"
|
||||
port = 8080
|
||||
log_path = "futriix.log"
|
||||
|
||||
[client]
|
||||
ip = "127.0.0.1"
|
||||
port = 8080
|
Loading…
x
Reference in New Issue
Block a user