use std::collections::{HashMap, HashSet};
use std::future::IntoFuture;
use nostr::event::EventId;
use nostr::filter::Filter;
use nostr::types::{RelayUrl, Timestamp};
use super::output::Output;
use crate::client::url::RelayUrlArg;
use crate::client::{Client, Error};
use crate::future::BoxedFuture;
use crate::relay::{RelayCapabilities, SyncOptions, SyncSummary as RelaySyncSummary};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SyncSummary {
pub local: HashSet<EventId>,
pub remote: HashMap<EventId, HashSet<RelayUrl>>,
pub sent: HashMap<EventId, HashSet<RelayUrl>>,
pub received: HashMap<EventId, HashSet<RelayUrl>>,
pub send_failures: HashMap<RelayUrl, HashMap<EventId, String>>,
}
impl SyncSummary {
pub(crate) fn merge_relay_summary(&mut self, url: RelayUrl, other: RelaySyncSummary) {
self.local.extend(other.local);
for event_id in other.remote {
self.remote.entry(event_id).or_default().insert(url.clone());
}
for event_id in other.sent {
self.sent.entry(event_id).or_default().insert(url.clone());
}
for event_id in other.received {
self.received
.entry(event_id)
.or_default()
.insert(url.clone());
}
self.send_failures
.entry(url)
.or_default()
.extend(other.send_failures);
}
}
#[must_use = "Does nothing unless you await!"]
pub struct SyncEvents<'client, 'url> {
client: &'client Client,
filter: Filter,
with: Option<Vec<RelayUrlArg<'url>>>,
opts: SyncOptions,
}
impl<'client, 'url> SyncEvents<'client, 'url> {
#[inline]
pub(crate) fn new(client: &'client Client, filter: Filter) -> Self {
Self {
client,
filter,
with: None,
opts: SyncOptions::new(),
}
}
pub fn with<I, U>(mut self, relays: I) -> Self
where
I: IntoIterator<Item = U>,
U: Into<RelayUrlArg<'url>>,
{
let mut list: Vec<RelayUrlArg<'url>> = self.with.unwrap_or_default();
list.extend(relays.into_iter().map(Into::into));
self.with = Some(list);
self
}
#[inline]
pub fn opts(mut self, opts: SyncOptions) -> Self {
self.opts = opts;
self
}
}
fn construct_filters<'url, I, T>(
urls: I,
filter: Filter,
) -> Result<HashMap<RelayUrl, Filter>, Error>
where
I: IntoIterator<Item = T>,
T: Into<RelayUrlArg<'url>>,
{
let mut filters: HashMap<RelayUrl, Filter> = HashMap::new();
for url in urls {
let url: RelayUrl = url.into().try_into_relay_url()?.into_owned();
filters.insert(url, filter.clone());
}
Ok(filters)
}
async fn make_sync_targets(
client: &Client,
filters: HashMap<RelayUrl, Filter>,
) -> Result<HashMap<RelayUrl, (Filter, Vec<(EventId, Timestamp)>)>, Error> {
let database = client.database();
let mut f = HashMap::with_capacity(filters.len());
for (url, filter) in filters.into_iter() {
let items: Vec<(EventId, Timestamp)> = database.negentropy_items(filter.clone()).await?;
f.insert(url, (filter, items));
}
Ok(f)
}
impl<'client, 'url> IntoFuture for SyncEvents<'client, 'url>
where
'url: 'client,
{
type Output = Result<Output<SyncSummary>, Error>;
type IntoFuture = BoxedFuture<'client, Self::Output>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let targets: HashMap<RelayUrl, (Filter, Vec<(EventId, Timestamp)>)> =
match (self.client.gossip(), self.with) {
(Some(gossip), None) => {
let filters: HashMap<RelayUrl, Filter> = self
.client
.gossip_break_down_filter(gossip, self.filter)
.await?;
make_sync_targets(self.client, filters).await?
}
(_, Some(with)) => {
let filters: HashMap<RelayUrl, Filter> =
construct_filters(with, self.filter)?;
make_sync_targets(self.client, filters).await?
}
(None, None) => {
let urls: HashSet<RelayUrl> = self
.client
.pool()
.relay_urls_with_any_cap(
RelayCapabilities::READ | RelayCapabilities::WRITE,
)
.await;
let filters: HashMap<RelayUrl, Filter> =
construct_filters(urls, self.filter)?;
make_sync_targets(self.client, filters).await?
}
};
self.client.pool().sync(targets, self.opts).await
})
}
}
#[cfg(test)]
mod tests {
use nostr::event::Kind;
use super::*;
use crate::error::ErrorKind;
#[tokio::test]
async fn test_sync_with_empty_list_of_relays() {
let client = Client::default();
let filter = Filter::default().kind(Kind::TextNote).limit(100);
let relays: Vec<RelayUrl> = Vec::new();
let res = client.sync(filter).with(relays).await;
let err = res.unwrap_err();
assert_eq!(err.kind(), ErrorKind::Invalid);
assert_eq!(err.to_string(), "relay/s not specified");
}
}