Skip to content
This repository was archived by the owner on Mar 24, 2024. It is now read-only.

Commit 0ca4e6a

Browse files
committed
improve notification clearing
1 parent 9d23e7e commit 0ca4e6a

File tree

6 files changed

+100
-54
lines changed

6 files changed

+100
-54
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "highlights"
3-
version = "2.1.5"
3+
version = "2.1.6"
44
authors = ["ThatsNoMoon <[email protected]>"]
55
repository = "https://github.com/ThatsNoMoon/highlights"
66
license = "OSL-3.0"

src/bot/highlighting.rs

Lines changed: 63 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
//! Functions for sending, editing, and deleting notifications.
55
66
use std::{
7-
cmp::min, collections::HashMap, fmt::Write as _, ops::Range, time::Duration,
7+
cmp::min,
8+
collections::HashMap,
9+
fmt::Write as _,
10+
ops::Range,
11+
time::{Duration, SystemTime, UNIX_EPOCH},
812
};
913

1014
use anyhow::{anyhow, bail, Context as _, Error, Result};
@@ -35,7 +39,7 @@ use tracing::{debug, error, info_span};
3539
use crate::{
3640
bot::util::{followup_eph, user_can_read_channel},
3741
db::{Ignore, Keyword, Notification, UserState, UserStateKind},
38-
global::{EMBED_COLOR, ERROR_COLOR, NOTIFICATION_RETRIES},
42+
global::{DISCORD_EPOCH, EMBED_COLOR, ERROR_COLOR, NOTIFICATION_RETRIES},
3943
settings::settings,
4044
};
4145

@@ -76,6 +80,16 @@ pub(crate) async fn should_notify_keyword(
7680
keyword: &Keyword,
7781
ignores: &[Ignore],
7882
) -> Result<bool> {
83+
if let Some(lifetime) = settings().behavior.notification_lifetime {
84+
let creation = (message.id.0 >> 22) + DISCORD_EPOCH;
85+
let now =
86+
SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() as u64;
87+
let age = Duration::from_millis(now.saturating_sub(creation));
88+
if age > lifetime {
89+
return Ok(false);
90+
}
91+
}
92+
7993
if message
8094
.mentions
8195
.iter()
@@ -743,37 +757,58 @@ async fn clear_old_notifications(
743757
lifetime: Duration,
744758
) -> Result<()> {
745759
debug!("Clearing old notifications");
746-
Notification::old_notifications(lifetime)
747-
.await?
748-
.into_iter()
749-
.map(|notification| {
750-
clear_sent_notification(
751-
ctx,
752-
notification.user_id,
753-
notification.notification_message,
754-
"*Notification expired*",
755-
)
756-
.or_else(|e| async move {
757-
match e.downcast_ref::<SerenityError>() {
758-
Some(SerenityError::Http(inner)) => match &**inner {
759-
HttpError::UnsuccessfulRequest(ErrorResponse {
760-
status_code: StatusCode::NOT_FOUND,
761-
..
762-
}) => Ok(()),
760+
let cutoff_time = SystemTime::now() - lifetime;
763761

762+
loop {
763+
let notifications =
764+
Notification::notifications_before(5, cutoff_time).await?;
765+
766+
if notifications.is_empty() {
767+
break Ok(());
768+
}
769+
770+
let sent_ids = notifications
771+
.iter()
772+
.map(|n| n.notification_message)
773+
.collect::<Vec<_>>();
774+
775+
debug!("Clearing {} notifications", notifications.len());
776+
777+
let wait_cycle = sleep(Duration::from_secs(2));
778+
779+
notifications
780+
.iter()
781+
.map(|notification| {
782+
clear_sent_notification(
783+
ctx,
784+
notification.user_id,
785+
notification.notification_message,
786+
"*Notification expired*",
787+
)
788+
.or_else(|e| async move {
789+
match e.downcast_ref::<SerenityError>() {
790+
Some(SerenityError::Http(inner)) => match &**inner {
791+
HttpError::UnsuccessfulRequest(ErrorResponse {
792+
status_code: StatusCode::NOT_FOUND,
793+
..
794+
}) => Ok(()),
795+
796+
_ => Err(e),
797+
},
764798
_ => Err(e),
765-
},
766-
_ => Err(e),
767-
}
799+
}
800+
})
768801
})
769-
})
770-
.collect::<FuturesUnordered<_>>()
771-
.try_for_each(|_| async { Ok(()) })
772-
.await?;
802+
.collect::<FuturesUnordered<_>>()
803+
.try_for_each(|_| async { Ok(()) })
804+
.await?;
773805

774-
Notification::delete_old_notifications(lifetime).await?;
806+
Notification::delete_notifications(sent_ids).await?;
775807

776-
Ok(())
808+
debug!("Waiting before clearing more notifications");
809+
810+
wait_cycle.await;
811+
}
777812
}
778813

779814
#[cfg(test)]

src/db/notification.rs

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
//! Handling for sent notification messages.
55
6-
use std::time::{Duration, SystemTime, UNIX_EPOCH};
6+
use std::time::{SystemTime, UNIX_EPOCH};
77

88
use anyhow::Result;
99
use futures_util::TryStreamExt;
@@ -12,13 +12,13 @@ use sea_orm::{
1212
DeriveActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey,
1313
DeriveRelation, EntityTrait, EnumIter, PrimaryKeyTrait,
1414
},
15-
ColumnTrait, IntoActiveModel, QueryFilter,
15+
ColumnTrait, Condition, IntoActiveModel, QueryFilter, QueryOrder,
16+
QuerySelect,
1617
};
1718
use serenity::model::id::{MessageId, UserId};
1819

1920
use super::{connection, DbInt, IdDbExt};
20-
21-
const DISCORD_EPOCH: u64 = 1420070400000;
21+
use crate::global::DISCORD_EPOCH;
2222

2323
#[derive(
2424
Clone, Debug, PartialEq, Eq, DeriveEntityModel, DeriveActiveModelBehavior,
@@ -111,11 +111,14 @@ impl Notification {
111111

112112
/// Gets notifications older than a certain duration from the DB.
113113
#[tracing::instrument]
114-
pub(crate) async fn old_notifications(
115-
age: Duration,
114+
pub(crate) async fn notifications_before(
115+
count: u64,
116+
time: SystemTime,
116117
) -> Result<Vec<Notification>> {
117118
Entity::find()
118-
.filter(Column::OriginalMessage.lte(age_to_oldest_snowflake(age)?))
119+
.filter(Column::OriginalMessage.lte(time_to_max_snowflake(time)?))
120+
.order_by_asc(Column::OriginalMessage)
121+
.limit(count)
119122
.stream(connection())
120123
.await?
121124
.map_err(Into::into)
@@ -124,26 +127,36 @@ impl Notification {
124127
.await
125128
}
126129

127-
/// Deletes notifications older than a certain duration from the DB.
128-
#[tracing::instrument]
129-
pub(crate) async fn delete_old_notifications(age: Duration) -> Result<()> {
130+
/// Deletes a list of notifications from the DB.
131+
#[tracing::instrument(skip_all)]
132+
pub(crate) async fn delete_notifications(
133+
message_ids: impl IntoIterator<Item = MessageId>,
134+
) -> Result<()> {
130135
Entity::delete_many()
131-
.filter(Column::OriginalMessage.lte(age_to_oldest_snowflake(age)?))
136+
.filter(message_ids.into_iter().fold(
137+
Condition::any(),
138+
|cond, id| {
139+
cond.add(Column::NotificationMessage.eq(id.into_db()))
140+
},
141+
))
132142
.exec(connection())
133143
.await?;
134144

135145
Ok(())
136146
}
137147
}
138148

139-
fn age_to_oldest_snowflake(age: Duration) -> Result<u64> {
140-
let millis = age.as_millis() as u64;
141-
let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() as u64;
142-
let oldest_unix = now - millis;
143-
let oldest_discord = oldest_unix - DISCORD_EPOCH;
149+
fn time_to_min_snowflake(time: SystemTime) -> Result<u64> {
150+
let unix = time.duration_since(UNIX_EPOCH)?.as_millis() as u64;
151+
let oldest_discord = unix - DISCORD_EPOCH;
144152
Ok(oldest_discord << 22)
145153
}
146154

155+
fn time_to_max_snowflake(time: SystemTime) -> Result<u64> {
156+
let min = time_to_min_snowflake(time)?;
157+
Ok(min | (!0 >> 22))
158+
}
159+
147160
impl From<Model> for Notification {
148161
fn from(model: Model) -> Self {
149162
Self {

src/global.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ pub(crate) const NOTIFICATION_RETRIES: u8 = 5;
1010
pub(crate) const EMBED_COLOR: u32 = 0xefff47;
1111
/// Color of embeds reporting an error to the user.
1212
pub(crate) const ERROR_COLOR: u32 = 0xff4747;
13+
14+
pub(crate) const DISCORD_EPOCH: u64 = 1420070400000;

src/settings.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,10 @@ mod user_address {
9494
where
9595
E: de::Error,
9696
{
97-
let socket_addr = v
98-
.to_socket_addrs()
99-
.map_err(E::custom)?
100-
.into_iter()
101-
.next()
102-
.ok_or_else(|| {
103-
E::custom("provided host did not resolve to an address")
104-
})?;
97+
let socket_addr =
98+
v.to_socket_addrs().map_err(E::custom)?.next().ok_or_else(
99+
|| E::custom("provided host did not resolve to an address"),
100+
)?;
105101

106102
Ok(UserAddress { socket_addr })
107103
}

0 commit comments

Comments
 (0)