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
|
2020-03-19 01:37:10 +01:00
|
|
|
//! each new client connection (each in its own thread).use super::{message::Message, receiver::Receiver}
|
2020-03-25 22:50:32 +01:00
|
|
|
use super::receiver::Receiver;
|
2020-03-19 01:37:10 +01:00
|
|
|
use crate::{
|
|
|
|
config,
|
2020-03-31 00:54:00 +02:00
|
|
|
err::RedisParseErr,
|
2020-03-25 22:50:32 +01:00
|
|
|
messages::Event,
|
2020-03-27 17:00:48 +01:00
|
|
|
parse_client_request::{Stream::Public, Subscription, Timeline},
|
2020-03-19 01:37:10 +01:00
|
|
|
};
|
|
|
|
use futures::{
|
|
|
|
Async::{self, NotReady, Ready},
|
|
|
|
Poll,
|
|
|
|
};
|
2020-03-27 17:00:48 +01:00
|
|
|
use std::sync::{Arc, Mutex};
|
2019-07-06 02:08:50 +02:00
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
/// Struct for managing all Redis streams.
|
2019-10-04 00:02:23 +02:00
|
|
|
#[derive(Clone, Debug)]
|
2019-07-08 13:31:42 +02:00
|
|
|
pub struct ClientAgent {
|
2020-03-27 17:00:48 +01:00
|
|
|
receiver: Arc<Mutex<Receiver>>,
|
|
|
|
id: Uuid,
|
2020-03-20 01:54:23 +01:00
|
|
|
pub subscription: Subscription,
|
2019-07-06 02:08:50 +02:00
|
|
|
}
|
|
|
|
|
2019-07-08 13:31:42 +02:00
|
|
|
impl ClientAgent {
|
|
|
|
/// Create a new `ClientAgent` with no shared data.
|
2020-03-27 17:00:48 +01:00
|
|
|
pub fn blank(redis_cfg: config::RedisConfig) -> Self {
|
2019-07-08 13:31:42 +02:00
|
|
|
ClientAgent {
|
2020-03-27 17:00:48 +01:00
|
|
|
receiver: Arc::new(Mutex::new(Receiver::new(redis_cfg))),
|
2019-07-06 02:08:50 +02:00
|
|
|
id: Uuid::default(),
|
2020-03-19 01:37:10 +01:00
|
|
|
subscription: Subscription::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,
|
2020-03-19 01:37:10 +01:00
|
|
|
subscription: self.subscription.clone(),
|
2019-07-08 13:31:42 +02:00
|
|
|
}
|
|
|
|
}
|
2020-03-19 01:37:10 +01:00
|
|
|
|
|
|
|
/// Initializes the `ClientAgent` with a unique ID associated with a specific user's
|
|
|
|
/// subscription. 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.
|
2020-03-19 01:37:10 +01:00
|
|
|
pub fn init_for_user(&mut self, subscription: Subscription) {
|
2020-03-25 22:50:32 +01:00
|
|
|
use std::time::Instant;
|
2019-07-08 13:31:42 +02:00
|
|
|
self.id = Uuid::new_v4();
|
2020-03-19 01:37:10 +01:00
|
|
|
self.subscription = subscription;
|
2020-03-25 22:50:32 +01:00
|
|
|
let start_time = Instant::now();
|
2019-07-06 02:08:50 +02:00
|
|
|
let mut receiver = self.receiver.lock().expect("No thread panic (stream.rs)");
|
2020-03-27 17:00:48 +01:00
|
|
|
receiver.manage_new_timeline(
|
|
|
|
self.id,
|
|
|
|
self.subscription.timeline,
|
|
|
|
self.subscription.hashtag_name.clone(),
|
|
|
|
);
|
2020-03-25 22:50:32 +01:00
|
|
|
log::info!("init_for_user had lock for: {:?}", start_time.elapsed());
|
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 {
|
2020-03-25 22:50:32 +01:00
|
|
|
type Item = Event;
|
2020-03-31 00:54:00 +02:00
|
|
|
type Error = RedisParseErr;
|
2019-07-06 02:08:50 +02:00
|
|
|
|
|
|
|
/// 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 result = {
|
|
|
|
let mut receiver = self
|
|
|
|
.receiver
|
|
|
|
.lock()
|
2019-07-08 13:31:42 +02:00
|
|
|
.expect("ClientAgent: No other thread panic");
|
2020-03-19 01:37:10 +01:00
|
|
|
receiver.configure_for_polling(self.id, self.subscription.timeline);
|
2019-09-28 23:57:37 +02:00
|
|
|
receiver.poll()
|
|
|
|
};
|
2019-07-08 13:31:42 +02:00
|
|
|
|
2020-03-19 01:37:10 +01:00
|
|
|
let allowed_langs = &self.subscription.allowed_langs;
|
|
|
|
let blocked_users = &self.subscription.blocks.blocked_users;
|
|
|
|
let blocking_users = &self.subscription.blocks.blocking_users;
|
|
|
|
let blocked_domains = &self.subscription.blocks.blocked_domains;
|
|
|
|
let (send, block) = (|msg| Ok(Ready(Some(msg))), Ok(NotReady));
|
2020-03-25 22:50:32 +01:00
|
|
|
use Event::*;
|
2019-07-08 13:31:42 +02:00
|
|
|
match result {
|
2020-03-25 22:50:32 +01:00
|
|
|
Ok(Async::Ready(Some(event))) => match event {
|
|
|
|
Update {
|
|
|
|
payload: status, ..
|
|
|
|
} => match self.subscription.timeline {
|
2020-03-20 00:40:01 +01:00
|
|
|
_ if status.involves_blocked_user(blocked_users) => block,
|
|
|
|
_ if status.from_blocked_domain(blocked_domains) => block,
|
|
|
|
_ if status.from_blocking_user(blocking_users) => block,
|
|
|
|
Timeline(Public, _, _) if status.language_not_allowed(allowed_langs) => block,
|
2020-03-25 22:50:32 +01:00
|
|
|
_ => send(Update {
|
|
|
|
payload: status,
|
|
|
|
queued_at: None,
|
|
|
|
}),
|
2020-03-20 00:40:01 +01:00
|
|
|
},
|
2020-03-25 22:50:32 +01:00
|
|
|
Notification { .. }
|
|
|
|
| Conversation { .. }
|
|
|
|
| Delete { .. }
|
|
|
|
| FiltersChanged
|
|
|
|
| Announcement { .. }
|
|
|
|
| AnnouncementReaction { .. }
|
|
|
|
| AnnouncementDelete { .. } => send(event),
|
2020-03-19 01:37:10 +01:00
|
|
|
},
|
|
|
|
Ok(Ready(None)) => Ok(Ready(None)),
|
|
|
|
Ok(NotReady) => Ok(NotReady),
|
2019-07-06 02:08:50 +02:00
|
|
|
Err(e) => Err(e),
|
2019-07-08 13:31:42 +02:00
|
|
|
}
|
2019-07-06 02:08:50 +02:00
|
|
|
}
|
|
|
|
}
|