/* Hurry Curry! - a game about cooking Copyright 2024 metamuffin This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License only. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ use crate::server::Server; use anyhow::{bail, Result}; use hurrycurry_protocol::{registry::Submission, VERSION}; use log::{error, info, warn}; use rand::random; use reqwest::{header::USER_AGENT, Client, Url}; use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr}, str::FromStr, sync::Arc, time::Duration, }; use tokio::{sync::RwLock, time::interval}; const REGISTRY_URI: &'static str = "https://hurrycurry-registry.metamuffin.org"; pub struct Register { name: String, port: u16, register_uri: Option, state: Arc>, ip4_client: Option, ip6_client: Option, secret: u128, players: usize, } impl Register { pub fn new( name: String, port: u16, register_uri: Option, state: Arc>, no6: bool, no4: bool, ) -> Self { Self { name, register_uri, players: 0, port, secret: random(), state, ip4_client: if no4 { None } else { Some( Client::builder() .local_address(IpAddr::V4(Ipv4Addr::UNSPECIFIED)) .build() .unwrap(), ) }, ip6_client: if no6 { None } else { Some( Client::builder() .local_address(IpAddr::V6(Ipv6Addr::UNSPECIFIED)) .build() .unwrap(), ) }, } } pub async fn register_loop(mut self) { let mut interval = interval(Duration::from_secs(60)); loop { interval.tick().await; self.players = self.state.read().await.count_chefs(); if let Err(e) = self.register().await { warn!("register error: {e}") } } } pub async fn register(&self) -> Result<()> { info!("register update"); let [v4, v6] = [&self.ip4_client, &self.ip6_client].map(|client| async { if let Some(r) = client.as_ref().map(|client| { self.register_with( client, self.register_uri .clone() .unwrap_or(format!("ws://0.0.0.0:{}", self.port)), ) }) { Some(r.await) } else { None } }); #[rustfmt::skip] match tokio::join!(v4, v6) { (None, None) => info!("no registration sent"), (Some(Ok(())), None) => info!("Registration successful (IPv4)"), (None, Some(Ok(()))) => info!("Registration successful (IPv6)"), (Some(Ok(())), Some(Ok(()))) => info!("Registration successful (IPv4 + IPv6)"), (Some(Err(e)), None) => info!("Registration failed (IPv4): {e}"), (None, Some(Err(e))) => error!("Registration failed (IPv6): {e}"), (Some(Err(e1)), Some(Err(e2))) => error!("Registration failed (IPv4 + IPv6): {e1}, {e2}"), (Some(Ok(())), Some(Err(e))) => warn!("Registration partially failed (IPv4 ok, IPv6 fail): {e}"), (Some(Err(e)), Some(Ok(()))) => warn!("Registration partially failed (IPv4 fail, IPv6 ok): {e}"), } Ok(()) } // TODO ip v6 pub async fn register_with(&self, client: &Client, uri: String) -> Result<()> { let res = client .post(Url::from_str(&format!("{REGISTRY_URI}/v1/register")).unwrap()) .header( USER_AGENT, format!("hurrycurry-server {}", env!("CARGO_PKG_VERSION")), ) .json(&Submission { last_game: 0, name: self.name.clone(), uri, players: self.players, secret: self.secret, version: VERSION, }) .send() .await?; let r = res.text().await?; if r == "ok" { Ok(()) } else { bail!("{r}"); } } }