add admin command to delete all remote media from a specific server

Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
strawberry 2024-08-25 16:10:07 -04:00 committed by Jason Volk
parent 8923c9a227
commit fb49e37067
3 changed files with 105 additions and 6 deletions

View file

@ -1,5 +1,5 @@
use conduit::{debug, info, Result};
use ruma::{events::room::message::RoomMessageEventContent, EventId, MxcUri};
use conduit::{debug, info, trace, warn, Result};
use ruma::{events::room::message::RoomMessageEventContent, EventId, Mxc, MxcUri, ServerName};
use crate::{admin_command, utils::parse_local_user_id};
@ -201,3 +201,59 @@ pub(super) async fn delete_all_from_user(&self, username: String, force: bool) -
"Deleted {deleted_count} total files.",
)))
}
#[admin_command]
pub(super) async fn delete_all_from_server(
&self, server_name: Box<ServerName>, force: bool,
) -> Result<RoomMessageEventContent> {
if server_name == self.services.globals.server_name() {
return Ok(RoomMessageEventContent::text_plain("This command only works for remote media."));
}
let Ok(all_mxcs) = self.services.media.get_all_mxcs().await else {
return Ok(RoomMessageEventContent::text_plain("Failed to get MXC URIs from our database"));
};
let mut deleted_count: usize = 0;
for mxc in all_mxcs {
let mxc_server_name = match mxc.server_name() {
Ok(server_name) => server_name,
Err(e) => {
if force {
warn!("Failed to parse MXC {mxc} server name from database, ignoring error and skipping: {e}");
continue;
}
return Ok(RoomMessageEventContent::text_plain(format!(
"Failed to parse MXC {mxc} server name from database: {e}",
)));
},
};
if mxc_server_name != server_name || self.services.globals.server_is_ours(mxc_server_name) {
trace!("skipping MXC URI {mxc}");
continue;
}
let mxc: Mxc<'_> = mxc.as_str().try_into()?;
match self.services.media.delete(&mxc).await {
Ok(()) => {
deleted_count = deleted_count.saturating_add(1);
},
Err(e) => {
if force {
warn!("Failed to delete {mxc}, ignoring error and skipping: {e}");
continue;
}
return Ok(RoomMessageEventContent::text_plain(format!("Failed to delete MXC {mxc}: {e}")));
},
}
}
Ok(RoomMessageEventContent::text_plain(format!(
"Deleted {deleted_count} total files.",
)))
}

View file

@ -2,7 +2,7 @@ mod commands;
use clap::Subcommand;
use conduit::Result;
use ruma::{EventId, MxcUri};
use ruma::{EventId, MxcUri, ServerName};
use crate::admin_command_dispatch;
@ -46,4 +46,13 @@ pub(super) enum MediaCommand {
#[arg(short, long)]
force: bool,
},
/// - Deletes all remote media from the specified remote server
DeleteAllFromServer {
server_name: Box<ServerName>,
/// Continues deleting media if an undeletable object is found
#[arg(short, long)]
force: bool,
},
}

View file

@ -159,11 +159,43 @@ impl Service {
}
}
/// Gets all the MXC URIs in our media database
pub async fn get_all_mxcs(&self) -> Result<Vec<OwnedMxcUri>> {
let all_keys = self.db.get_all_media_keys();
let mut mxcs = Vec::with_capacity(all_keys.len());
for key in all_keys {
debug!("Full MXC key from database: {key:?}");
// we need to get the MXC URL from the first part of the key (the first 0xff /
// 255 push). this is all necessary because of conduit using magic keys for
// media
let mut parts = key.split(|&b| b == 0xFF);
let mxc = parts
.next()
.map(|bytes| {
utils::string_from_bytes(bytes)
.map_err(|e| err!(Database(error!("Failed to parse MXC unicode bytes from our database: {e}"))))
})
.transpose()?;
let Some(mxc_s) = mxc else {
return Err!(Database("Parsed MXC URL unicode bytes from database but still is None"));
};
debug_info!("Parsed MXC key to URL: {mxc_s}");
let mxc = OwnedMxcUri::from(mxc_s);
mxcs.push(mxc);
}
Ok(mxcs)
}
/// Deletes all remote only media files in the given at or after
/// time/duration. Returns a u32 with the amount of media files deleted.
pub async fn delete_all_remote_media_at_after_time(&self, time: String, force: bool) -> Result<usize> {
let all_keys = self.db.get_all_media_keys();
let user_duration: SystemTime = match cyborgtime::parse_duration(&time) {
Err(e) => return Err!(Database(error!("Failed to parse specified time duration: {e}"))),
Ok(duration) => SystemTime::now()
@ -171,7 +203,9 @@ impl Service {
.ok_or(err!(Arithmetic("Duration {duration:?} is too large")))?,
};
let mut remote_mxcs: Vec<String> = vec![];
let all_keys = self.db.get_all_media_keys();
let mut remote_mxcs = Vec::with_capacity(all_keys.len());
for key in all_keys {
debug!("Full MXC key from database: {key:?}");