diff --git a/docs/admin_reference.md b/docs/admin_reference.md index 1c9d4fb0..7f23aee2 100644 --- a/docs/admin_reference.md +++ b/docs/admin_reference.md @@ -1078,7 +1078,10 @@ Respecting homeservers put this file here for listing administration, moderation * `delete` — - Deletes a single media file from our database and on the filesystem via a single MXC URL or event ID (not redacted) * `delete-list` — - Deletes a codeblock list of MXC URLs from our database and on the filesystem. This will always ignore errors -* `delete-past-remote-media` — - Deletes all remote (and optionally local) media created before or after [duration] time using filesystem metadata first created at date, or fallback to last modified date. This will always ignore errors by default +* `delete-past-remote-media` — Deletes all remote (and optionally local) media created before/after +[duration] ago, using filesystem metadata first created at date, or +fallback to last modified date. This will always ignore errors by +default. * `delete-all-from-user` — - Deletes all the local media from a local user on our server. This will always ignore errors by default * `delete-all-from-server` — - Deletes all remote media from the specified remote server. This will always ignore errors by default * `get-file-info` — @@ -1110,13 +1113,25 @@ Respecting homeservers put this file here for listing administration, moderation ## `admin media delete-past-remote-media` -- Deletes all remote (and optionally local) media created before or after [duration] time using filesystem metadata first created at date, or fallback to last modified date. This will always ignore errors by default +Deletes all remote (and optionally local) media created before/after +[duration] ago, using filesystem metadata first created at date, or +fallback to last modified date. This will always ignore errors by +default. + +* Examples: + * Delete all remote media older than a year: + + `!admin media delete-past-remote-media -b 1y` + + * Delete all remote and local media from 3 days ago, up until now: + + `!admin media delete-past-remote-media -a 3d --yes-i-want-to-delete-local-media` **Usage:** `admin media delete-past-remote-media [OPTIONS] ` ###### **Arguments:** -* `` — - The relative time (e.g. 30s, 5m, 7d) within which to search +* `` — - The relative time (e.g. 30s, 5m, 7d) from now within which to search ###### **Options:** diff --git a/src/admin/media/commands.rs b/src/admin/media/commands.rs index 7aed28db..ab857ccd 100644 --- a/src/admin/media/commands.rs +++ b/src/admin/media/commands.rs @@ -2,7 +2,8 @@ use std::time::Duration; use conduwuit::{ Err, Result, debug, debug_info, debug_warn, error, info, trace, - utils::time::parse_timepoint_ago, warn, + utils::time::{TimeDirection, parse_timepoint_ago}, + warn, }; use conduwuit_service::media::Dim; use ruma::{Mxc, OwnedEventId, OwnedMxcUri, OwnedServerName}; @@ -235,14 +236,19 @@ pub(super) async fn delete_past_remote_media( } assert!(!(before && after), "--before and --after should not be specified together"); - let duration = parse_timepoint_ago(&duration)?; + let direction = if after { + TimeDirection::After + } else { + TimeDirection::Before + }; + + let time_boundary = parse_timepoint_ago(&duration)?; let deleted_count = self .services .media - .delete_all_remote_media_at_after_time( - duration, - before, - after, + .delete_all_media_within_timeframe( + time_boundary, + direction, yes_i_want_to_delete_local_media, ) .await?; diff --git a/src/admin/media/mod.rs b/src/admin/media/mod.rs index 66d49959..07f570a5 100644 --- a/src/admin/media/mod.rs +++ b/src/admin/media/mod.rs @@ -27,12 +27,24 @@ pub enum MediaCommand { /// filesystem. This will always ignore errors. DeleteList, - /// - Deletes all remote (and optionally local) media created before or - /// after [duration] time using filesystem metadata first created at date, - /// or fallback to last modified date. This will always ignore errors by - /// default. + /// Deletes all remote (and optionally local) media created before/after + /// [duration] ago, using filesystem metadata first created at date, or + /// fallback to last modified date. This will always ignore errors by + /// default. + /// + /// * Examples: + /// * Delete all remote media older than a year: + /// + /// `!admin media delete-past-remote-media -b 1y` + /// + /// * Delete all remote and local media from 3 days ago, up until now: + /// + /// `!admin media delete-past-remote-media -a 3d + /// --yes-i-want-to-delete-local-media` + #[command(verbatim_doc_comment)] DeletePastRemoteMedia { - /// - The relative time (e.g. 30s, 5m, 7d) within which to search + /// - The relative time (e.g. 30s, 5m, 7d) from now within which to + /// search duration: String, /// - Only delete media created before [duration] ago diff --git a/src/core/utils/tests.rs b/src/core/utils/tests.rs index 05a0655b..69fc1a3b 100644 --- a/src/core/utils/tests.rs +++ b/src/core/utils/tests.rs @@ -276,3 +276,22 @@ async fn set_intersection_sorted_stream2() { .await; assert!(r.eq(&["ccc", "ggg", "iii"])); } + +#[test] +fn is_within_bounds() { + use std::time::{Duration, SystemTime}; + + use utils::time::{TimeDirection, is_within_bounds}; + + let now = SystemTime::now(); + let yesterday = now - Duration::from_secs(86400); + assert!(is_within_bounds(yesterday, now, TimeDirection::Before)); + assert!(!is_within_bounds(yesterday, now, TimeDirection::After)); + + let tomorrow = now + Duration::from_secs(86400); + assert!(is_within_bounds(tomorrow, now, TimeDirection::After)); + assert!(!is_within_bounds(tomorrow, now, TimeDirection::Before)); + + assert!(is_within_bounds(now, now, TimeDirection::Before)); + assert!(is_within_bounds(now, now, TimeDirection::After)); +} diff --git a/src/core/utils/time.rs b/src/core/utils/time.rs index 394e08cb..b8fad41d 100644 --- a/src/core/utils/time.rs +++ b/src/core/utils/time.rs @@ -126,3 +126,24 @@ pub enum Unit { Micros(u128), Nanos(u128), } + +#[derive(Eq, PartialEq, Clone, Copy, Debug)] +pub enum TimeDirection { + Before, + After, +} + +/// Checks if `item_time` is before or after `time_boundary`. +/// If both times are the same, it will return true for both directions, as the +/// matching is inclusive. +#[must_use] +pub fn is_within_bounds( + item_time: SystemTime, + time_boundary: SystemTime, + direction: TimeDirection, +) -> bool { + match direction { + | TimeDirection::Before => item_time <= time_boundary, + | TimeDirection::After => item_time >= time_boundary, + } +} diff --git a/src/service/media/mod.rs b/src/service/media/mod.rs index b52fd320..aea52cea 100644 --- a/src/service/media/mod.rs +++ b/src/service/media/mod.rs @@ -11,7 +11,10 @@ use async_trait::async_trait; use base64::{Engine as _, engine::general_purpose}; use conduwuit::{ Err, Result, Server, debug, debug_error, debug_info, debug_warn, err, error, trace, - utils::{self, MutexMap}, + utils::{ + self, MutexMap, + time::{self, TimeDirection}, + }, warn, }; use ruma::{Mxc, OwnedMxcUri, UserId, http_headers::ContentDisposition}; @@ -226,13 +229,12 @@ impl Service { Ok(mxcs) } - /// Deletes all remote only media files in the given at or after - /// time/duration. Returns a usize with the amount of media files deleted. - pub async fn delete_all_remote_media_at_after_time( + /// Deletes all media files in the given time frame. + /// Returns a usize with the amount of media files deleted. + pub async fn delete_all_media_within_timeframe( &self, - time: SystemTime, - before: bool, - after: bool, + time_boundary: SystemTime, + direction: TimeDirection, yes_i_want_to_delete_local_media: bool, ) -> Result { let all_keys = self.db.get_all_media_keys().await; @@ -299,18 +301,14 @@ impl Service { debug!("File created at: {file_created_at:?}"); - if file_created_at >= time && before { + if time::is_within_bounds(file_created_at, time_boundary, direction) { debug!( - "File is within (before) user duration, pushing to list of file paths and \ - keys to delete." - ); - remote_mxcs.push(mxc.to_string()); - } else if file_created_at <= time && after { - debug!( - "File is not within (after) user duration, pushing to list of file paths \ - and keys to delete." + "File is within bounds ({direction:?} {time_boundary:?}), pushing to list \ + of file paths and keys to delete.", ); remote_mxcs.push(mxc.to_string()); + } else { + debug!("File is outside bounds ({direction:?} {time_boundary:?}), ignoring."); } } @@ -318,7 +316,7 @@ impl Service { return Err!(Database("Did not found any eligible MXCs to delete.")); } - debug_info!("Deleting media now in the past {time:?}"); + debug_info!("Deleting media now {direction:?} {time_boundary:?}"); let mut deletion_count: usize = 0;