fix(#1134): Update docs and implementation of admin media delete-past-remote-media (#1136)

Reviewed-on: https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1136
Co-authored-by: Odd E. Ebbesen <git@oddware.net>
Co-committed-by: Odd E. Ebbesen <git@oddware.net>
This commit is contained in:
Odd E. Ebbesen
2025-10-27 12:31:25 +00:00
committed by Jade Ellis
parent 910a3182f7
commit cb7875e479
6 changed files with 102 additions and 31 deletions
+18 -3
View File
@@ -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` — - 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-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-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 * `delete-all-from-server` — - Deletes all remote media from the specified remote server. This will always ignore errors by default
* `get-file-info` * `get-file-info`
@@ -1110,13 +1113,25 @@ Respecting homeservers put this file here for listing administration, moderation
## `admin media delete-past-remote-media` ## `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] <DURATION>` **Usage:** `admin media delete-past-remote-media [OPTIONS] <DURATION>`
###### **Arguments:** ###### **Arguments:**
* `<DURATION>` — - The relative time (e.g. 30s, 5m, 7d) within which to search * `<DURATION>` — - The relative time (e.g. 30s, 5m, 7d) from now within which to search
###### **Options:** ###### **Options:**
+12 -6
View File
@@ -2,7 +2,8 @@ use std::time::Duration;
use conduwuit::{ use conduwuit::{
Err, Result, debug, debug_info, debug_warn, error, info, trace, 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 conduwuit_service::media::Dim;
use ruma::{Mxc, OwnedEventId, OwnedMxcUri, OwnedServerName}; 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"); 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 let deleted_count = self
.services .services
.media .media
.delete_all_remote_media_at_after_time( .delete_all_media_within_timeframe(
duration, time_boundary,
before, direction,
after,
yes_i_want_to_delete_local_media, yes_i_want_to_delete_local_media,
) )
.await?; .await?;
+17 -5
View File
@@ -27,12 +27,24 @@ pub enum MediaCommand {
/// filesystem. This will always ignore errors. /// filesystem. This will always ignore errors.
DeleteList, DeleteList,
/// - Deletes all remote (and optionally local) media created before or /// Deletes all remote (and optionally local) media created before/after
/// after [duration] time using filesystem metadata first created at date, /// [duration] ago, using filesystem metadata first created at date, or
/// or fallback to last modified date. This will always ignore errors by /// fallback to last modified date. This will always ignore errors by
/// default. /// 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 { 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, duration: String,
/// - Only delete media created before [duration] ago /// - Only delete media created before [duration] ago
+19
View File
@@ -276,3 +276,22 @@ async fn set_intersection_sorted_stream2() {
.await; .await;
assert!(r.eq(&["ccc", "ggg", "iii"])); 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));
}
+21
View File
@@ -126,3 +126,24 @@ pub enum Unit {
Micros(u128), Micros(u128),
Nanos(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,
}
}
+15 -17
View File
@@ -11,7 +11,10 @@ use async_trait::async_trait;
use base64::{Engine as _, engine::general_purpose}; use base64::{Engine as _, engine::general_purpose};
use conduwuit::{ use conduwuit::{
Err, Result, Server, debug, debug_error, debug_info, debug_warn, err, error, trace, Err, Result, Server, debug, debug_error, debug_info, debug_warn, err, error, trace,
utils::{self, MutexMap}, utils::{
self, MutexMap,
time::{self, TimeDirection},
},
warn, warn,
}; };
use ruma::{Mxc, OwnedMxcUri, UserId, http_headers::ContentDisposition}; use ruma::{Mxc, OwnedMxcUri, UserId, http_headers::ContentDisposition};
@@ -226,13 +229,12 @@ impl Service {
Ok(mxcs) Ok(mxcs)
} }
/// Deletes all remote only media files in the given at or after /// Deletes all media files in the given time frame.
/// time/duration. Returns a usize with the amount of media files deleted. /// Returns a usize with the amount of media files deleted.
pub async fn delete_all_remote_media_at_after_time( pub async fn delete_all_media_within_timeframe(
&self, &self,
time: SystemTime, time_boundary: SystemTime,
before: bool, direction: TimeDirection,
after: bool,
yes_i_want_to_delete_local_media: bool, yes_i_want_to_delete_local_media: bool,
) -> Result<usize> { ) -> Result<usize> {
let all_keys = self.db.get_all_media_keys().await; let all_keys = self.db.get_all_media_keys().await;
@@ -299,18 +301,14 @@ impl Service {
debug!("File created at: {file_created_at:?}"); 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!( debug!(
"File is within (before) user duration, pushing to list of file paths and \ "File is within bounds ({direction:?} {time_boundary:?}), pushing to list \
keys to delete." 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."
); );
remote_mxcs.push(mxc.to_string()); 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.")); 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; let mut deletion_count: usize = 0;