mirror of
https://git.asonix.dog/asonix/relay.git
synced 2024-11-28 20:41:33 +00:00
Add local jobs, query connected servers for info
This commit is contained in:
parent
97b5612717
commit
3bfa2c0e45
9 changed files with 624 additions and 38 deletions
110
src/jobs/instance.rs
Normal file
110
src/jobs/instance.rs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
use crate::jobs::JobState;
|
||||||
|
use activitystreams::primitives::XsdAnyUri;
|
||||||
|
use anyhow::Error;
|
||||||
|
use background_jobs::{Job, Processor};
|
||||||
|
use std::{future::Future, pin::Pin};
|
||||||
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct QueryInstance {
|
||||||
|
listener: XsdAnyUri,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryInstance {
|
||||||
|
pub fn new(listener: XsdAnyUri) -> Self {
|
||||||
|
QueryInstance { listener }
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn perform(mut self, state: JobState) -> Result<(), Error> {
|
||||||
|
let listener = self.listener.clone();
|
||||||
|
|
||||||
|
let url = self.listener.as_url_mut();
|
||||||
|
url.set_fragment(None);
|
||||||
|
url.set_query(None);
|
||||||
|
url.set_path("api/v1/instance");
|
||||||
|
|
||||||
|
let instance = state
|
||||||
|
.requests
|
||||||
|
.fetch::<Instance>(self.listener.as_str())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let description = if instance.description.is_empty() {
|
||||||
|
instance.short_description
|
||||||
|
} else {
|
||||||
|
instance.description
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(contact) = instance.contact {
|
||||||
|
state
|
||||||
|
.node_cache
|
||||||
|
.set_contact(
|
||||||
|
listener.clone(),
|
||||||
|
contact.username,
|
||||||
|
contact.display_name,
|
||||||
|
contact.url,
|
||||||
|
contact.avatar,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
state
|
||||||
|
.node_cache
|
||||||
|
.set_instance(
|
||||||
|
listener,
|
||||||
|
instance.title,
|
||||||
|
description,
|
||||||
|
instance.version,
|
||||||
|
instance.registrations,
|
||||||
|
instance.approval_required,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct InstanceProcessor;
|
||||||
|
|
||||||
|
impl Job for QueryInstance {
|
||||||
|
type State = JobState;
|
||||||
|
type Processor = InstanceProcessor;
|
||||||
|
type Future = Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>;
|
||||||
|
|
||||||
|
fn run(self, state: Self::State) -> Self::Future {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
|
actix::spawn(async move {
|
||||||
|
let _ = tx.send(self.perform(state).await);
|
||||||
|
});
|
||||||
|
|
||||||
|
Box::pin(async move { rx.await? })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Processor for InstanceProcessor {
|
||||||
|
type Job = QueryInstance;
|
||||||
|
|
||||||
|
const NAME: &'static str = "InstanceProcessor";
|
||||||
|
const QUEUE: &'static str = "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct Instance {
|
||||||
|
title: String,
|
||||||
|
short_description: String,
|
||||||
|
description: String,
|
||||||
|
version: String,
|
||||||
|
registrations: bool,
|
||||||
|
approval_required: bool,
|
||||||
|
|
||||||
|
contact: Option<Contact>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct Contact {
|
||||||
|
username: String,
|
||||||
|
display_name: String,
|
||||||
|
url: XsdAnyUri,
|
||||||
|
avatar: XsdAnyUri,
|
||||||
|
}
|
|
@ -1,66 +1,105 @@
|
||||||
mod deliver;
|
mod deliver;
|
||||||
mod deliver_many;
|
mod deliver_many;
|
||||||
|
mod instance;
|
||||||
|
mod nodeinfo;
|
||||||
|
mod process_listeners;
|
||||||
mod storage;
|
mod storage;
|
||||||
pub use self::{deliver::Deliver, deliver_many::DeliverMany};
|
pub use self::{
|
||||||
|
deliver::Deliver, deliver_many::DeliverMany, instance::QueryInstance, nodeinfo::QueryNodeinfo,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::Db,
|
db::Db,
|
||||||
error::MyError,
|
error::MyError,
|
||||||
jobs::{deliver::DeliverProcessor, deliver_many::DeliverManyProcessor, storage::Storage},
|
jobs::{
|
||||||
|
deliver::DeliverProcessor,
|
||||||
|
deliver_many::DeliverManyProcessor,
|
||||||
|
instance::InstanceProcessor,
|
||||||
|
nodeinfo::NodeinfoProcessor,
|
||||||
|
process_listeners::{Listeners, ListenersProcessor},
|
||||||
|
storage::Storage,
|
||||||
|
},
|
||||||
|
node::NodeCache,
|
||||||
requests::Requests,
|
requests::Requests,
|
||||||
state::State,
|
state::State,
|
||||||
};
|
};
|
||||||
use background_jobs::{Job, QueueHandle, WorkerConfig};
|
use background_jobs::{memory_storage::Storage as MemoryStorage, Job, QueueHandle, WorkerConfig};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
pub fn create_server(db: Db) -> JobServer {
|
pub fn create_server(db: Db) -> JobServer {
|
||||||
JobServer::new(background_jobs::create_server(Storage::new(db)))
|
let local = background_jobs::create_server(MemoryStorage::new());
|
||||||
|
let shared = background_jobs::create_server(Storage::new(db));
|
||||||
|
|
||||||
|
local.every(Duration::from_secs(60 * 5), Listeners);
|
||||||
|
|
||||||
|
JobServer::new(shared, local)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_workers(state: State, job_server: JobServer) {
|
pub fn create_workers(state: State, job_server: JobServer) {
|
||||||
let queue_handle = job_server.queue_handle();
|
let state2 = state.clone();
|
||||||
|
let job_server2 = job_server.clone();
|
||||||
|
|
||||||
WorkerConfig::new(move || JobState::new(state.requests(), job_server.clone()))
|
let remote_handle = job_server.remote.clone();
|
||||||
|
let local_handle = job_server.local.clone();
|
||||||
|
|
||||||
|
WorkerConfig::new(move || JobState::new(state.clone(), job_server.clone()))
|
||||||
.register(DeliverProcessor)
|
.register(DeliverProcessor)
|
||||||
.register(DeliverManyProcessor)
|
.register(DeliverManyProcessor)
|
||||||
.set_processor_count("default", 4)
|
.set_processor_count("default", 4)
|
||||||
.start(queue_handle);
|
.start(remote_handle);
|
||||||
|
|
||||||
|
WorkerConfig::new(move || JobState::new(state2.clone(), job_server2.clone()))
|
||||||
|
.register(NodeinfoProcessor)
|
||||||
|
.register(InstanceProcessor)
|
||||||
|
.register(ListenersProcessor)
|
||||||
|
.set_processor_count("default", 4)
|
||||||
|
.start(local_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct JobState {
|
pub struct JobState {
|
||||||
requests: Requests,
|
requests: Requests,
|
||||||
|
state: State,
|
||||||
|
node_cache: NodeCache,
|
||||||
job_server: JobServer,
|
job_server: JobServer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct JobServer {
|
pub struct JobServer {
|
||||||
inner: QueueHandle,
|
remote: QueueHandle,
|
||||||
|
local: QueueHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JobState {
|
impl JobState {
|
||||||
fn new(requests: Requests, job_server: JobServer) -> Self {
|
fn new(state: State, job_server: JobServer) -> Self {
|
||||||
JobState {
|
JobState {
|
||||||
requests,
|
requests: state.requests(),
|
||||||
|
node_cache: state.node_cache(),
|
||||||
|
state,
|
||||||
job_server,
|
job_server,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JobServer {
|
impl JobServer {
|
||||||
fn new(queue_handle: QueueHandle) -> Self {
|
fn new(remote_handle: QueueHandle, local_handle: QueueHandle) -> Self {
|
||||||
JobServer {
|
JobServer {
|
||||||
inner: queue_handle,
|
remote: remote_handle,
|
||||||
|
local: local_handle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_handle(&self) -> QueueHandle {
|
|
||||||
self.inner.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn queue<J>(&self, job: J) -> Result<(), MyError>
|
pub fn queue<J>(&self, job: J) -> Result<(), MyError>
|
||||||
where
|
where
|
||||||
J: Job,
|
J: Job,
|
||||||
{
|
{
|
||||||
self.inner.queue(job).map_err(MyError::Queue)
|
self.remote.queue(job).map_err(MyError::Queue)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queue_local<J>(&self, job: J) -> Result<(), MyError>
|
||||||
|
where
|
||||||
|
J: Job,
|
||||||
|
{
|
||||||
|
self.local.queue(job).map_err(MyError::Queue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
170
src/jobs/nodeinfo.rs
Normal file
170
src/jobs/nodeinfo.rs
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
use crate::jobs::JobState;
|
||||||
|
use activitystreams::primitives::XsdAnyUri;
|
||||||
|
use anyhow::Error;
|
||||||
|
use background_jobs::{Job, Processor};
|
||||||
|
use std::{future::Future, pin::Pin};
|
||||||
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct QueryNodeinfo {
|
||||||
|
listener: XsdAnyUri,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryNodeinfo {
|
||||||
|
pub fn new(listener: XsdAnyUri) -> Self {
|
||||||
|
QueryNodeinfo { listener }
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn perform(mut self, state: JobState) -> Result<(), Error> {
|
||||||
|
let listener = self.listener.clone();
|
||||||
|
|
||||||
|
let url = self.listener.as_url_mut();
|
||||||
|
url.set_fragment(None);
|
||||||
|
url.set_query(None);
|
||||||
|
url.set_path(".well-known/nodeinfo");
|
||||||
|
|
||||||
|
let well_known = state
|
||||||
|
.requests
|
||||||
|
.fetch::<WellKnown>(self.listener.as_str())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let href = if let Some(link) = well_known.links.into_iter().next() {
|
||||||
|
link.href
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let nodeinfo = state.requests.fetch::<Nodeinfo>(&href).await?;
|
||||||
|
|
||||||
|
state
|
||||||
|
.node_cache
|
||||||
|
.set_info(
|
||||||
|
listener,
|
||||||
|
nodeinfo.software.name,
|
||||||
|
nodeinfo.software.version,
|
||||||
|
nodeinfo.open_registrations,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct NodeinfoProcessor;
|
||||||
|
|
||||||
|
impl Job for QueryNodeinfo {
|
||||||
|
type State = JobState;
|
||||||
|
type Processor = NodeinfoProcessor;
|
||||||
|
type Future = Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>;
|
||||||
|
|
||||||
|
fn run(self, state: Self::State) -> Self::Future {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
|
actix::spawn(async move {
|
||||||
|
let _ = tx.send(self.perform(state).await);
|
||||||
|
});
|
||||||
|
|
||||||
|
Box::pin(async move { rx.await? })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Processor for NodeinfoProcessor {
|
||||||
|
type Job = QueryNodeinfo;
|
||||||
|
|
||||||
|
const NAME: &'static str = "NodeinfoProcessor";
|
||||||
|
const QUEUE: &'static str = "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct Nodeinfo {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
version: SupportedVersion,
|
||||||
|
|
||||||
|
software: Software,
|
||||||
|
open_registrations: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct Software {
|
||||||
|
name: String,
|
||||||
|
version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct WellKnown {
|
||||||
|
links: Vec<Link>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct Link {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
rel: SupportedNodeinfo,
|
||||||
|
|
||||||
|
href: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SupportedVersion;
|
||||||
|
struct SupportedNodeinfo;
|
||||||
|
|
||||||
|
static SUPPORTED_VERSION: &'static str = "2.0";
|
||||||
|
static SUPPORTED_NODEINFO: &'static str = "http://nodeinfo.diaspora.software/ns/schema/2.0";
|
||||||
|
|
||||||
|
struct SupportedVersionVisitor;
|
||||||
|
struct SupportedNodeinfoVisitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for SupportedVersionVisitor {
|
||||||
|
type Value = SupportedVersion;
|
||||||
|
|
||||||
|
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "the string '{}'", SUPPORTED_VERSION)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
if s == SUPPORTED_VERSION {
|
||||||
|
Ok(SupportedVersion)
|
||||||
|
} else {
|
||||||
|
Err(serde::de::Error::custom("Invalid nodeinfo version"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for SupportedNodeinfoVisitor {
|
||||||
|
type Value = SupportedNodeinfo;
|
||||||
|
|
||||||
|
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "the string '{}'", SUPPORTED_NODEINFO)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
if s == SUPPORTED_NODEINFO {
|
||||||
|
Ok(SupportedNodeinfo)
|
||||||
|
} else {
|
||||||
|
Err(serde::de::Error::custom("Invalid nodeinfo version"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> serde::de::Deserialize<'de> for SupportedVersion {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::de::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_str(SupportedVersionVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> serde::de::Deserialize<'de> for SupportedNodeinfo {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::de::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_str(SupportedNodeinfoVisitor)
|
||||||
|
}
|
||||||
|
}
|
47
src/jobs/process_listeners.rs
Normal file
47
src/jobs/process_listeners.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
use crate::jobs::{instance::QueryInstance, nodeinfo::QueryNodeinfo, JobState};
|
||||||
|
use anyhow::Error;
|
||||||
|
use background_jobs::{Job, Processor};
|
||||||
|
use std::{future::Future, pin::Pin};
|
||||||
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct Listeners;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ListenersProcessor;
|
||||||
|
|
||||||
|
impl Listeners {
|
||||||
|
async fn perform(self, state: JobState) -> Result<(), Error> {
|
||||||
|
for listener in state.state.listeners().await {
|
||||||
|
state
|
||||||
|
.job_server
|
||||||
|
.queue_local(QueryInstance::new(listener.clone()))?;
|
||||||
|
state.job_server.queue_local(QueryNodeinfo::new(listener))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Job for Listeners {
|
||||||
|
type State = JobState;
|
||||||
|
type Processor = ListenersProcessor;
|
||||||
|
type Future = Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>;
|
||||||
|
|
||||||
|
fn run(self, state: Self::State) -> Self::Future {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
|
actix::spawn(async move {
|
||||||
|
let _ = tx.send(self.perform(state).await);
|
||||||
|
});
|
||||||
|
|
||||||
|
Box::pin(async move { rx.await? })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Processor for ListenersProcessor {
|
||||||
|
type Job = Listeners;
|
||||||
|
|
||||||
|
const NAME: &'static str = "ProcessListenersProcessor";
|
||||||
|
const QUEUE: &'static str = "default";
|
||||||
|
}
|
10
src/main.rs
10
src/main.rs
|
@ -18,6 +18,7 @@ mod db;
|
||||||
mod error;
|
mod error;
|
||||||
mod inbox;
|
mod inbox;
|
||||||
mod jobs;
|
mod jobs;
|
||||||
|
mod node;
|
||||||
mod nodeinfo;
|
mod nodeinfo;
|
||||||
mod notify;
|
mod notify;
|
||||||
mod rehydrate;
|
mod rehydrate;
|
||||||
|
@ -42,11 +43,11 @@ async fn index(
|
||||||
state: web::Data<State>,
|
state: web::Data<State>,
|
||||||
config: web::Data<Config>,
|
config: web::Data<Config>,
|
||||||
) -> Result<HttpResponse, MyError> {
|
) -> Result<HttpResponse, MyError> {
|
||||||
let listeners = state.listeners().await;
|
let nodes = state.node_cache().nodes().await;
|
||||||
|
|
||||||
let mut buf = BufWriter::new(Vec::new());
|
let mut buf = BufWriter::new(Vec::new());
|
||||||
|
|
||||||
templates::index(&mut buf, &listeners, &config)?;
|
templates::index(&mut buf, &nodes, &config)?;
|
||||||
let buf = buf.into_inner().map_err(|e| {
|
let buf = buf.into_inner().map_err(|e| {
|
||||||
error!("Error rendering template, {}", e.error());
|
error!("Error rendering template, {}", e.error());
|
||||||
MyError::FlushBuffer
|
MyError::FlushBuffer
|
||||||
|
@ -111,11 +112,10 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let state = State::hydrate(config.clone(), &db).await?;
|
let state = State::hydrate(config.clone(), &db).await?;
|
||||||
|
let job_server = create_server(db.clone());
|
||||||
|
|
||||||
rehydrate::spawn(db.clone(), state.clone());
|
rehydrate::spawn(db.clone(), state.clone());
|
||||||
notify::spawn(state.clone(), &config)?;
|
notify::spawn(state.clone(), job_server.clone(), &config)?;
|
||||||
|
|
||||||
let job_server = create_server(db.clone());
|
|
||||||
|
|
||||||
if args.jobs_only() {
|
if args.jobs_only() {
|
||||||
for _ in 0..num_cpus::get() {
|
for _ in 0..num_cpus::get() {
|
||||||
|
|
194
src/node.rs
Normal file
194
src/node.rs
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
use activitystreams::primitives::XsdAnyUri;
|
||||||
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
pub type ListenersCache = Arc<RwLock<HashSet<XsdAnyUri>>>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct NodeCache {
|
||||||
|
listeners: ListenersCache,
|
||||||
|
nodes: Arc<RwLock<HashMap<XsdAnyUri, Node>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodeCache {
|
||||||
|
pub fn new(listeners: ListenersCache) -> Self {
|
||||||
|
NodeCache {
|
||||||
|
listeners,
|
||||||
|
nodes: Arc::new(RwLock::new(HashMap::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn nodes(&self) -> Vec<Node> {
|
||||||
|
let listeners: HashSet<_> = self.listeners.read().await.clone();
|
||||||
|
|
||||||
|
self.nodes
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(k, v)| {
|
||||||
|
if listeners.contains(k) {
|
||||||
|
Some(v.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_info(
|
||||||
|
&self,
|
||||||
|
listener: XsdAnyUri,
|
||||||
|
software: String,
|
||||||
|
version: String,
|
||||||
|
reg: bool,
|
||||||
|
) {
|
||||||
|
if !self.listeners.read().await.contains(&listener) {
|
||||||
|
let mut nodes = self.nodes.write().await;
|
||||||
|
nodes.remove(&listener);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut write_guard = self.nodes.write().await;
|
||||||
|
let node = write_guard
|
||||||
|
.entry(listener.clone())
|
||||||
|
.or_insert(Node::new(listener));
|
||||||
|
node.set_info(software, version, reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_instance(
|
||||||
|
&self,
|
||||||
|
listener: XsdAnyUri,
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
version: String,
|
||||||
|
reg: bool,
|
||||||
|
requires_approval: bool,
|
||||||
|
) {
|
||||||
|
if !self.listeners.read().await.contains(&listener) {
|
||||||
|
let mut nodes = self.nodes.write().await;
|
||||||
|
nodes.remove(&listener);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut write_guard = self.nodes.write().await;
|
||||||
|
let node = write_guard
|
||||||
|
.entry(listener.clone())
|
||||||
|
.or_insert(Node::new(listener));
|
||||||
|
node.set_instance(title, description, version, reg, requires_approval);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_contact(
|
||||||
|
&self,
|
||||||
|
listener: XsdAnyUri,
|
||||||
|
username: String,
|
||||||
|
display_name: String,
|
||||||
|
url: XsdAnyUri,
|
||||||
|
avatar: XsdAnyUri,
|
||||||
|
) {
|
||||||
|
if !self.listeners.read().await.contains(&listener) {
|
||||||
|
let mut nodes = self.nodes.write().await;
|
||||||
|
nodes.remove(&listener);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut write_guard = self.nodes.write().await;
|
||||||
|
let node = write_guard
|
||||||
|
.entry(listener.clone())
|
||||||
|
.or_insert(Node::new(listener));
|
||||||
|
node.set_contact(username, display_name, url, avatar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Node {
|
||||||
|
pub base: XsdAnyUri,
|
||||||
|
pub info: Option<Info>,
|
||||||
|
pub instance: Option<Instance>,
|
||||||
|
pub contact: Option<Contact>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node {
|
||||||
|
pub fn new(mut uri: XsdAnyUri) -> Self {
|
||||||
|
let url = uri.as_mut();
|
||||||
|
url.set_fragment(None);
|
||||||
|
url.set_query(None);
|
||||||
|
url.set_path("");
|
||||||
|
|
||||||
|
Node {
|
||||||
|
base: uri,
|
||||||
|
info: None,
|
||||||
|
instance: None,
|
||||||
|
contact: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_info(&mut self, software: String, version: String, reg: bool) -> &mut Self {
|
||||||
|
self.info = Some(Info {
|
||||||
|
software,
|
||||||
|
version,
|
||||||
|
reg,
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_instance(
|
||||||
|
&mut self,
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
version: String,
|
||||||
|
reg: bool,
|
||||||
|
requires_approval: bool,
|
||||||
|
) -> &mut Self {
|
||||||
|
self.instance = Some(Instance {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
version,
|
||||||
|
reg,
|
||||||
|
requires_approval,
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_contact(
|
||||||
|
&mut self,
|
||||||
|
username: String,
|
||||||
|
display_name: String,
|
||||||
|
url: XsdAnyUri,
|
||||||
|
avatar: XsdAnyUri,
|
||||||
|
) -> &mut Self {
|
||||||
|
self.contact = Some(Contact {
|
||||||
|
username,
|
||||||
|
display_name,
|
||||||
|
url,
|
||||||
|
avatar,
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Info {
|
||||||
|
pub software: String,
|
||||||
|
pub version: String,
|
||||||
|
pub reg: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Instance {
|
||||||
|
pub title: String,
|
||||||
|
pub description: String,
|
||||||
|
pub version: String,
|
||||||
|
pub reg: bool,
|
||||||
|
pub requires_approval: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Contact {
|
||||||
|
pub username: String,
|
||||||
|
pub display_name: String,
|
||||||
|
pub url: XsdAnyUri,
|
||||||
|
pub avatar: XsdAnyUri,
|
||||||
|
}
|
|
@ -1,4 +1,9 @@
|
||||||
use crate::{db::listen, error::MyError, state::State};
|
use crate::{
|
||||||
|
db::listen,
|
||||||
|
error::MyError,
|
||||||
|
jobs::{JobServer, QueryInstance, QueryNodeinfo},
|
||||||
|
state::State,
|
||||||
|
};
|
||||||
use activitystreams::primitives::XsdAnyUri;
|
use activitystreams::primitives::XsdAnyUri;
|
||||||
use actix::clock::{delay_for, Duration};
|
use actix::clock::{delay_for, Duration};
|
||||||
use bb8_postgres::tokio_postgres::{tls::NoTls, AsyncMessage, Config, Notification};
|
use bb8_postgres::tokio_postgres::{tls::NoTls, AsyncMessage, Config, Notification};
|
||||||
|
@ -9,7 +14,7 @@ use futures::{
|
||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
async fn handle_notification(state: State, notif: Notification) {
|
async fn handle_notification(state: State, job_server: JobServer, notif: Notification) {
|
||||||
match notif.channel() {
|
match notif.channel() {
|
||||||
"new_blocks" => {
|
"new_blocks" => {
|
||||||
info!("Caching block of {}", notif.payload());
|
info!("Caching block of {}", notif.payload());
|
||||||
|
@ -22,7 +27,9 @@ async fn handle_notification(state: State, notif: Notification) {
|
||||||
"new_listeners" => {
|
"new_listeners" => {
|
||||||
if let Ok(uri) = notif.payload().parse::<XsdAnyUri>() {
|
if let Ok(uri) = notif.payload().parse::<XsdAnyUri>() {
|
||||||
info!("Caching listener {}", uri);
|
info!("Caching listener {}", uri);
|
||||||
state.cache_listener(uri).await;
|
state.cache_listener(uri.clone()).await;
|
||||||
|
let _ = job_server.queue_local(QueryInstance::new(uri.clone()));
|
||||||
|
let _ = job_server.queue_local(QueryNodeinfo::new(uri));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"rm_blocks" => {
|
"rm_blocks" => {
|
||||||
|
@ -43,12 +50,14 @@ async fn handle_notification(state: State, notif: Notification) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn(state: State, config: &crate::config::Config) -> Result<(), MyError> {
|
pub fn spawn(
|
||||||
|
state: State,
|
||||||
|
job_server: JobServer,
|
||||||
|
config: &crate::config::Config,
|
||||||
|
) -> Result<(), MyError> {
|
||||||
let config: Config = config.database_url().parse()?;
|
let config: Config = config.database_url().parse()?;
|
||||||
|
|
||||||
actix::spawn(async move {
|
actix::spawn(async move {
|
||||||
let mut client;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (new_client, mut conn) = match config.connect(NoTls).await {
|
let (new_client, mut conn) = match config.connect(NoTls).await {
|
||||||
Ok((client, conn)) => (client, conn),
|
Ok((client, conn)) => (client, conn),
|
||||||
|
@ -59,7 +68,7 @@ pub fn spawn(state: State, config: &crate::config::Config) -> Result<(), MyError
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
client = Arc::new(new_client);
|
let client = Arc::new(new_client);
|
||||||
let new_client = client.clone();
|
let new_client = client.clone();
|
||||||
|
|
||||||
actix::spawn(async move {
|
actix::spawn(async move {
|
||||||
|
@ -88,7 +97,7 @@ pub fn spawn(state: State, config: &crate::config::Config) -> Result<(), MyError
|
||||||
});
|
});
|
||||||
|
|
||||||
while let Some(n) = stream.next().await {
|
while let Some(n) = stream.next().await {
|
||||||
actix::spawn(handle_notification(state.clone(), n));
|
actix::spawn(handle_notification(state.clone(), job_server.clone(), n));
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(client);
|
drop(client);
|
||||||
|
|
10
src/state.rs
10
src/state.rs
|
@ -3,6 +3,7 @@ use crate::{
|
||||||
config::{Config, UrlKind},
|
config::{Config, UrlKind},
|
||||||
db::Db,
|
db::Db,
|
||||||
error::MyError,
|
error::MyError,
|
||||||
|
node::NodeCache,
|
||||||
requests::Requests,
|
requests::Requests,
|
||||||
};
|
};
|
||||||
use activitystreams::primitives::XsdAnyUri;
|
use activitystreams::primitives::XsdAnyUri;
|
||||||
|
@ -28,9 +29,14 @@ pub struct State {
|
||||||
blocks: Arc<RwLock<HashSet<String>>>,
|
blocks: Arc<RwLock<HashSet<String>>>,
|
||||||
whitelists: Arc<RwLock<HashSet<String>>>,
|
whitelists: Arc<RwLock<HashSet<String>>>,
|
||||||
listeners: Arc<RwLock<HashSet<XsdAnyUri>>>,
|
listeners: Arc<RwLock<HashSet<XsdAnyUri>>>,
|
||||||
|
node_cache: NodeCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
pub fn node_cache(&self) -> NodeCache {
|
||||||
|
self.node_cache.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn requests(&self) -> Requests {
|
pub fn requests(&self) -> Requests {
|
||||||
Requests::new(
|
Requests::new(
|
||||||
self.config.generate_url(UrlKind::MainKey),
|
self.config.generate_url(UrlKind::MainKey),
|
||||||
|
@ -191,6 +197,7 @@ impl State {
|
||||||
let (blocks, whitelists, listeners, private_key) = try_join!(f1, f2, f3, f4)?;
|
let (blocks, whitelists, listeners, private_key) = try_join!(f1, f2, f3, f4)?;
|
||||||
|
|
||||||
let public_key = private_key.to_public_key();
|
let public_key = private_key.to_public_key();
|
||||||
|
let listeners = Arc::new(RwLock::new(listeners));
|
||||||
|
|
||||||
Ok(State {
|
Ok(State {
|
||||||
public_key,
|
public_key,
|
||||||
|
@ -200,7 +207,8 @@ impl State {
|
||||||
actor_id_cache: Arc::new(RwLock::new(LruCache::new(1024 * 8))),
|
actor_id_cache: Arc::new(RwLock::new(LruCache::new(1024 * 8))),
|
||||||
blocks: Arc::new(RwLock::new(blocks)),
|
blocks: Arc::new(RwLock::new(blocks)),
|
||||||
whitelists: Arc::new(RwLock::new(whitelists)),
|
whitelists: Arc::new(RwLock::new(whitelists)),
|
||||||
listeners: Arc::new(RwLock::new(listeners)),
|
listeners: listeners.clone(),
|
||||||
|
node_cache: NodeCache::new(listeners),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
@use crate::{config::{Config, UrlKind}, templates::statics::index_css};
|
@use crate::{config::{Config, UrlKind}, templates::statics::index_css, node::Node};
|
||||||
@use activitystreams::primitives::XsdAnyUri;
|
|
||||||
|
|
||||||
@(listeners: &[XsdAnyUri], config: &Config)
|
@(nodes: &[Node], config: &Config)
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
|
@ -17,13 +16,23 @@
|
||||||
<main>
|
<main>
|
||||||
<section>
|
<section>
|
||||||
<h3>Connected Servers:</h3>
|
<h3>Connected Servers:</h3>
|
||||||
@if listeners.is_empty() {
|
@if nodes.is_empty() {
|
||||||
<p>There are no connected servers at this time.</p>
|
<p>There are no connected servers at this time.</p>
|
||||||
} else {
|
} else {
|
||||||
<ul>
|
<ul>
|
||||||
@for listener in listeners {
|
@for node in nodes {
|
||||||
@if let Some(domain) = listener.as_url().domain() {
|
@if let Some(domain) = node.base.as_url().domain() {
|
||||||
<li>@domain</li>
|
<li>
|
||||||
|
<p><a href="@node.base">@domain</a></p>
|
||||||
|
@if let Some(info) = node.info.as_ref() {
|
||||||
|
<p>Running @info.software version @info.version</p>
|
||||||
|
@if info.reg {
|
||||||
|
<p>Registration is open</p>
|
||||||
|
} else {
|
||||||
|
<p>Registration is closed</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</li>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
Loading…
Reference in a new issue