diff --git a/client/OrangeGuidanceTomestone.csproj b/client/OrangeGuidanceTomestone.csproj index 5b9d148..b50eb8c 100755 --- a/client/OrangeGuidanceTomestone.csproj +++ b/client/OrangeGuidanceTomestone.csproj @@ -58,17 +58,17 @@ - - - - + + + + - - - - + + + + diff --git a/client/Pinger.cs b/client/Pinger.cs new file mode 100644 index 0000000..fa2759d --- /dev/null +++ b/client/Pinger.cs @@ -0,0 +1,52 @@ +using System.Diagnostics; +using Dalamud.Game; +using Dalamud.Logging; +using OrangeGuidanceTomestone.Helpers; + +namespace OrangeGuidanceTomestone; + +internal class Pinger : IDisposable { + private Plugin Plugin { get; } + private Stopwatch Stopwatch { get; } = new(); + private int _waitSecs; + + internal Pinger(Plugin plugin) { + this.Plugin = plugin; + + this.Stopwatch.Start(); + + this.Plugin.Framework.Update += this.Ping; + } + + public void Dispose() { + this.Plugin.Framework.Update -= this.Ping; + } + + private void Ping(Framework framework) { + if (this.Stopwatch.Elapsed < TimeSpan.FromSeconds(this._waitSecs)) { + return; + } + + this.Stopwatch.Restart(); + + if (this.Plugin.Config.ApiKey == string.Empty) { + this._waitSecs = 5; + return; + } + + // 30 mins + this._waitSecs = 1_800; + + Task.Run(async () => { + var resp = await ServerHelper.SendRequest( + this.Plugin.Config.ApiKey, + HttpMethod.Post, + "/ping" + ); + + if (!resp.IsSuccessStatusCode) { + PluginLog.LogWarning($"Failed to ping, status {resp.StatusCode}"); + } + }); + } +} diff --git a/client/Plugin.cs b/client/Plugin.cs index ddcd118..aa7e624 100644 --- a/client/Plugin.cs +++ b/client/Plugin.cs @@ -40,6 +40,7 @@ public class Plugin : IDalamudPlugin { internal Messages Messages { get; } internal VfxReplacer VfxReplacer { get; } internal Commands Commands { get; } + internal Pinger Pinger { get; } internal string AvfxFilePath { get; } @@ -52,6 +53,7 @@ public class Plugin : IDalamudPlugin { this.Ui = new PluginUi(this); this.VfxReplacer = new VfxReplacer(this); this.Commands = new Commands(this); + this.Pinger = new Pinger(this); if (this.Config.ApiKey == string.Empty) { this.GetApiKey(); @@ -59,6 +61,7 @@ public class Plugin : IDalamudPlugin { } public void Dispose() { + this.Pinger.Dispose(); this.Commands.Dispose(); this.VfxReplacer.Dispose(); this.Ui.Dispose(); diff --git a/server/migrations/8_add_last_seen.sql b/server/migrations/8_add_last_seen.sql new file mode 100644 index 0000000..45667d4 --- /dev/null +++ b/server/migrations/8_add_last_seen.sql @@ -0,0 +1,6 @@ +alter table users + add column last_seen timestamp not null default 0; + +-- noinspection SqlWithoutWhere +update users +set last_seen = current_timestamp; diff --git a/server/src/main.rs b/server/src/main.rs index 7032a0e..1f6dd0e 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -19,7 +19,6 @@ mod message; mod web; mod util; mod config; -mod task; static MIGRATOR: Migrator = sqlx::migrate!(); diff --git a/server/src/message.rs b/server/src/message.rs index 97706dd..c2f2869 100644 --- a/server/src/message.rs +++ b/server/src/message.rs @@ -1,4 +1,3 @@ -use chrono::{Duration, Utc}; use serde::{Deserialize, Serialize}; use sqlx::types::chrono::NaiveDateTime; use uuid::Uuid; @@ -45,13 +44,8 @@ pub struct RetrievedMessage { pub created: NaiveDateTime, #[serde(skip)] pub user: String, -} - -impl RetrievedMessage { - pub fn adjusted_time_since_posting(&self) -> Duration { - let score = (self.positive_votes - self.negative_votes).max(0); - Utc::now().naive_utc().signed_duration_since(self.created) - Duration::weeks(score as i64) - } + #[serde(skip)] + pub last_seen_minutes: i64, } #[derive(Debug, Serialize)] diff --git a/server/src/task.rs b/server/src/task.rs deleted file mode 100644 index d0835d6..0000000 --- a/server/src/task.rs +++ /dev/null @@ -1 +0,0 @@ -mod delete_old; diff --git a/server/src/task/delete_old.rs b/server/src/task/delete_old.rs deleted file mode 100644 index 9fa0768..0000000 --- a/server/src/task/delete_old.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::sync::Arc; -use crate::State; - -pub fn delete_old(state: Arc) { - tokio::task::spawn(async move { - - }); -} diff --git a/server/src/web.rs b/server/src/web.rs index 2afccb3..b20e38b 100644 --- a/server/src/web.rs +++ b/server/src/web.rs @@ -17,6 +17,7 @@ mod vote; mod get_mine; mod get_message; mod claim; +mod ping; pub fn routes(state: Arc) -> BoxedFilter<(impl Reply, )> { register::register(Arc::clone(&state)) @@ -28,6 +29,7 @@ pub fn routes(state: Arc) -> BoxedFilter<(impl Reply, )> { .or(get_location::get_location(Arc::clone(&state))) .or(get_mine::get_mine(Arc::clone(&state))) .or(claim::claim(Arc::clone(&state))) + .or(ping::ping(Arc::clone(&state))) .recover(handle_rejection) .boxed() } diff --git a/server/src/web/get_location.rs b/server/src/web/get_location.rs index a9c1732..7718512 100644 --- a/server/src/web/get_location.rs +++ b/server/src/web/get_location.rs @@ -38,10 +38,12 @@ async fn logic(state: Arc, id: i64, location: u32) -> Result, id: i64) { // just count nearby messages. this is O(n^2) but alternatives are hard let mut ids = Vec::with_capacity(messages.len()); for a in messages.iter() { - let mut nearby = 0; + if a.user == id_str { + // always include own messages + ids.push(a.id.clone()); + continue; + } + if a.last_seen_minutes >= 35 { + continue; + } + + let mut nearby = 0; for b in messages.iter() { if a.id == b.id { continue; @@ -81,36 +92,39 @@ fn filter_messages(messages: &mut Vec, id: i64) { nearby += 1; } - if a.user == id_str || nearby <= 2 { - // always include groups of three or fewer - ids.push(a.id.clone()); - continue; - } + let (numerator, denominator) = if nearby <= 2 { + // no need to do calculations for groups of three or fewer + (17, 20) + } else { + let time_since_creation = a.created.signed_duration_since(Utc::now().naive_utc()); + let brand_new = time_since_creation < Duration::minutes(30); + let new = time_since_creation < Duration::hours(2); - let score = (a.positive_votes - a.negative_votes).max(0); - let time_since_creation = a.adjusted_time_since_posting(); - if time_since_creation > Duration::weeks(1) { - continue; - } + let mut numerator = 1; + if brand_new { + numerator = nearby; + } else if new { + numerator += (nearby / 3).min(1); + } - // originally thresholds were 6 hours and 2 days - let brand_new = time_since_creation < Duration::minutes(30); - let new = time_since_creation < Duration::hours(2); + let score = (a.positive_votes - a.negative_votes).max(0); + if score > 0 { + let pad = score as f32 / nearby as f32; + let rounded = pad.round() as u32; + numerator += rounded.max(nearby / 2); + } - let mut numerator = 1; - if brand_new { - numerator = nearby; - } else if new { - numerator += (nearby / 3).min(1); - } + nearby *= 2; - if score > 0 { - let pad = score as f32 / nearby as f32; - let rounded = pad.round() as u32; - numerator += rounded.max(nearby / 2); - } + if numerator * 20 > nearby * 17 { + numerator = 17; + nearby = 20; + } - if rand::thread_rng().gen_ratio(numerator.min(nearby), nearby * 2) { + (numerator, nearby) + }; + + if rand::thread_rng().gen_ratio(numerator.min(denominator), denominator) { ids.push(a.id.clone()); } } diff --git a/server/src/web/ping.rs b/server/src/web/ping.rs new file mode 100644 index 0000000..ddacab5 --- /dev/null +++ b/server/src/web/ping.rs @@ -0,0 +1,32 @@ +use std::sync::Arc; + +use anyhow::Context; +use warp::{Filter, Rejection, Reply}; +use warp::filters::BoxedFilter; + +use crate::State; +use crate::web::AnyhowRejection; + +pub fn ping(state: Arc) -> BoxedFilter<(impl Reply, )> { + warp::post() + .and(warp::path("ping")) + .and(warp::path::end()) + .and(super::get_id(Arc::clone(&state))) + .and_then(move |(id, _)| logic(Arc::clone(&state), id)) + .boxed() +} + +async fn logic(state: Arc, id: i64) -> Result { + sqlx::query!( + // language=sqlite + "update users set last_seen = current_timestamp where id = ?", + id, + ) + .execute(&state.db) + .await + .context("could not update user") + .map_err(AnyhowRejection) + .map_err(warp::reject::custom)?; + + Ok(warp::reply()) +} diff --git a/server/src/web/register.rs b/server/src/web/register.rs index 31fd7f9..261c4dc 100644 --- a/server/src/web/register.rs +++ b/server/src/web/register.rs @@ -21,7 +21,7 @@ async fn logic(state: Arc) -> Result { let hashed = crate::util::hash(&auth); sqlx::query!( // language=sqlite - "insert into users (auth) values (?)", + "insert into users (auth, last_seen) values (?, current_timestamp)", hashed, ) .execute(&state.db)