Last active
August 29, 2015 14:11
-
-
Save timonv/9e938230721de6acf0ad to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::io::{TcpStream, BufferedWriter}; | |
use std::io::Stream; | |
use std::sync::{RWLock, Arc}; | |
use std::collections::HashMap; | |
pub struct ChatServer<T: 'static + Reader + Writer> { | |
users: Arc<RWLock<HashMap<String, T>>> | |
} | |
// Generics are kinda viral... | |
impl<T: Reader + Writer + Clone> ChatServer<T> { | |
pub fn new() -> ChatServer<T> { | |
ChatServer { users: Arc::new(RWLock::new(HashMap::<String, T>::new())) } | |
} | |
pub fn register_user(&self, name: String, stream: T) { | |
let mut streams = self.users.write(); | |
streams.insert(name, stream); | |
streams.downgrade(); | |
} | |
pub fn is_registered(&self, name: &str) -> bool { | |
// TODO Check if stream is still valid | |
let streams = self.users.read(); | |
streams.contains_key(name) | |
} | |
// RFC Maybe better to return the senders of the channel here? | |
pub fn listen_and_broadcast(&self, rec: Receiver<(String, String)>) { | |
let clone = self.clone(); | |
spawn(proc() { | |
loop { | |
let (user, message) = rec.recv(); | |
clone.broadcast(user, message); | |
} | |
}) | |
} | |
fn broadcast(&self, origin: String, message: String) { | |
let message = self.format_message(&origin, &message); | |
let users = self.users.clone(); | |
let unlocked = users.read(); | |
for (user, stream) in unlocked.iter() { | |
if *user != origin { // Why? | |
let mut stream = stream.clone(); | |
let mut writer = BufferedWriter::new(stream.clone()); | |
writer.write_line(message.as_slice()).unwrap(); | |
writer.flush().unwrap(); | |
} | |
} | |
} | |
fn format_message(&self, name: &String, message: &String) -> String { | |
format!("[{}]: {}", name, message.trim()) | |
} | |
} | |
// TODO Remove need for this by doing everything over spawns with channels | |
impl<T: Reader + Writer>Clone for ChatServer<T> { | |
fn clone(&self) -> ChatServer<T> { | |
ChatServer { users: self.users.clone() } | |
} | |
} | |
#[cfg(test)] | |
mod test { | |
use super::ChatServer; | |
use std::io::IoResult; | |
use std::sync::{RWLock, Arc}; | |
use std::str; | |
#[test] | |
fn test_registering_of_user() { | |
let server = ChatServer::new(); | |
let stream = FakeStream::new(); | |
server.register_user("Pietje".to_string(), stream); | |
assert!(server.is_registered("Pietje")) | |
} | |
#[test] | |
fn test_broadcasting_a_message() { | |
let server = ChatServer::new(); | |
let pietje = FakeStream::new(); | |
server.register_user("Pietje".to_string(), pietje.clone()); | |
let message = "Baby don't hurt me, don't hurt me, no more!"; | |
let (tx, rx) = sync_channel::<(String, String)>(0); | |
server.listen_and_broadcast(rx); | |
tx.send(("Jantje".to_string(), message.to_string())); | |
let expected = "[Jantje]: ".to_string() + message.to_string(); | |
assert_eq!(expected.as_slice(), pietje.read_output().trim()); | |
} | |
struct FakeStream { | |
input: String, | |
output: Arc<RWLock<String>> | |
} | |
impl FakeStream { | |
pub fn new() -> FakeStream { | |
// Because when we clone, we need it to reference the same output stream (as a TcpConnection | |
// would) | |
FakeStream { input: String::new(), output: Arc::new(RWLock::new(String::new()))} | |
} | |
pub fn read_output(&self) -> &str { | |
println!("{}", "Reading!"); | |
let output = self.output.read(); | |
output.as_slice().clone() | |
} | |
} | |
impl Reader for FakeStream { | |
fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> { | |
let mut len = 0; | |
// THIS IS GENIUS | |
for (slot, byte) in buf.iter_mut().zip(self.input.as_bytes().iter()) { | |
*slot = *byte; | |
len += 1; | |
} | |
Ok(len) | |
} | |
} | |
impl Writer for FakeStream { | |
fn write(&mut self, buf: &[u8]) -> IoResult<()> { | |
let mut unlocked = self.output.write(); | |
*unlocked = str::from_utf8(buf).expect("Test your shizzle, pizzle").to_string(); | |
println!("{}", "Writing!"); | |
println!("{}", *unlocked); | |
unlocked.downgrade(); | |
Ok(()) | |
} | |
} | |
impl Clone for FakeStream { | |
fn clone(&self) -> FakeStream { | |
FakeStream { input: self.input.clone(), output: self.output.clone() } | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment