Keep track of when servers were last seen
This commit is contained in:
		
							parent
							
								
									b49eeaf822
								
							
						
					
					
						commit
						88b0383084
					
				
					 15 changed files with 228 additions and 34 deletions
				
			
		
							
								
								
									
										1
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -340,6 +340,7 @@ dependencies = [
 | 
			
		|||
 "sled",
 | 
			
		||||
 "teloxide",
 | 
			
		||||
 "thiserror",
 | 
			
		||||
 "time",
 | 
			
		||||
 "tokio",
 | 
			
		||||
 "toml",
 | 
			
		||||
 "tracing",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -66,6 +66,7 @@ teloxide = { version = "0.11.1", default-features = false, features = [
 | 
			
		|||
  "rustls",
 | 
			
		||||
] }
 | 
			
		||||
thiserror = "1.0"
 | 
			
		||||
time = { version = "0.3.17", features = ["serde"] }
 | 
			
		||||
tracing = "0.1"
 | 
			
		||||
tracing-awc = "0.1.6"
 | 
			
		||||
tracing-error = "0.2"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,6 @@
 | 
			
		|||
use activitystreams::iri_string::types::IriString;
 | 
			
		||||
use std::collections::{BTreeMap, BTreeSet};
 | 
			
		||||
use time::OffsetDateTime;
 | 
			
		||||
 | 
			
		||||
pub mod client;
 | 
			
		||||
pub mod routes;
 | 
			
		||||
| 
						 | 
				
			
			@ -22,3 +24,9 @@ pub(crate) struct BlockedDomains {
 | 
			
		|||
pub(crate) struct ConnectedActors {
 | 
			
		||||
    pub(crate) connected_actors: Vec<IriString>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize, serde::Serialize)]
 | 
			
		||||
pub(crate) struct LastSeen {
 | 
			
		||||
    pub(crate) last_seen: BTreeMap<OffsetDateTime, BTreeSet<String>>,
 | 
			
		||||
    pub(crate) never: Vec<String>,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
use crate::{
 | 
			
		||||
    admin::{AllowedDomains, BlockedDomains, ConnectedActors, Domains},
 | 
			
		||||
    admin::{AllowedDomains, BlockedDomains, ConnectedActors, Domains, LastSeen},
 | 
			
		||||
    collector::Snapshot,
 | 
			
		||||
    config::{AdminUrlKind, Config},
 | 
			
		||||
    error::{Error, ErrorKind},
 | 
			
		||||
| 
						 | 
				
			
			@ -55,6 +55,10 @@ pub(crate) async fn stats(client: &Client, config: &Config) -> Result<Snapshot,
 | 
			
		|||
    get_results(client, config, AdminUrlKind::Stats).await
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(crate) async fn last_seen(client: &Client, config: &Config) -> Result<LastSeen, Error> {
 | 
			
		||||
    get_results(client, config, AdminUrlKind::LastSeen).await
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn get_results<T: DeserializeOwned>(
 | 
			
		||||
    client: &Client,
 | 
			
		||||
    config: &Config,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
use crate::{
 | 
			
		||||
    admin::{AllowedDomains, BlockedDomains, ConnectedActors, Domains},
 | 
			
		||||
    admin::{AllowedDomains, BlockedDomains, ConnectedActors, Domains, LastSeen},
 | 
			
		||||
    collector::{MemoryCollector, Snapshot},
 | 
			
		||||
    error::Error,
 | 
			
		||||
    extractors::Admin,
 | 
			
		||||
| 
						 | 
				
			
			@ -8,6 +8,8 @@ use actix_web::{
 | 
			
		|||
    web::{Data, Json},
 | 
			
		||||
    HttpResponse,
 | 
			
		||||
};
 | 
			
		||||
use std::collections::{BTreeMap, BTreeSet};
 | 
			
		||||
use time::OffsetDateTime;
 | 
			
		||||
 | 
			
		||||
pub(crate) async fn allow(
 | 
			
		||||
    admin: Admin,
 | 
			
		||||
| 
						 | 
				
			
			@ -69,3 +71,20 @@ pub(crate) async fn stats(
 | 
			
		|||
) -> Result<Json<Snapshot>, Error> {
 | 
			
		||||
    Ok(Json(collector.snapshot()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(crate) async fn last_seen(admin: Admin) -> Result<Json<LastSeen>, Error> {
 | 
			
		||||
    let nodes = admin.db_ref().last_seen().await?;
 | 
			
		||||
 | 
			
		||||
    let mut last_seen: BTreeMap<OffsetDateTime, BTreeSet<String>> = BTreeMap::new();
 | 
			
		||||
    let mut never = Vec::new();
 | 
			
		||||
 | 
			
		||||
    for (domain, datetime) in nodes {
 | 
			
		||||
        if let Some(datetime) = datetime {
 | 
			
		||||
            last_seen.entry(datetime).or_default().insert(domain);
 | 
			
		||||
        } else {
 | 
			
		||||
            never.push(domain);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(Json(LastSeen { last_seen, never }))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										17
									
								
								src/args.rs
									
										
									
									
									
								
							
							
						
						
									
										17
									
								
								src/args.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -17,11 +17,22 @@ pub(crate) struct Args {
 | 
			
		|||
 | 
			
		||||
    #[arg(short, long, help = "Get statistics from the server")]
 | 
			
		||||
    stats: bool,
 | 
			
		||||
 | 
			
		||||
    #[arg(
 | 
			
		||||
        short,
 | 
			
		||||
        long,
 | 
			
		||||
        help = "List domains by when they were last succesfully contacted"
 | 
			
		||||
    )]
 | 
			
		||||
    contacted: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Args {
 | 
			
		||||
    pub(crate) fn any(&self) -> bool {
 | 
			
		||||
        !self.blocks.is_empty() || !self.allowed.is_empty() || self.list || self.stats
 | 
			
		||||
        !self.blocks.is_empty()
 | 
			
		||||
            || !self.allowed.is_empty()
 | 
			
		||||
            || self.list
 | 
			
		||||
            || self.stats
 | 
			
		||||
            || self.contacted
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn new() -> Self {
 | 
			
		||||
| 
						 | 
				
			
			@ -47,4 +58,8 @@ impl Args {
 | 
			
		|||
    pub(crate) fn stats(&self) -> bool {
 | 
			
		||||
        self.stats
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn contacted(&self) -> bool {
 | 
			
		||||
        self.contacted
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -92,6 +92,7 @@ pub enum AdminUrlKind {
 | 
			
		|||
    Blocked,
 | 
			
		||||
    Connected,
 | 
			
		||||
    Stats,
 | 
			
		||||
    LastSeen,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::fmt::Debug for Config {
 | 
			
		||||
| 
						 | 
				
			
			@ -429,33 +430,22 @@ impl Config {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    fn do_generate_admin_url(&self, kind: AdminUrlKind) -> Result<IriString, Error> {
 | 
			
		||||
        let iri = match kind {
 | 
			
		||||
            AdminUrlKind::Allow => FixedBaseResolver::new(self.base_uri.as_ref())
 | 
			
		||||
                .resolve(IriRelativeStr::new("api/v1/admin/allow")?.as_ref())
 | 
			
		||||
                .try_to_dedicated_string()?,
 | 
			
		||||
            AdminUrlKind::Disallow => FixedBaseResolver::new(self.base_uri.as_ref())
 | 
			
		||||
                .resolve(IriRelativeStr::new("api/v1/admin/disallow")?.as_ref())
 | 
			
		||||
                .try_to_dedicated_string()?,
 | 
			
		||||
            AdminUrlKind::Block => FixedBaseResolver::new(self.base_uri.as_ref())
 | 
			
		||||
                .resolve(IriRelativeStr::new("api/v1/admin/block")?.as_ref())
 | 
			
		||||
                .try_to_dedicated_string()?,
 | 
			
		||||
            AdminUrlKind::Unblock => FixedBaseResolver::new(self.base_uri.as_ref())
 | 
			
		||||
                .resolve(IriRelativeStr::new("api/v1/admin/unblock")?.as_ref())
 | 
			
		||||
                .try_to_dedicated_string()?,
 | 
			
		||||
            AdminUrlKind::Allowed => FixedBaseResolver::new(self.base_uri.as_ref())
 | 
			
		||||
                .resolve(IriRelativeStr::new("api/v1/admin/allowed")?.as_ref())
 | 
			
		||||
                .try_to_dedicated_string()?,
 | 
			
		||||
            AdminUrlKind::Blocked => FixedBaseResolver::new(self.base_uri.as_ref())
 | 
			
		||||
                .resolve(IriRelativeStr::new("api/v1/admin/blocked")?.as_ref())
 | 
			
		||||
                .try_to_dedicated_string()?,
 | 
			
		||||
            AdminUrlKind::Connected => FixedBaseResolver::new(self.base_uri.as_ref())
 | 
			
		||||
                .resolve(IriRelativeStr::new("api/v1/admin/connected")?.as_ref())
 | 
			
		||||
                .try_to_dedicated_string()?,
 | 
			
		||||
            AdminUrlKind::Stats => FixedBaseResolver::new(self.base_uri.as_ref())
 | 
			
		||||
                .resolve(IriRelativeStr::new("api/v1/admin/stats")?.as_ref())
 | 
			
		||||
                .try_to_dedicated_string()?,
 | 
			
		||||
        let path = match kind {
 | 
			
		||||
            AdminUrlKind::Allow => "api/v1/admin/allow",
 | 
			
		||||
            AdminUrlKind::Disallow => "api/v1/admin/disallow",
 | 
			
		||||
            AdminUrlKind::Block => "api/v1/admin/block",
 | 
			
		||||
            AdminUrlKind::Unblock => "api/v1/admin/unblock",
 | 
			
		||||
            AdminUrlKind::Allowed => "api/v1/admin/allowed",
 | 
			
		||||
            AdminUrlKind::Blocked => "api/v1/admin/blocked",
 | 
			
		||||
            AdminUrlKind::Connected => "api/v1/admin/connected",
 | 
			
		||||
            AdminUrlKind::Stats => "api/v1/admin/stats",
 | 
			
		||||
            AdminUrlKind::LastSeen => "api/v1/admin/last_seen",
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let iri = FixedBaseResolver::new(self.base_uri.as_ref())
 | 
			
		||||
            .resolve(IriRelativeStr::new(path)?.as_ref())
 | 
			
		||||
            .try_to_dedicated_string()?;
 | 
			
		||||
 | 
			
		||||
        Ok(iri)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,11 @@
 | 
			
		|||
mod actor;
 | 
			
		||||
mod last_online;
 | 
			
		||||
mod media;
 | 
			
		||||
mod node;
 | 
			
		||||
mod state;
 | 
			
		||||
 | 
			
		||||
pub(crate) use actor::ActorCache;
 | 
			
		||||
pub(crate) use last_online::LastOnline;
 | 
			
		||||
pub(crate) use media::MediaCache;
 | 
			
		||||
pub(crate) use node::{Node, NodeCache};
 | 
			
		||||
pub(crate) use state::State;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										28
									
								
								src/data/last_online.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/data/last_online.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
use activitystreams::iri_string::types::IriStr;
 | 
			
		||||
use std::{collections::HashMap, sync::Mutex};
 | 
			
		||||
use time::OffsetDateTime;
 | 
			
		||||
 | 
			
		||||
pub(crate) struct LastOnline {
 | 
			
		||||
    domains: Mutex<HashMap<String, OffsetDateTime>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl LastOnline {
 | 
			
		||||
    pub(crate) fn mark_seen(&self, iri: &IriStr) {
 | 
			
		||||
        if let Some(authority) = iri.authority_str() {
 | 
			
		||||
            self.domains
 | 
			
		||||
                .lock()
 | 
			
		||||
                .unwrap()
 | 
			
		||||
                .insert(authority.to_string(), OffsetDateTime::now_utc());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn take(&self) -> HashMap<String, OffsetDateTime> {
 | 
			
		||||
        std::mem::take(&mut *self.domains.lock().unwrap())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn empty() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            domains: Mutex::new(HashMap::default()),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -12,6 +12,8 @@ use rand::thread_rng;
 | 
			
		|||
use rsa::{RsaPrivateKey, RsaPublicKey};
 | 
			
		||||
use std::sync::{Arc, RwLock};
 | 
			
		||||
 | 
			
		||||
use super::LastOnline;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct State {
 | 
			
		||||
    pub(crate) public_key: RsaPublicKey,
 | 
			
		||||
| 
						 | 
				
			
			@ -19,6 +21,7 @@ pub struct State {
 | 
			
		|||
    object_cache: Arc<RwLock<LruCache<IriString, IriString>>>,
 | 
			
		||||
    node_cache: NodeCache,
 | 
			
		||||
    breakers: Breakers,
 | 
			
		||||
    pub(crate) last_online: Arc<LastOnline>,
 | 
			
		||||
    pub(crate) db: Db,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -43,6 +46,7 @@ impl State {
 | 
			
		|||
            self.private_key.clone(),
 | 
			
		||||
            config.user_agent(),
 | 
			
		||||
            self.breakers.clone(),
 | 
			
		||||
            self.last_online.clone(),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -114,6 +118,7 @@ impl State {
 | 
			
		|||
            node_cache: NodeCache::new(db.clone()),
 | 
			
		||||
            breakers: Breakers::default(),
 | 
			
		||||
            db,
 | 
			
		||||
            last_online: Arc::new(LastOnline::empty()),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Ok(state)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										55
									
								
								src/db.rs
									
										
									
									
									
								
							
							
						
						
									
										55
									
								
								src/db.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -7,8 +7,13 @@ use rsa::{
 | 
			
		|||
    pkcs8::{DecodePrivateKey, EncodePrivateKey},
 | 
			
		||||
    RsaPrivateKey,
 | 
			
		||||
};
 | 
			
		||||
use sled::Tree;
 | 
			
		||||
use std::{collections::HashMap, sync::Arc, time::SystemTime};
 | 
			
		||||
use sled::{Batch, Tree};
 | 
			
		||||
use std::{
 | 
			
		||||
    collections::{BTreeMap, HashMap},
 | 
			
		||||
    sync::Arc,
 | 
			
		||||
    time::SystemTime,
 | 
			
		||||
};
 | 
			
		||||
use time::OffsetDateTime;
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug)]
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +33,7 @@ struct Inner {
 | 
			
		|||
    actor_id_info: Tree,
 | 
			
		||||
    actor_id_instance: Tree,
 | 
			
		||||
    actor_id_contact: Tree,
 | 
			
		||||
    last_seen: Tree,
 | 
			
		||||
    restricted_mode: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -247,6 +253,7 @@ impl Db {
 | 
			
		|||
                actor_id_info: db.open_tree("actor-id-info")?,
 | 
			
		||||
                actor_id_instance: db.open_tree("actor-id-instance")?,
 | 
			
		||||
                actor_id_contact: db.open_tree("actor-id-contact")?,
 | 
			
		||||
                last_seen: db.open_tree("last-seen")?,
 | 
			
		||||
                restricted_mode,
 | 
			
		||||
            }),
 | 
			
		||||
        })
 | 
			
		||||
| 
						 | 
				
			
			@ -254,7 +261,7 @@ impl Db {
 | 
			
		|||
 | 
			
		||||
    async fn unblock<T>(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: impl Fn(&Inner) -> Result<T, Error> + Send + 'static,
 | 
			
		||||
        f: impl FnOnce(&Inner) -> Result<T, Error> + Send + 'static,
 | 
			
		||||
    ) -> Result<T, Error>
 | 
			
		||||
    where
 | 
			
		||||
        T: Send + 'static,
 | 
			
		||||
| 
						 | 
				
			
			@ -266,6 +273,48 @@ impl Db {
 | 
			
		|||
        Ok(t)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) async fn mark_last_seen(
 | 
			
		||||
        &self,
 | 
			
		||||
        nodes: HashMap<String, OffsetDateTime>,
 | 
			
		||||
    ) -> Result<(), Error> {
 | 
			
		||||
        let mut batch = Batch::default();
 | 
			
		||||
 | 
			
		||||
        for (domain, datetime) in nodes {
 | 
			
		||||
            let datetime_string = serde_json::to_vec(&datetime)?;
 | 
			
		||||
 | 
			
		||||
            batch.insert(domain.as_bytes(), datetime_string);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.unblock(move |inner| inner.last_seen.apply_batch(batch).map_err(Error::from))
 | 
			
		||||
            .await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) async fn last_seen(
 | 
			
		||||
        &self,
 | 
			
		||||
    ) -> Result<BTreeMap<String, Option<OffsetDateTime>>, Error> {
 | 
			
		||||
        self.unblock(|inner| {
 | 
			
		||||
            let mut map = BTreeMap::new();
 | 
			
		||||
 | 
			
		||||
            for iri in inner.connected() {
 | 
			
		||||
                let Some(authority_str) = iri.authority_str() else {
 | 
			
		||||
                    continue;
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                if let Some(datetime) = inner.last_seen.get(authority_str)? {
 | 
			
		||||
                    map.insert(
 | 
			
		||||
                        authority_str.to_string(),
 | 
			
		||||
                        Some(serde_json::from_slice(&datetime)?),
 | 
			
		||||
                    );
 | 
			
		||||
                } else {
 | 
			
		||||
                    map.insert(authority_str.to_string(), None);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Ok(map)
 | 
			
		||||
        })
 | 
			
		||||
        .await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) async fn connected_ids(&self) -> Result<Vec<IriString>, Error> {
 | 
			
		||||
        self.unblock(|inner| Ok(inner.connected().collect())).await
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ mod deliver_many;
 | 
			
		|||
mod instance;
 | 
			
		||||
mod nodeinfo;
 | 
			
		||||
mod process_listeners;
 | 
			
		||||
mod record_last_online;
 | 
			
		||||
 | 
			
		||||
pub(crate) use self::{
 | 
			
		||||
    contact::QueryContact, deliver::Deliver, deliver_many::DeliverMany, instance::QueryInstance,
 | 
			
		||||
| 
						 | 
				
			
			@ -15,7 +16,7 @@ use crate::{
 | 
			
		|||
    config::Config,
 | 
			
		||||
    data::{ActorCache, MediaCache, NodeCache, State},
 | 
			
		||||
    error::{Error, ErrorKind},
 | 
			
		||||
    jobs::process_listeners::Listeners,
 | 
			
		||||
    jobs::{process_listeners::Listeners, record_last_online::RecordLastOnline},
 | 
			
		||||
    requests::Requests,
 | 
			
		||||
};
 | 
			
		||||
use background_jobs::{
 | 
			
		||||
| 
						 | 
				
			
			@ -62,6 +63,7 @@ pub(crate) fn create_workers(
 | 
			
		|||
    .register::<QueryInstance>()
 | 
			
		||||
    .register::<Listeners>()
 | 
			
		||||
    .register::<QueryContact>()
 | 
			
		||||
    .register::<RecordLastOnline>()
 | 
			
		||||
    .register::<apub::Announce>()
 | 
			
		||||
    .register::<apub::Follow>()
 | 
			
		||||
    .register::<apub::Forward>()
 | 
			
		||||
| 
						 | 
				
			
			@ -73,6 +75,7 @@ pub(crate) fn create_workers(
 | 
			
		|||
    .start_with_threads(parallelism);
 | 
			
		||||
 | 
			
		||||
    shared.every(Duration::from_secs(60 * 5), Listeners);
 | 
			
		||||
    shared.every(Duration::from_secs(60 * 10), RecordLastOnline);
 | 
			
		||||
 | 
			
		||||
    let job_server = JobServer::new(shared.queue_handle().clone());
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										28
									
								
								src/jobs/record_last_online.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/jobs/record_last_online.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
use crate::{error::Error, jobs::JobState};
 | 
			
		||||
use background_jobs::{ActixJob, Backoff};
 | 
			
		||||
use std::{future::Future, pin::Pin};
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
 | 
			
		||||
pub(crate) struct RecordLastOnline;
 | 
			
		||||
 | 
			
		||||
impl RecordLastOnline {
 | 
			
		||||
    #[tracing::instrument(skip(state))]
 | 
			
		||||
    async fn perform(self, state: JobState) -> Result<(), Error> {
 | 
			
		||||
        let nodes = state.state.last_online.take();
 | 
			
		||||
 | 
			
		||||
        state.state.db.mark_last_seen(nodes).await
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ActixJob for RecordLastOnline {
 | 
			
		||||
    type State = JobState;
 | 
			
		||||
    type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>;
 | 
			
		||||
 | 
			
		||||
    const NAME: &'static str = "relay::jobs::RecordLastOnline";
 | 
			
		||||
    const QUEUE: &'static str = "maintenance";
 | 
			
		||||
    const BACKOFF: Backoff = Backoff::Linear(1);
 | 
			
		||||
 | 
			
		||||
    fn run(self, state: Self::State) -> Self::Future {
 | 
			
		||||
        Box::pin(async move { self.perform(state).await.map_err(Into::into) })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								src/main.rs
									
										
									
									
									
								
							
							
						
						
									
										36
									
								
								src/main.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -144,6 +144,39 @@ async fn do_client_main(config: Config, args: Args) -> Result<(), anyhow::Error>
 | 
			
		|||
        println!("Updated lists");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if args.contacted() {
 | 
			
		||||
        let last_seen = admin::client::last_seen(&client, &config).await?;
 | 
			
		||||
 | 
			
		||||
        let mut report = String::from("Contacted:");
 | 
			
		||||
 | 
			
		||||
        if !last_seen.never.is_empty() {
 | 
			
		||||
            report += "\nNever seen:\n";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for domain in last_seen.never {
 | 
			
		||||
            report += "\t";
 | 
			
		||||
            report += &domain;
 | 
			
		||||
            report += "\n";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if !last_seen.last_seen.is_empty() {
 | 
			
		||||
            report += "\nSeen:\n";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (datetime, domains) in last_seen.last_seen {
 | 
			
		||||
            for domain in domains {
 | 
			
		||||
                report += "\t";
 | 
			
		||||
                report += &datetime.to_string();
 | 
			
		||||
                report += " - ";
 | 
			
		||||
                report += &domain;
 | 
			
		||||
                report += "\n";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        report += "\n";
 | 
			
		||||
        println!("{report}");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if args.list() {
 | 
			
		||||
        let (blocked, allowed, connected) = tokio::try_join!(
 | 
			
		||||
            admin::client::blocked(&client, &config),
 | 
			
		||||
| 
						 | 
				
			
			@ -258,7 +291,8 @@ async fn do_server_main(
 | 
			
		|||
                        .route("/allowed", web::get().to(admin::routes::allowed))
 | 
			
		||||
                        .route("/blocked", web::get().to(admin::routes::blocked))
 | 
			
		||||
                        .route("/connected", web::get().to(admin::routes::connected))
 | 
			
		||||
                        .route("/stats", web::get().to(admin::routes::stats)),
 | 
			
		||||
                        .route("/stats", web::get().to(admin::routes::stats))
 | 
			
		||||
                        .route("/last_seen", web::get().to(admin::routes::last_seen)),
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,7 @@
 | 
			
		|||
use crate::error::{Error, ErrorKind};
 | 
			
		||||
use crate::{
 | 
			
		||||
    data::LastOnline,
 | 
			
		||||
    error::{Error, ErrorKind},
 | 
			
		||||
};
 | 
			
		||||
use activitystreams::iri_string::types::IriString;
 | 
			
		||||
use actix_web::http::header::Date;
 | 
			
		||||
use awc::{error::SendRequestError, Client, ClientResponse};
 | 
			
		||||
| 
						 | 
				
			
			@ -146,6 +149,7 @@ pub(crate) struct Requests {
 | 
			
		|||
    private_key: RsaPrivateKey,
 | 
			
		||||
    config: Config,
 | 
			
		||||
    breakers: Breakers,
 | 
			
		||||
    last_online: Arc<LastOnline>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::fmt::Debug for Requests {
 | 
			
		||||
| 
						 | 
				
			
			@ -174,6 +178,7 @@ impl Requests {
 | 
			
		|||
        private_key: RsaPrivateKey,
 | 
			
		||||
        user_agent: String,
 | 
			
		||||
        breakers: Breakers,
 | 
			
		||||
        last_online: Arc<LastOnline>,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        Requests {
 | 
			
		||||
            client: Rc::new(RefCell::new(build_client(&user_agent))),
 | 
			
		||||
| 
						 | 
				
			
			@ -184,6 +189,7 @@ impl Requests {
 | 
			
		|||
            private_key,
 | 
			
		||||
            config: Config::default().mastodon_compat(),
 | 
			
		||||
            breakers,
 | 
			
		||||
            last_online,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -233,6 +239,7 @@ impl Requests {
 | 
			
		|||
            return Err(ErrorKind::Status(parsed_url.to_string(), res.status()).into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.last_online.mark_seen(&parsed_url);
 | 
			
		||||
        self.breakers.succeed(&parsed_url);
 | 
			
		||||
 | 
			
		||||
        Ok(res)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue