2022-12-20 05:42:28 +00:00
|
|
|
use crate::comms::BusMessage;
|
2022-12-26 00:20:19 +00:00
|
|
|
use crate::db::{DbEvent, DbPerson, DbPersonRelay, DbRelay};
|
|
|
|
use crate::error::Error;
|
2022-12-24 00:22:23 +00:00
|
|
|
use crate::relationship::Relationship;
|
2022-12-23 04:11:10 +00:00
|
|
|
use crate::settings::Settings;
|
2022-12-24 00:22:23 +00:00
|
|
|
use async_recursion::async_recursion;
|
2022-12-26 00:20:19 +00:00
|
|
|
use nostr_types::{Event, EventKind, Id, IdHex, PublicKey, PublicKeyHex, Unixtime, Url};
|
2022-12-20 05:49:07 +00:00
|
|
|
use rusqlite::Connection;
|
2022-12-20 06:11:45 +00:00
|
|
|
use std::collections::HashMap;
|
2022-12-20 20:01:53 +00:00
|
|
|
use std::sync::atomic::AtomicBool;
|
2022-12-20 05:42:28 +00:00
|
|
|
use tokio::sync::{broadcast, mpsc, Mutex};
|
2022-12-26 00:20:19 +00:00
|
|
|
use tracing::info;
|
2022-12-20 05:42:28 +00:00
|
|
|
|
|
|
|
/// Only one of these is ever created, via lazy_static!, and represents
|
|
|
|
/// global state for the rust application
|
|
|
|
pub struct Globals {
|
2022-12-20 05:49:07 +00:00
|
|
|
/// This is our connection to SQLite. Only one thread at a time.
|
|
|
|
pub db: Mutex<Option<Connection>>,
|
|
|
|
|
2022-12-20 05:42:28 +00:00
|
|
|
/// This is a broadcast channel. All Minions should listen on it.
|
|
|
|
/// To create a receiver, just run .subscribe() on it.
|
|
|
|
pub to_minions: broadcast::Sender<BusMessage>,
|
|
|
|
|
|
|
|
/// This is a mpsc channel. The Overlord listens on it.
|
|
|
|
/// To create a sender, just clone() it.
|
|
|
|
pub to_overlord: mpsc::UnboundedSender<BusMessage>,
|
|
|
|
|
|
|
|
/// This is ephemeral. It is filled during lazy_static initialization,
|
|
|
|
/// and stolen away when the Overlord is created.
|
|
|
|
pub from_minions: Mutex<Option<mpsc::UnboundedReceiver<BusMessage>>>,
|
2022-12-20 06:11:45 +00:00
|
|
|
|
2022-12-24 00:22:23 +00:00
|
|
|
/// All nostr events, keyed by the event Id
|
|
|
|
pub events: Mutex<HashMap<Id, Event>>,
|
|
|
|
|
|
|
|
/// All relationships between events
|
|
|
|
pub relationships: Mutex<HashMap<Id, Vec<(Id, Relationship)>>>,
|
|
|
|
|
|
|
|
/// The date of the latest reply. Only reply relationships count, not reactions,
|
|
|
|
/// deletions, or quotes
|
|
|
|
pub last_reply: Mutex<HashMap<Id, Unixtime>>,
|
|
|
|
|
2022-12-22 20:28:04 +00:00
|
|
|
/// Desired events, referred to by others, with possible URLs where we can
|
|
|
|
/// get them. We may already have these, but if not we should ask for them.
|
|
|
|
pub desired_events: Mutex<HashMap<Id, Vec<Url>>>,
|
|
|
|
|
2022-12-20 06:11:45 +00:00
|
|
|
/// All nostr people records currently loaded into memory, keyed by pubkey
|
|
|
|
pub people: Mutex<HashMap<PublicKey, DbPerson>>,
|
2022-12-20 20:01:53 +00:00
|
|
|
|
2022-12-24 07:52:23 +00:00
|
|
|
/// All nostr relay records we have
|
|
|
|
pub relays: Mutex<HashMap<Url, DbRelay>>,
|
|
|
|
|
2022-12-20 20:01:53 +00:00
|
|
|
/// Whether or not we have a saved private key and need the password to unlock it
|
|
|
|
pub need_password: AtomicBool,
|
2022-12-23 04:11:10 +00:00
|
|
|
|
2022-12-24 04:12:17 +00:00
|
|
|
/// Whether or not we are shutting down. For the UI (minions will be signaled and
|
|
|
|
/// waited for by the overlord)
|
|
|
|
pub shutting_down: AtomicBool,
|
|
|
|
|
2022-12-23 04:11:10 +00:00
|
|
|
/// Settings
|
|
|
|
pub settings: Mutex<Settings>,
|
2022-12-20 05:42:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
pub static ref GLOBALS: Globals = {
|
|
|
|
|
|
|
|
// Setup a communications channel from the Overlord to the Minions.
|
|
|
|
let (to_minions, _) = broadcast::channel(16);
|
|
|
|
|
|
|
|
// Setup a communications channel from the Minions to the Overlord.
|
|
|
|
let (to_overlord, from_minions) = mpsc::unbounded_channel();
|
|
|
|
|
|
|
|
Globals {
|
2022-12-20 05:49:07 +00:00
|
|
|
db: Mutex::new(None),
|
2022-12-20 05:42:28 +00:00
|
|
|
to_minions,
|
|
|
|
to_overlord,
|
|
|
|
from_minions: Mutex::new(Some(from_minions)),
|
2022-12-24 00:22:23 +00:00
|
|
|
events: Mutex::new(HashMap::new()),
|
|
|
|
relationships: Mutex::new(HashMap::new()),
|
|
|
|
last_reply: Mutex::new(HashMap::new()),
|
2022-12-22 20:28:04 +00:00
|
|
|
desired_events: Mutex::new(HashMap::new()),
|
2022-12-20 06:11:45 +00:00
|
|
|
people: Mutex::new(HashMap::new()),
|
2022-12-24 07:52:23 +00:00
|
|
|
relays: Mutex::new(HashMap::new()),
|
2022-12-20 20:01:53 +00:00
|
|
|
need_password: AtomicBool::new(false),
|
2022-12-24 04:12:17 +00:00
|
|
|
shutting_down: AtomicBool::new(false),
|
2022-12-23 04:11:10 +00:00
|
|
|
settings: Mutex::new(Settings::default()),
|
2022-12-20 05:42:28 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2022-12-20 06:11:45 +00:00
|
|
|
|
2022-12-24 00:11:51 +00:00
|
|
|
impl Globals {
|
|
|
|
pub fn blocking_get_feed(threaded: bool) -> Vec<Id> {
|
2022-12-24 02:41:07 +00:00
|
|
|
let feed: Vec<Event> = GLOBALS
|
|
|
|
.events
|
2022-12-24 00:11:51 +00:00
|
|
|
.blocking_lock()
|
|
|
|
.iter()
|
|
|
|
.map(|(_, e)| e)
|
2022-12-24 02:41:07 +00:00
|
|
|
.filter(|e| e.kind == EventKind::TextNote)
|
2022-12-24 00:11:51 +00:00
|
|
|
.filter(|e| {
|
|
|
|
if threaded {
|
2022-12-24 02:41:07 +00:00
|
|
|
e.replies_to().is_none()
|
2022-12-24 00:11:51 +00:00
|
|
|
} else {
|
|
|
|
true
|
|
|
|
}
|
2022-12-24 02:41:07 +00:00
|
|
|
})
|
2022-12-24 00:11:51 +00:00
|
|
|
.cloned()
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
Self::sort_feed(feed, threaded)
|
2022-12-23 01:31:16 +00:00
|
|
|
}
|
2022-12-21 17:22:54 +00:00
|
|
|
|
2022-12-24 02:41:07 +00:00
|
|
|
fn sort_feed(mut feed: Vec<Event>, threaded: bool) -> Vec<Id> {
|
2022-12-24 00:11:51 +00:00
|
|
|
if threaded {
|
|
|
|
feed.sort_unstable_by(|a, b| {
|
2022-12-24 02:41:07 +00:00
|
|
|
let a_last = GLOBALS.last_reply.blocking_lock().get(&a.id).cloned();
|
|
|
|
let b_last = GLOBALS.last_reply.blocking_lock().get(&b.id).cloned();
|
|
|
|
let a_time = a_last.unwrap_or(a.created_at);
|
|
|
|
let b_time = b_last.unwrap_or(b.created_at);
|
|
|
|
b_time.cmp(&a_time)
|
2022-12-24 00:11:51 +00:00
|
|
|
});
|
2022-12-24 02:41:07 +00:00
|
|
|
} else {
|
|
|
|
feed.sort_unstable_by(|a, b| b.created_at.cmp(&a.created_at));
|
2022-12-24 00:11:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
feed.iter().map(|e| e.id).collect()
|
|
|
|
}
|
2022-12-24 00:22:23 +00:00
|
|
|
|
|
|
|
pub async fn store_desired_event(id: Id, url: Option<Url>) {
|
|
|
|
let mut desired_events = GLOBALS.desired_events.lock().await;
|
|
|
|
desired_events
|
|
|
|
.entry(id)
|
|
|
|
.and_modify(|urls| {
|
|
|
|
if let Some(ref u) = url {
|
2022-12-25 22:48:08 +00:00
|
|
|
if let Ok(valid) = Url::new_validated(u) {
|
|
|
|
urls.push(valid);
|
|
|
|
}
|
2022-12-24 00:22:23 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.or_insert_with(|| if let Some(u) = url { vec![u] } else { vec![] });
|
|
|
|
}
|
|
|
|
|
2022-12-26 00:20:19 +00:00
|
|
|
async fn get_desired_events_prelude() -> Result<(), Error> {
|
|
|
|
// Strip out Ids of events that we already have
|
|
|
|
{
|
|
|
|
// danger - two locks could lead to deadlock, check other code locking these
|
|
|
|
let mut desired_events = GLOBALS.desired_events.lock().await;
|
|
|
|
let events = GLOBALS.events.lock().await;
|
|
|
|
desired_events.retain(|&id, _| !events.contains_key(&id));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load from database
|
|
|
|
{
|
|
|
|
let ids: Vec<IdHex> = GLOBALS
|
|
|
|
.desired_events
|
|
|
|
.lock()
|
|
|
|
.await
|
|
|
|
.iter()
|
|
|
|
.map(|(id, _)| Into::<IdHex>::into(*id))
|
|
|
|
.collect();
|
|
|
|
let db_events = DbEvent::fetch_by_ids(ids).await?;
|
|
|
|
let mut events: Vec<Event> = Vec::with_capacity(db_events.len());
|
|
|
|
for dbevent in db_events.iter() {
|
|
|
|
let e = serde_json::from_str(&dbevent.raw)?;
|
|
|
|
events.push(e);
|
|
|
|
}
|
|
|
|
let mut count = 0;
|
|
|
|
for event in events.iter() {
|
|
|
|
count += 1;
|
|
|
|
crate::process::process_new_event(event, false, None).await?;
|
|
|
|
}
|
|
|
|
info!("Loaded {} desired events from the database", count);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Strip out Ids of events that we already have (again, we just loaded from db)
|
|
|
|
{
|
|
|
|
// danger - two locks could lead to deadlock, check other code locking these
|
|
|
|
let mut desired_events = GLOBALS.desired_events.lock().await;
|
|
|
|
let events = GLOBALS.events.lock().await;
|
|
|
|
desired_events.retain(|&id, _| !events.contains_key(&id));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn get_desired_events() -> Result<(HashMap<Url, Vec<Id>>, Vec<Id>), Error> {
|
|
|
|
Globals::get_desired_events_prelude().await?;
|
|
|
|
|
|
|
|
let desired_events = GLOBALS.desired_events.lock().await;
|
|
|
|
let mut output: HashMap<Url, Vec<Id>> = HashMap::new();
|
|
|
|
let mut orphans: Vec<Id> = Vec::new();
|
|
|
|
for (id, vec_url) in desired_events.iter() {
|
|
|
|
if vec_url.is_empty() {
|
|
|
|
orphans.push(*id);
|
|
|
|
} else {
|
|
|
|
for url in vec_url.iter() {
|
|
|
|
output
|
|
|
|
.entry(url.to_owned())
|
|
|
|
.and_modify(|vec| vec.push(*id))
|
|
|
|
.or_insert_with(|| vec![*id]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok((output, orphans))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
pub async fn get_desired_events_for_url(url: Url) -> Result<Vec<Id>, Error> {
|
|
|
|
Globals::get_desired_events_prelude().await?;
|
|
|
|
|
|
|
|
let desired_events = GLOBALS.desired_events.lock().await;
|
|
|
|
let mut output: Vec<Id> = Vec::new();
|
|
|
|
for (id, vec_url) in desired_events.iter() {
|
|
|
|
if vec_url.is_empty() || vec_url.contains(&url) {
|
|
|
|
output.push(*id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(output)
|
|
|
|
}
|
|
|
|
|
2022-12-24 00:22:23 +00:00
|
|
|
pub async fn add_relationship(id: Id, related: Id, relationship: Relationship) {
|
|
|
|
let r = (related, relationship);
|
|
|
|
let mut relationships = GLOBALS.relationships.lock().await;
|
|
|
|
relationships
|
|
|
|
.entry(id)
|
|
|
|
.and_modify(|vec| {
|
|
|
|
if !vec.contains(&r) {
|
|
|
|
vec.push(r.clone());
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.or_insert_with(|| vec![r]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[async_recursion]
|
|
|
|
pub async fn update_last_reply(id: Id, time: Unixtime) {
|
|
|
|
{
|
|
|
|
let mut last_reply = GLOBALS.last_reply.lock().await;
|
|
|
|
last_reply
|
|
|
|
.entry(id)
|
|
|
|
.and_modify(|lasttime| {
|
|
|
|
if time > *lasttime {
|
|
|
|
*lasttime = time;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.or_insert_with(|| time);
|
|
|
|
} // drops lock
|
|
|
|
|
|
|
|
// Recurse upwards
|
|
|
|
if let Some(event) = GLOBALS.events.lock().await.get(&id).cloned() {
|
|
|
|
if let Some((id, _maybe_url)) = event.replies_to() {
|
|
|
|
Self::update_last_reply(id, event.created_at).await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-24 02:41:07 +00:00
|
|
|
pub fn get_replies_sync(id: Id) -> Vec<Id> {
|
|
|
|
let mut output: Vec<Id> = Vec::new();
|
|
|
|
if let Some(vec) = GLOBALS.relationships.blocking_lock().get(&id) {
|
|
|
|
for (id, relationship) in vec.iter() {
|
|
|
|
if *relationship == Relationship::Reply {
|
|
|
|
output.push(*id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
output
|
|
|
|
}
|
|
|
|
|
2022-12-24 00:22:23 +00:00
|
|
|
// FIXME - this allows people to react many times to the same event, and
|
|
|
|
// it counts them all!
|
2022-12-24 02:41:07 +00:00
|
|
|
pub fn get_reactions_sync(id: Id) -> HashMap<char, usize> {
|
2022-12-24 00:22:23 +00:00
|
|
|
let mut output: HashMap<char, usize> = HashMap::new();
|
|
|
|
|
2022-12-24 02:41:07 +00:00
|
|
|
if let Some(relationships) = GLOBALS.relationships.blocking_lock().get(&id).cloned() {
|
2022-12-24 00:22:23 +00:00
|
|
|
for (_id, relationship) in relationships.iter() {
|
|
|
|
if let Relationship::Reaction(reaction) = relationship {
|
|
|
|
if let Some(ch) = reaction.chars().next() {
|
|
|
|
output
|
|
|
|
.entry(ch)
|
|
|
|
.and_modify(|count| *count += 1)
|
|
|
|
.or_insert_with(|| 1);
|
|
|
|
} else {
|
|
|
|
output
|
|
|
|
.entry('+') // if empty, presumed to be an upvote
|
|
|
|
.and_modify(|count| *count += 1)
|
|
|
|
.or_insert_with(|| 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
output
|
|
|
|
}
|
2022-12-20 06:11:45 +00:00
|
|
|
}
|
|
|
|
|
2022-12-20 19:23:30 +00:00
|
|
|
pub async fn followed_pubkeys() -> Vec<PublicKeyHex> {
|
2022-12-20 06:11:45 +00:00
|
|
|
let people = GLOBALS.people.lock().await;
|
|
|
|
people
|
|
|
|
.iter()
|
|
|
|
.map(|(_, p)| p)
|
|
|
|
.filter(|p| p.followed == 1)
|
|
|
|
.map(|p| p.pubkey.clone())
|
|
|
|
.collect()
|
|
|
|
}
|
2022-12-20 13:10:08 +00:00
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
pub async fn follow_key_and_relay(pubkey: String, relay: String) -> Result<DbPerson, String> {
|
|
|
|
let pubkeyhex = PublicKeyHex(pubkey.clone());
|
|
|
|
|
2022-12-25 22:48:08 +00:00
|
|
|
// Validate the url
|
|
|
|
let _ = Url::new_validated(&relay).map_err(|e| format!("{}", e))?;
|
|
|
|
|
2022-12-20 13:10:08 +00:00
|
|
|
// Create or update them
|
|
|
|
let person = match DbPerson::fetch_one(pubkeyhex.clone())
|
|
|
|
.await
|
|
|
|
.map_err(|e| format!("{}", e))?
|
|
|
|
{
|
|
|
|
Some(mut person) => {
|
|
|
|
person.followed = 1;
|
|
|
|
DbPerson::update(person.clone())
|
|
|
|
.await
|
|
|
|
.map_err(|e| format!("{}", e))?;
|
|
|
|
person
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
let mut person = DbPerson::new(pubkeyhex.clone());
|
|
|
|
person.followed = 1;
|
|
|
|
DbPerson::insert(person.clone())
|
|
|
|
.await
|
|
|
|
.map_err(|e| format!("{}", e))?;
|
|
|
|
person
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Insert (or ignore) this relay
|
|
|
|
DbRelay::insert(DbRelay::new(relay.clone()))
|
|
|
|
.await
|
|
|
|
.map_err(|e| format!("{}", e))?;
|
|
|
|
|
|
|
|
// Insert (or ignore) this person's relay
|
|
|
|
DbPersonRelay::insert(DbPersonRelay {
|
|
|
|
person: pubkey,
|
|
|
|
relay,
|
2022-12-23 18:01:42 +00:00
|
|
|
..Default::default()
|
2022-12-20 13:10:08 +00:00
|
|
|
})
|
|
|
|
.await
|
|
|
|
.map_err(|e| format!("{}", e))?;
|
|
|
|
|
|
|
|
// Tell the overlord to update the minion to watch for their events
|
|
|
|
// possibly starting a new minion if necessary.
|
|
|
|
// FIXME TODO
|
|
|
|
|
|
|
|
// Reply to javascript with the person which will be set in the store
|
|
|
|
Ok(person)
|
|
|
|
}
|