From 4d624846ee935a6c74d88bf62303f49fc8842768 Mon Sep 17 00:00:00 2001 From: strawberry Date: Thu, 22 Feb 2024 00:08:08 -0500 Subject: [PATCH] admin command to delete media via MXC url Signed-off-by: strawberry --- src/database/key_value/media.rs | 39 ++++++++++++++++++++-- src/service/admin/mod.rs | 28 ++++++++++++++-- src/service/media/data.rs | 2 ++ src/service/media/mod.rs | 57 ++++++++++++--------------------- 4 files changed, 85 insertions(+), 41 deletions(-) diff --git a/src/database/key_value/media.rs b/src/database/key_value/media.rs index da7162b6..dcafc74e 100644 --- a/src/database/key_value/media.rs +++ b/src/database/key_value/media.rs @@ -1,4 +1,5 @@ use ruma::api::client::error::ErrorKind; +use tracing::debug; use crate::{ database::KeyValueDatabase, @@ -40,14 +41,46 @@ impl service::media::Data for KeyValueDatabase { } fn delete_file_mxc(&self, mxc: String) -> Result<()> { - let mut key = mxc.as_bytes().to_vec(); - key.push(0xff); + debug!("MXC URI: {:?}", mxc); - self.mediaid_file.remove(&key)?; + let mut prefix = mxc.as_bytes().to_vec(); + prefix.push(0xff); + + debug!("MXC db prefix: {:?}", prefix); + + for (key, _) in self.mediaid_file.scan_prefix(prefix) { + debug!("Deleting key: {:?}", key); + self.mediaid_file.remove(&key)?; + } + //return Err(Error::bad_database("Media not found.")); Ok(()) } + /// Searches for all files with the given MXC (e.g. thumbnail and original image) + fn search_mxc_metadata_prefix(&self, mxc: String) -> Result>> { + debug!("MXC URI: {:?}", mxc); + + let mut prefix = mxc.as_bytes().to_vec(); + prefix.push(0xff); + + let mut keys: Vec> = vec![]; + + for (key, _) in self.mediaid_file.scan_prefix(prefix) { + keys.push(key); + } + + if keys.is_empty() { + return Err(Error::bad_database( + "Failed to find any keys in database with the provided MXC.", + )); + } + + debug!("Got the following keys: {:?}", keys); + + Ok(keys) + } + fn search_file_metadata( &self, mxc: String, diff --git a/src/service/admin/mod.rs b/src/service/admin/mod.rs index 73977f3b..0807bda2 100644 --- a/src/service/admin/mod.rs +++ b/src/service/admin/mod.rs @@ -27,8 +27,8 @@ use ruma::{ }, TimelineEventType, }, - EventId, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomOrAliasId, - RoomVersionId, ServerName, UserId, + EventId, MxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, + RoomOrAliasId, RoomVersionId, ServerName, UserId, }; use serde_json::value::to_raw_value; use tokio::sync::{mpsc, Mutex}; @@ -69,6 +69,10 @@ enum AdminCommand { /// - Commands for managing the server Server(ServerCommand), + #[command(subcommand)] + /// - Commands for managing media + Media(MediaCommand), + #[command(subcommand)] // TODO: should i split out debug commands to a separate thing? the // debug commands seem like they could fit in the other categories fine @@ -77,6 +81,16 @@ enum AdminCommand { Debug(DebugCommand), } +#[cfg_attr(test, derive(Debug))] +#[derive(Subcommand)] +enum MediaCommand { + /// - Deletes a single media file from our database and on the filesystem via a single MXC URI + Delete { + /// The MXC URI to delete + mxc: Box, + }, +} + #[cfg_attr(test, derive(Debug))] #[derive(Subcommand)] enum AppserviceCommand { @@ -606,6 +620,16 @@ impl Service { } } }, + AdminCommand::Media(command) => match command { + MediaCommand::Delete { mxc } => { + debug!("Got MXC URI: {}", mxc); + services().media.delete(mxc.to_string()).await?; + + return Ok(RoomMessageEventContent::text_plain( + "Deleted the MXC from our database and on our filesystem.", + )); + } + }, AdminCommand::Users(command) => match command { UserCommand::List => match services().users.list_local_users() { Ok(users) => { diff --git a/src/service/media/data.rs b/src/service/media/data.rs index 667267f6..0404b548 100644 --- a/src/service/media/data.rs +++ b/src/service/media/data.rs @@ -20,6 +20,8 @@ pub trait Data: Send + Sync { height: u32, ) -> Result<(Option, Option, Vec)>; + fn search_mxc_metadata_prefix(&self, mxc: String) -> Result>>; + fn remove_url_preview(&self, url: &str) -> Result<()>; fn set_url_preview( diff --git a/src/service/media/mod.rs b/src/service/media/mod.rs index 7ecd7519..9c3d4a86 100644 --- a/src/service/media/mod.rs +++ b/src/service/media/mod.rs @@ -8,7 +8,7 @@ use std::{ pub(crate) use data::Data; use serde::Serialize; -use tracing::{debug, error, warn}; +use tracing::{debug, error}; use crate::{services, Error, Result}; use image::imageops::FilterType; @@ -93,45 +93,30 @@ impl Service { /// Deletes a file in the database and from the media directory via an MXC pub async fn delete(&self, mxc: String) -> Result<()> { - if let Ok(filemeta) = self.get(mxc.clone()).await { - match filemeta { - Some(filemeta) => { - debug!("Got file metadata: {:?}", filemeta); - let file_key = filemeta.file; - debug!("File key from file metadata: {:?}", file_key); + if let Ok(keys) = self.db.search_mxc_metadata_prefix(mxc.clone()) { + for key in keys { + let file_path = if cfg!(feature = "sha256_media") { + services().globals.get_media_file_new(&key) + } else { + #[allow(deprecated)] + services().globals.get_media_file(&key) + }; + debug!("Got local file path: {:?}", file_path); - let file_path = if cfg!(feature = "sha256_media") { - services().globals.get_media_file_new(&file_key) - } else { - #[allow(deprecated)] - services().globals.get_media_file(&file_key) - }; - debug!("Got local file path: {:?}", file_path); + debug!( + "Deleting local file {:?} from filesystem, original MXC: {}", + file_path, mxc + ); + tokio::fs::remove_file(file_path).await?; - debug!( - "Deleting local file {:?} from filesystem, original MXC: {mxc}", - file_path - ); - tokio::fs::remove_file(file_path).await?; - - debug!("Deleting MXC {mxc} from database"); - self.db.delete_file_mxc(mxc)?; - - Ok(()) - } - None => { - warn!( - "MXC {mxc} does not exist in our database or file in MXC does not exist." - ); - Err(Error::bad_database( - "MXC does not exist in our database or file in MXC does not exist.", - )) - } + debug!("Deleting MXC {mxc} from database"); + self.db.delete_file_mxc(mxc.clone())?; } + + Ok(()) } else { - // we shouldn't get to this point as this is failing to actually attempt to get the file metadata (Result) - error!("Failed getting file metadata for MXC \"{mxc}\" in database (does not exist or database issue?)"); - Err(Error::bad_database("Failed getting file metadata via MXC in database (does not exist or database issue?)")) + error!("Failed to find any media keys for MXC \"{mxc}\" in our database (MXC does not exist)"); + Err(Error::bad_database("Failed to find any media keys for the provided MXC in our database (MXC does not exist)")) } }