add admin command to delete all remote media files

This commit is contained in:
Benjamin Lee 2024-09-15 00:34:20 -07:00
parent d7087c66bb
commit 9d14c5d461
No known key found for this signature in database
GPG key ID: FB9624E2885D55A4
4 changed files with 98 additions and 2 deletions

View file

@ -161,4 +161,27 @@ impl service::media::Data for KeyValueDatabase {
})
.collect()
}
fn all_file_metadata(
&self,
) -> Box<
dyn Iterator<Item = Result<(OwnedMxcUri, FileMeta, MediaFileKey)>> + '_,
> {
Box::new(
self.mediaid_file
.iter()
.map(|(key, _)| {
let key = MediaFileKey::new(key);
let parts = MediaFileKeyParts::try_from(&key)?;
if parts.width != 0 && parts.height != 0 {
// Skip thumbnails
return Ok(None);
};
Ok(Some((parts.mxc, parts.meta, key)))
})
.filter_map(Result::transpose),
)
}
}

View file

@ -23,8 +23,8 @@ use ruma::{
TimelineEventType,
},
signatures::verify_json,
EventId, MilliSecondsSinceUnixEpoch, OwnedMxcUri, OwnedRoomId, RoomId,
RoomVersionId, ServerName, UserId,
EventId, MilliSecondsSinceUnixEpoch, OwnedMxcUri, OwnedRoomId,
OwnedServerName, RoomId, RoomVersionId, ServerName, UserId,
};
use serde_json::value::to_raw_value;
use tokio::sync::{mpsc, Mutex, RwLock};
@ -185,6 +185,17 @@ enum AdminCommand {
mxc: OwnedMxcUri,
},
/// Delete cached remote media from the database.
///
/// This media may still be fetched and cached again in the future.
DeleteRemoteMedia {
/// If specified, only delete remote media from this origin.
///
/// If not specified, all remote media will be deleted.
#[clap(long)]
origin: Option<OwnedServerName>,
},
/// Verify json signatures
/// [commandbody]()
/// # ```
@ -803,6 +814,50 @@ impl Service {
services().media.delete(mxc).await?;
RoomMessageEventContent::text_plain("Media deleted.")
}
AdminCommand::DeleteRemoteMedia {
origin,
} => {
if origin.as_deref() == Some(services().globals.server_name()) {
return Ok(RoomMessageEventContent::text_plain(
"Specified origin is this server. Will not delete \
anything.",
));
}
let mut count = 0;
// The `media.iter_all()` iterator is not `Send`, so spawn it in
// a separate thread and send the results over a channel.
let (tx, mut rx) = mpsc::channel(1);
tokio::task::spawn_blocking(move || {
for mxc in services().media.iter_all() {
if tx.blocking_send(mxc).is_err() {
break;
}
}
});
while let Some(mxc) = rx.recv().await {
let mxc = mxc?;
let server_name = mxc.server_name();
if server_name == Ok(services().globals.server_name()) {
continue;
}
if let Some(origin) = &origin {
if server_name != Ok(origin) {
continue;
}
}
count += 1;
services().media.delete(mxc).await?;
}
RoomMessageEventContent::text_plain(format!(
"{count} media objects deleted."
))
}
AdminCommand::DeactivateUser {
leave_rooms,
user_id,

View file

@ -141,6 +141,15 @@ impl Service {
Ok(())
}
/// List all media stored in the database.
///
/// Each MXC is list once. Thumbnails are not included separately from the
/// original media.
#[tracing::instrument(skip(self))]
pub(crate) fn iter_all(&self) -> impl Iterator<Item = Result<OwnedMxcUri>> {
self.db.all_file_metadata().map(|media| media.map(|(mxc, ..)| mxc))
}
/// Returns width, height of the thumbnail and whether it should be cropped.
/// Returns None when the server should send the original file.
fn thumbnail_properties(

View file

@ -29,4 +29,13 @@ pub(crate) trait Data: Send + Sync {
&self,
mxc: OwnedMxcUri,
) -> Result<Vec<(FileMeta, MediaFileKey)>>;
/// Returns an iterator over metadata for all media.
///
/// Thumbnails are not included.
fn all_file_metadata(
&self,
) -> Box<
dyn Iterator<Item = Result<(OwnedMxcUri, FileMeta, MediaFileKey)>> + '_,
>;
}