2019-07-08 13:31:42 +02:00
//! Provides an interface between the `Warp` filters and the underlying
2019-07-10 02:13:37 +02:00
//! mechanics of talking with Redis/managing multiple threads.
2019-07-06 02:08:50 +02:00
//!
2019-07-08 13:31:42 +02:00
//! The `ClientAgent`'s interface is very simple. All you can do with it is:
//! * Create a totally new `ClientAgent` with no shared data;
//! * Clone an existing `ClientAgent`, sharing the `Receiver`;
2019-07-08 21:21:02 +02:00
//! * Manage an new timeline/user pair; or
2019-07-08 13:31:42 +02:00
//! * Poll an existing `ClientAgent` to see if there are any new messages
2019-07-06 02:08:50 +02:00
//! for clients
//!
2019-07-08 13:31:42 +02:00
//! When you poll the `ClientAgent`, it is responsible for polling internal data
2019-07-06 02:08:50 +02:00
//! structures, getting any updates from Redis, and then filtering out any updates
//! that should be excluded by relevant filters.
//!
//! Because `StreamManagers` are lightweight data structures that do not directly
2019-07-08 13:31:42 +02:00
//! communicate with Redis, it we create a new `ClientAgent` for
//! each new client connection (each in its own thread).
use super ::receiver ::Receiver ;
use crate ::parse_client_request ::user ::User ;
2019-07-06 02:08:50 +02:00
use futures ::{ Async , Poll } ;
2019-07-08 21:21:02 +02:00
use log ;
2019-07-06 02:08:50 +02:00
use serde_json ::{ json , Value } ;
2019-07-08 13:31:42 +02:00
use std ::{ sync , time } ;
2019-07-06 02:08:50 +02:00
use tokio ::io ::Error ;
use uuid ::Uuid ;
/// Struct for managing all Redis streams.
#[ derive(Clone, Default, Debug) ]
2019-07-08 13:31:42 +02:00
pub struct ClientAgent {
2019-07-06 02:08:50 +02:00
receiver : sync ::Arc < sync ::Mutex < Receiver > > ,
id : uuid ::Uuid ,
target_timeline : String ,
current_user : User ,
}
2019-07-08 13:31:42 +02:00
impl ClientAgent {
/// Create a new `ClientAgent` with no shared data.
pub fn blank ( ) -> Self {
ClientAgent {
2019-07-06 02:08:50 +02:00
receiver : sync ::Arc ::new ( sync ::Mutex ::new ( Receiver ::new ( ) ) ) ,
id : Uuid ::default ( ) ,
target_timeline : String ::new ( ) ,
2019-09-09 19:06:24 +02:00
current_user : User ::default ( ) ,
2019-07-06 02:08:50 +02:00
}
}
2019-07-08 13:31:42 +02:00
/// Clones the `ClientAgent`, sharing the `Receiver`.
pub fn clone_with_shared_receiver ( & self ) -> Self {
Self {
receiver : self . receiver . clone ( ) ,
id : self . id ,
target_timeline : self . target_timeline . clone ( ) ,
current_user : self . current_user . clone ( ) ,
}
}
/// Initializes the `ClientAgent` with a unique ID, a `User`, and the target timeline.
/// Also passes values to the `Receiver` for it's initialization.
2019-07-06 02:08:50 +02:00
///
/// Note that this *may or may not* result in a new Redis connection.
/// If the server has already subscribed to the timeline on behalf of
2019-07-08 13:31:42 +02:00
/// a different user, the `Receiver` is responsible for figuring
2019-07-06 02:08:50 +02:00
/// that out and avoiding duplicated connections. Thus, it is safe to
/// use this method for each new client connection.
2019-09-09 19:06:24 +02:00
pub fn init_for_user ( & mut self , user : User ) {
2019-07-08 13:31:42 +02:00
self . id = Uuid ::new_v4 ( ) ;
2019-09-09 19:06:24 +02:00
self . target_timeline = user . target_timeline . to_owned ( ) ;
2019-07-08 13:31:42 +02:00
self . current_user = user ;
2019-07-06 02:08:50 +02:00
let mut receiver = self . receiver . lock ( ) . expect ( " No thread panic (stream.rs) " ) ;
2019-09-09 19:06:24 +02:00
receiver . manage_new_timeline ( self . id , & self . target_timeline ) ;
2019-07-06 02:08:50 +02:00
}
}
2019-07-08 13:31:42 +02:00
/// The stream that the `ClientAgent` manages. `Poll` is the only method implemented.
impl futures ::stream ::Stream for ClientAgent {
2019-07-06 02:08:50 +02:00
type Item = Value ;
type Error = Error ;
/// Checks for any new messages that should be sent to the client.
///
2019-07-08 13:31:42 +02:00
/// The `ClientAgent` polls the `Receiver` and replies
/// with `Ok(Ready(Some(Value)))` if there is a new message to send to
2019-07-06 02:08:50 +02:00
/// the client. If there is no new message or if the new message should be
2019-07-08 13:31:42 +02:00
/// filtered out based on one of the user's filters, then the `ClientAgent`
/// replies with `Ok(NotReady)`. The `ClientAgent` bubles up any
2019-07-06 02:08:50 +02:00
/// errors from the underlying data structures.
fn poll ( & mut self ) -> Poll < Option < Self ::Item > , Self ::Error > {
let start_time = time ::Instant ::now ( ) ;
let result = {
2019-09-11 23:28:27 +02:00
let before_locking_receiver = time ::Instant ::now ( ) ;
2019-07-06 02:08:50 +02:00
let mut receiver = self
. receiver
. lock ( )
2019-07-08 13:31:42 +02:00
. expect ( " ClientAgent: No other thread panic " ) ;
2019-09-11 23:28:27 +02:00
let before_configuring_receiver = time ::Instant ::now ( ) ;
2019-07-06 02:08:50 +02:00
receiver . configure_for_polling ( self . id , & self . target_timeline . clone ( ) ) ;
2019-09-11 23:28:27 +02:00
let before_polling_receiver = time ::Instant ::now ( ) ;
let result = receiver . poll ( ) ;
if start_time . elapsed ( ) > time ::Duration ::from_millis ( 20 ) {
log ::warn! ( " Polling TOTAL time: {:?} \n since poll function: {:?} \n since configuring: {:?} \n since locking: {:?} " , start_time . elapsed ( ) , before_polling_receiver . elapsed ( ) , before_configuring_receiver . elapsed ( ) , before_locking_receiver . elapsed ( ) ) ;
}
result
2019-07-06 02:08:50 +02:00
} ;
2019-07-08 13:31:42 +02:00
match result {
2019-07-06 02:08:50 +02:00
Ok ( Async ::Ready ( Some ( value ) ) ) = > {
2019-07-08 13:31:42 +02:00
let user = & self . current_user ;
2019-07-06 02:08:50 +02:00
let toot = Toot ::from_json ( value ) ;
2019-07-08 13:31:42 +02:00
toot . filter ( & user )
2019-07-06 02:08:50 +02:00
}
Ok ( inner_value ) = > Ok ( inner_value ) ,
Err ( e ) = > Err ( e ) ,
2019-07-08 13:31:42 +02:00
}
2019-07-06 02:08:50 +02:00
}
}
2019-07-08 13:31:42 +02:00
/// The message to send to the client (which might not literally be a toot in some cases).
2019-07-06 02:08:50 +02:00
struct Toot {
category : String ,
payload : String ,
2019-07-11 05:44:04 +02:00
language : Option < String > ,
2019-07-06 02:08:50 +02:00
}
2019-07-08 13:31:42 +02:00
2019-07-06 02:08:50 +02:00
impl Toot {
2019-07-08 13:31:42 +02:00
/// Construct a `Toot` from well-formed JSON.
2019-07-06 02:08:50 +02:00
fn from_json ( value : Value ) -> Self {
2019-07-11 05:44:04 +02:00
let category = value [ " event " ] . as_str ( ) . expect ( " Redis string " ) . to_owned ( ) ;
let language = if category = = " update " {
Some ( value [ " payload " ] [ " language " ] . to_string ( ) )
} else {
None
} ;
2019-07-06 02:08:50 +02:00
Self {
2019-07-11 05:44:04 +02:00
category ,
2019-07-06 02:08:50 +02:00
payload : value [ " payload " ] . to_string ( ) ,
2019-07-11 05:44:04 +02:00
language ,
2019-07-06 02:08:50 +02:00
}
}
2019-07-08 13:31:42 +02:00
/// Convert a `Toot` to JSON inside an Option.
2019-07-06 02:08:50 +02:00
fn to_optional_json ( & self ) -> Option < Value > {
Some ( json! (
{ " event " : self . category ,
" payload " : self . payload , }
) )
}
2019-07-08 13:31:42 +02:00
/// Filter out any `Toot`'s that fail the provided filter.
fn filter ( & self , user : & User ) -> Result < Async < Option < Value > > , Error > {
2019-07-06 02:08:50 +02:00
let toot = self ;
let ( send_msg , skip_msg ) = (
Ok ( Async ::Ready ( toot . to_optional_json ( ) ) ) ,
Ok ( Async ::NotReady ) ,
) ;
2019-07-11 05:44:04 +02:00
if toot . category = = " update " {
use crate ::parse_client_request ::user ::Filter ;
let toot_language = & toot . language . clone ( ) . expect ( " Valid lanugage " ) ;
match & user . filter {
Filter ::NoFilter = > send_msg ,
Filter ::Notification if toot . category = = " notification " = > send_msg ,
// If not, skip it
Filter ::Notification = > skip_msg ,
Filter ::Language if user . langs . is_none ( ) = > send_msg ,
Filter ::Language if user . langs . clone ( ) . expect ( " " ) . contains ( toot_language ) = > {
send_msg
}
// If not, skip it
Filter ::Language = > skip_msg ,
}
} else {
send_msg
2019-07-06 02:08:50 +02:00
}
}
}