atagen

macltok

public
Star0
Watch0
5c749146259c5ffd2d87a86858afdde32013b6a0
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: src/normalise/media.rs

96fdf90aacd6atagen2026-09-08 08:24 UTC1
use std::sync::atomic::{AtomicU64, Ordering};
96fdf90aacd6atagen2026-09-08 08:24 UTC2
96fdf90aacd6atagen2026-09-08 08:24 UTC3
use matrix_sdk::{media::MediaEventContent, ruma::events::room::MediaSource};
96fdf90aacd6atagen2026-09-08 08:24 UTC4
use matrix_sdk_ui::timeline::{MsgLikeKind, TimelineItem as SdkTimelineItem, TimelineItemContent};
9bd34e25bb86atagen2026-10-24 07:23 UTC5
use ruma::{
9bd34e25bb86atagen2026-10-24 07:23 UTC6
    OwnedMxcUri,
9bd34e25bb86atagen2026-10-24 07:23 UTC7
    events::{
9bd34e25bb86atagen2026-10-24 07:23 UTC8
        room::{
9bd34e25bb86atagen2026-10-24 07:23 UTC9
            ImageInfo, ThumbnailInfo,
9bd34e25bb86atagen2026-10-24 07:23 UTC10
            message::{FileInfo, MessageType, VideoInfo},
9bd34e25bb86atagen2026-10-24 07:23 UTC11
        },
9bd34e25bb86atagen2026-10-24 07:23 UTC12
        sticker::{StickerEventContent, StickerMediaSource},
96fdf90aacd6atagen2026-09-08 08:24 UTC13
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC14
};
96fdf90aacd6atagen2026-09-08 08:24 UTC15
0291516d40b5atagen2026-10-22 08:31 UTC16
use super::link_preview::sender_previews;
96fdf90aacd6atagen2026-09-08 08:24 UTC17
use crate::{MediaDescriptor, MediaId, MediaKind};
96fdf90aacd6atagen2026-09-08 08:24 UTC18
96fdf90aacd6atagen2026-09-08 08:24 UTC19
static NEXT_MEDIA_SCOPE: AtomicU64 = AtomicU64::new(1);
96fdf90aacd6atagen2026-09-08 08:24 UTC20
96fdf90aacd6atagen2026-09-08 08:24 UTC21
/// Per-timeline-session nonce namespacing recycled SDK unique IDs.
96fdf90aacd6atagen2026-09-08 08:24 UTC22
///
96fdf90aacd6atagen2026-09-08 08:24 UTC23
/// SDK 0.18 recycles [`SdkTimelineItem::unique_id`] across the local-to-remote
96fdf90aacd6atagen2026-09-08 08:24 UTC24
/// echo replacement, while transaction IDs disappear. Scoping the recycled ID
96fdf90aacd6atagen2026-09-08 08:24 UTC25
/// by a process-wide nonce keeps media IDs stable across replacement without
96fdf90aacd6atagen2026-09-08 08:24 UTC26
/// colliding across rooms, threads, or reopened sessions.
da8896938cf0atagen2026-10-25 10:04 UTC27
///
da8896938cf0atagen2026-10-25 10:04 UTC28
/// It is also the owner its registrations belong to. Media stays registered while any
da8896938cf0atagen2026-10-25 10:04 UTC29
/// scope that registered it is open, and a bounded scope drops its least recently used
da8896938cf0atagen2026-10-25 10:04 UTC30
/// entries past its limit without touching anyone else's.
96fdf90aacd6atagen2026-09-08 08:24 UTC31
#[derive(Clone, Debug)]
96fdf90aacd6atagen2026-09-08 08:24 UTC32
pub(crate) struct MediaScope {
96fdf90aacd6atagen2026-09-08 08:24 UTC33
    nonce: u64,
da8896938cf0atagen2026-10-25 10:04 UTC34
    limit: Option<usize>,
96fdf90aacd6atagen2026-09-08 08:24 UTC35
}
96fdf90aacd6atagen2026-09-08 08:24 UTC36
96fdf90aacd6atagen2026-09-08 08:24 UTC37
impl MediaScope {
da8896938cf0atagen2026-10-25 10:04 UTC38
    /// for a timeline, which holds what is on screen and so is never trimmed
96fdf90aacd6atagen2026-09-08 08:24 UTC39
    pub(crate) fn new() -> Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC40
        Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC41
            nonce: NEXT_MEDIA_SCOPE.fetch_add(1, Ordering::Relaxed),
da8896938cf0atagen2026-10-25 10:04 UTC42
            limit: None,
96fdf90aacd6atagen2026-09-08 08:24 UTC43
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC44
    }
fa9114fcf75fatagen2026-09-24 12:51 UTC45
da8896938cf0atagen2026-10-25 10:04 UTC46
    pub(crate) fn bounded(limit: usize) -> Self {
da8896938cf0atagen2026-10-25 10:04 UTC47
        Self {
da8896938cf0atagen2026-10-25 10:04 UTC48
            limit: Some(limit.max(1)),
da8896938cf0atagen2026-10-25 10:04 UTC49
            ..Self::new()
da8896938cf0atagen2026-10-25 10:04 UTC50
        }
da8896938cf0atagen2026-10-25 10:04 UTC51
    }
da8896938cf0atagen2026-10-25 10:04 UTC52
da8896938cf0atagen2026-10-25 10:04 UTC53
    pub(crate) const fn nonce(&self) -> u64 {
da8896938cf0atagen2026-10-25 10:04 UTC54
        self.nonce
da8896938cf0atagen2026-10-25 10:04 UTC55
    }
da8896938cf0atagen2026-10-25 10:04 UTC56
da8896938cf0atagen2026-10-25 10:04 UTC57
    pub(crate) const fn limit(&self) -> Option<usize> {
da8896938cf0atagen2026-10-25 10:04 UTC58
        self.limit
fa9114fcf75fatagen2026-09-24 12:51 UTC59
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC60
}
96fdf90aacd6atagen2026-09-08 08:24 UTC61
96fdf90aacd6atagen2026-09-08 08:24 UTC62
#[derive(Clone)]
cbb715e11963atagen2026-10-24 23:24 UTC63
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
96fdf90aacd6atagen2026-09-08 08:24 UTC64
pub(crate) struct MediaRegistration {
96fdf90aacd6atagen2026-09-08 08:24 UTC65
    pub(crate) id: MediaId,
96fdf90aacd6atagen2026-09-08 08:24 UTC66
    pub(crate) kind: MediaKind,
96fdf90aacd6atagen2026-09-08 08:24 UTC67
    pub(crate) full: MediaSourceInfo,
96fdf90aacd6atagen2026-09-08 08:24 UTC68
    pub(crate) thumbnail: Option<MediaSourceInfo>,
96fdf90aacd6atagen2026-09-08 08:24 UTC69
}
96fdf90aacd6atagen2026-09-08 08:24 UTC70
96fdf90aacd6atagen2026-09-08 08:24 UTC71
#[derive(Clone)]
cbb715e11963atagen2026-10-24 23:24 UTC72
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
96fdf90aacd6atagen2026-09-08 08:24 UTC73
pub(crate) struct MediaSourceInfo {
96fdf90aacd6atagen2026-09-08 08:24 UTC74
    pub(crate) source: MediaSource,
96fdf90aacd6atagen2026-09-08 08:24 UTC75
    pub(crate) mimetype: Option<String>,
96fdf90aacd6atagen2026-09-08 08:24 UTC76
    pub(crate) declared_size: Option<u64>,
96fdf90aacd6atagen2026-09-08 08:24 UTC77
}
96fdf90aacd6atagen2026-09-08 08:24 UTC78
0291516d40b5atagen2026-10-22 08:31 UTC79
pub(super) fn media_registrations_for_item(
96fdf90aacd6atagen2026-09-08 08:24 UTC80
    item: &SdkTimelineItem,
96fdf90aacd6atagen2026-09-08 08:24 UTC81
    scope: &MediaScope,
0291516d40b5atagen2026-10-22 08:31 UTC82
) -> Vec<MediaRegistration> {
0291516d40b5atagen2026-10-22 08:31 UTC83
    let Some(event) = item.as_event() else {
0291516d40b5atagen2026-10-22 08:31 UTC84
        return Vec::new();
0291516d40b5atagen2026-10-22 08:31 UTC85
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC86
    let identity = media_identity(item, scope);
0291516d40b5atagen2026-10-22 08:31 UTC87
    let TimelineItemContent::MsgLike(content) = event.content() else {
0291516d40b5atagen2026-10-22 08:31 UTC88
        return Vec::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC89
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC90
    match &content.kind {
0291516d40b5atagen2026-10-22 08:31 UTC91
        MsgLikeKind::Message(message) => media_registration(&identity, message.msgtype())
0291516d40b5atagen2026-10-22 08:31 UTC92
            .into_iter()
0291516d40b5atagen2026-10-22 08:31 UTC93
            .chain(
0291516d40b5atagen2026-10-22 08:31 UTC94
                sender_previews(Some(&identity), message.msgtype())
0291516d40b5atagen2026-10-22 08:31 UTC95
                    .into_iter()
0291516d40b5atagen2026-10-22 08:31 UTC96
                    .filter_map(|(_, registration)| registration),
0291516d40b5atagen2026-10-22 08:31 UTC97
            )
0291516d40b5atagen2026-10-22 08:31 UTC98
            .collect(),
0291516d40b5atagen2026-10-22 08:31 UTC99
        MsgLikeKind::Sticker(sticker) => media_sticker_registration(&identity, sticker.content())
0291516d40b5atagen2026-10-22 08:31 UTC100
            .into_iter()
0291516d40b5atagen2026-10-22 08:31 UTC101
            .collect(),
0291516d40b5atagen2026-10-22 08:31 UTC102
        _ => Vec::new(),
96fdf90aacd6atagen2026-09-08 08:24 UTC103
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC104
}
96fdf90aacd6atagen2026-09-08 08:24 UTC105
96fdf90aacd6atagen2026-09-08 08:24 UTC106
pub(super) fn media_identity(item: &SdkTimelineItem, scope: &MediaScope) -> String {
96fdf90aacd6atagen2026-09-08 08:24 UTC107
    format!("{}#{}", scope.nonce, item.unique_id().0)
96fdf90aacd6atagen2026-09-08 08:24 UTC108
}
96fdf90aacd6atagen2026-09-08 08:24 UTC109
96fdf90aacd6atagen2026-09-08 08:24 UTC110
pub(super) fn media_from_message(identity: &str, message: &MessageType) -> Option<MediaDescriptor> {
9bd34e25bb86atagen2026-10-24 07:23 UTC111
    let descriptor =
9bd34e25bb86atagen2026-10-24 07:23 UTC112
        |kind, source: &MediaSource, filename: &str, caption: Option<&str>| MediaDescriptor {
9bd34e25bb86atagen2026-10-24 07:23 UTC113
            id: media_id(identity, "media"),
9bd34e25bb86atagen2026-10-24 07:23 UTC114
            kind,
9bd34e25bb86atagen2026-10-24 07:23 UTC115
            mxc: plain_mxc(source),
9bd34e25bb86atagen2026-10-24 07:23 UTC116
            filename: filename.to_owned(),
9bd34e25bb86atagen2026-10-24 07:23 UTC117
            caption: caption.map(str::to_owned),
9bd34e25bb86atagen2026-10-24 07:23 UTC118
            mimetype: None,
9bd34e25bb86atagen2026-10-24 07:23 UTC119
            size_bytes: None,
9bd34e25bb86atagen2026-10-24 07:23 UTC120
            width: None,
9bd34e25bb86atagen2026-10-24 07:23 UTC121
            height: None,
9bd34e25bb86atagen2026-10-24 07:23 UTC122
            duration_ms: None,
9bd34e25bb86atagen2026-10-24 07:23 UTC123
            blurhash: None,
9bd34e25bb86atagen2026-10-24 07:23 UTC124
            has_thumbnail: false,
9bd34e25bb86atagen2026-10-24 07:23 UTC125
        };
5a52012c9b1datagen2026-09-24 23:45 UTC126
    let millis = |duration: std::time::Duration| u64::try_from(duration.as_millis()).ok();
5a52012c9b1datagen2026-09-24 23:45 UTC127
    Some(match message {
5a52012c9b1datagen2026-09-24 23:45 UTC128
        MessageType::Image(content) => {
5a52012c9b1datagen2026-09-24 23:45 UTC129
            let info = content.info.as_deref();
5a52012c9b1datagen2026-09-24 23:45 UTC130
            MediaDescriptor {
5a52012c9b1datagen2026-09-24 23:45 UTC131
                mimetype: info.and_then(|info| info.mimetype.clone()),
5a52012c9b1datagen2026-09-24 23:45 UTC132
                size_bytes: info.and_then(|info| info.size).map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC133
                width: info.and_then(|info| info.width).map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC134
                height: info.and_then(|info| info.height).map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC135
                blurhash: info.and_then(|info| info.blurhash.clone()),
5a52012c9b1datagen2026-09-24 23:45 UTC136
                has_thumbnail: info.is_some_and(|info| info.thumbnail_source.is_some()),
9bd34e25bb86atagen2026-10-24 07:23 UTC137
                ..descriptor(
9bd34e25bb86atagen2026-10-24 07:23 UTC138
                    MediaKind::Image,
9bd34e25bb86atagen2026-10-24 07:23 UTC139
                    &content.source,
9bd34e25bb86atagen2026-10-24 07:23 UTC140
                    content.filename(),
9bd34e25bb86atagen2026-10-24 07:23 UTC141
                    content.caption(),
9bd34e25bb86atagen2026-10-24 07:23 UTC142
                )
5a52012c9b1datagen2026-09-24 23:45 UTC143
            }
5a52012c9b1datagen2026-09-24 23:45 UTC144
        }
5a52012c9b1datagen2026-09-24 23:45 UTC145
        MessageType::Video(content) => {
5a52012c9b1datagen2026-09-24 23:45 UTC146
            let info = content.info.as_deref();
5a52012c9b1datagen2026-09-24 23:45 UTC147
            MediaDescriptor {
5a52012c9b1datagen2026-09-24 23:45 UTC148
                mimetype: info.and_then(|info| info.mimetype.clone()),
5a52012c9b1datagen2026-09-24 23:45 UTC149
                size_bytes: info.and_then(|info| info.size).map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC150
                width: info.and_then(|info| info.width).map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC151
                height: info.and_then(|info| info.height).map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC152
                duration_ms: info.and_then(|info| info.duration).and_then(millis),
5a52012c9b1datagen2026-09-24 23:45 UTC153
                blurhash: info.and_then(|info| info.blurhash.clone()),
5a52012c9b1datagen2026-09-24 23:45 UTC154
                has_thumbnail: info.is_some_and(|info| info.thumbnail_source.is_some()),
9bd34e25bb86atagen2026-10-24 07:23 UTC155
                ..descriptor(
9bd34e25bb86atagen2026-10-24 07:23 UTC156
                    MediaKind::Video,
9bd34e25bb86atagen2026-10-24 07:23 UTC157
                    &content.source,
9bd34e25bb86atagen2026-10-24 07:23 UTC158
                    content.filename(),
9bd34e25bb86atagen2026-10-24 07:23 UTC159
                    content.caption(),
9bd34e25bb86atagen2026-10-24 07:23 UTC160
                )
5a52012c9b1datagen2026-09-24 23:45 UTC161
            }
5a52012c9b1datagen2026-09-24 23:45 UTC162
        }
5a52012c9b1datagen2026-09-24 23:45 UTC163
        MessageType::Audio(content) => {
5a52012c9b1datagen2026-09-24 23:45 UTC164
            let info = content.info.as_deref();
5a52012c9b1datagen2026-09-24 23:45 UTC165
            MediaDescriptor {
5a52012c9b1datagen2026-09-24 23:45 UTC166
                mimetype: info.and_then(|info| info.mimetype.clone()),
5a52012c9b1datagen2026-09-24 23:45 UTC167
                size_bytes: info.and_then(|info| info.size).map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC168
                duration_ms: info.and_then(|info| info.duration).and_then(millis),
9bd34e25bb86atagen2026-10-24 07:23 UTC169
                ..descriptor(
9bd34e25bb86atagen2026-10-24 07:23 UTC170
                    MediaKind::Audio,
9bd34e25bb86atagen2026-10-24 07:23 UTC171
                    &content.source,
9bd34e25bb86atagen2026-10-24 07:23 UTC172
                    content.filename(),
9bd34e25bb86atagen2026-10-24 07:23 UTC173
                    content.caption(),
9bd34e25bb86atagen2026-10-24 07:23 UTC174
                )
5a52012c9b1datagen2026-09-24 23:45 UTC175
            }
5a52012c9b1datagen2026-09-24 23:45 UTC176
        }
5a52012c9b1datagen2026-09-24 23:45 UTC177
        MessageType::File(content) => {
5a52012c9b1datagen2026-09-24 23:45 UTC178
            let info = content.info.as_deref();
5a52012c9b1datagen2026-09-24 23:45 UTC179
            MediaDescriptor {
5a52012c9b1datagen2026-09-24 23:45 UTC180
                mimetype: info.and_then(|info| info.mimetype.clone()),
5a52012c9b1datagen2026-09-24 23:45 UTC181
                size_bytes: info.and_then(|info| info.size).map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC182
                has_thumbnail: info.is_some_and(|info| info.thumbnail_source.is_some()),
9bd34e25bb86atagen2026-10-24 07:23 UTC183
                ..descriptor(
9bd34e25bb86atagen2026-10-24 07:23 UTC184
                    MediaKind::File,
9bd34e25bb86atagen2026-10-24 07:23 UTC185
                    &content.source,
9bd34e25bb86atagen2026-10-24 07:23 UTC186
                    content.filename(),
9bd34e25bb86atagen2026-10-24 07:23 UTC187
                    content.caption(),
9bd34e25bb86atagen2026-10-24 07:23 UTC188
                )
5a52012c9b1datagen2026-09-24 23:45 UTC189
            }
5a52012c9b1datagen2026-09-24 23:45 UTC190
        }
5a52012c9b1datagen2026-09-24 23:45 UTC191
        _ => return None,
96fdf90aacd6atagen2026-09-08 08:24 UTC192
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC193
}
96fdf90aacd6atagen2026-09-08 08:24 UTC194
96fdf90aacd6atagen2026-09-08 08:24 UTC195
pub(super) fn media_descriptor_for_sticker(
96fdf90aacd6atagen2026-09-08 08:24 UTC196
    identity: &str,
96fdf90aacd6atagen2026-09-08 08:24 UTC197
    content: &StickerEventContent,
96fdf90aacd6atagen2026-09-08 08:24 UTC198
) -> MediaDescriptor {
96fdf90aacd6atagen2026-09-08 08:24 UTC199
    MediaDescriptor {
96fdf90aacd6atagen2026-09-08 08:24 UTC200
        id: media_id(identity, "sticker"),
96fdf90aacd6atagen2026-09-08 08:24 UTC201
        kind: MediaKind::Sticker,
9bd34e25bb86atagen2026-10-24 07:23 UTC202
        mxc: match &content.source {
9bd34e25bb86atagen2026-10-24 07:23 UTC203
            StickerMediaSource::Plain(uri) => Some(uri.clone()),
9bd34e25bb86atagen2026-10-24 07:23 UTC204
            _ => None,
9bd34e25bb86atagen2026-10-24 07:23 UTC205
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC206
        filename: content.body.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC207
        caption: None,
96fdf90aacd6atagen2026-09-08 08:24 UTC208
        mimetype: content.info.mimetype.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC209
        size_bytes: content.info.size.map(u64::from),
96fdf90aacd6atagen2026-09-08 08:24 UTC210
        width: content.info.width.map(u64::from),
96fdf90aacd6atagen2026-09-08 08:24 UTC211
        height: content.info.height.map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC212
        duration_ms: None,
5a52012c9b1datagen2026-09-24 23:45 UTC213
        blurhash: content.info.blurhash.clone(),
5a52012c9b1datagen2026-09-24 23:45 UTC214
        has_thumbnail: content.info.thumbnail_source.is_some(),
96fdf90aacd6atagen2026-09-08 08:24 UTC215
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC216
}
96fdf90aacd6atagen2026-09-08 08:24 UTC217
9bd34e25bb86atagen2026-10-24 07:23 UTC218
fn plain_mxc(source: &MediaSource) -> Option<OwnedMxcUri> {
9bd34e25bb86atagen2026-10-24 07:23 UTC219
    match source {
9bd34e25bb86atagen2026-10-24 07:23 UTC220
        MediaSource::Plain(uri) => Some(uri.clone()),
9bd34e25bb86atagen2026-10-24 07:23 UTC221
        MediaSource::Encrypted(_) => None,
9bd34e25bb86atagen2026-10-24 07:23 UTC222
    }
9bd34e25bb86atagen2026-10-24 07:23 UTC223
}
9bd34e25bb86atagen2026-10-24 07:23 UTC224
96fdf90aacd6atagen2026-09-08 08:24 UTC225
pub(super) fn media_id(identity: &str, suffix: &str) -> MediaId {
96fdf90aacd6atagen2026-09-08 08:24 UTC226
    MediaId::new(format!("{identity}#{suffix}"))
96fdf90aacd6atagen2026-09-08 08:24 UTC227
}
96fdf90aacd6atagen2026-09-08 08:24 UTC228
cbb715e11963atagen2026-10-24 23:24 UTC229
pub(super) fn media_sticker_registration(
96fdf90aacd6atagen2026-09-08 08:24 UTC230
    identity: &str,
96fdf90aacd6atagen2026-09-08 08:24 UTC231
    content: &StickerEventContent,
96fdf90aacd6atagen2026-09-08 08:24 UTC232
) -> Option<MediaRegistration> {
96fdf90aacd6atagen2026-09-08 08:24 UTC233
    Some(MediaRegistration {
96fdf90aacd6atagen2026-09-08 08:24 UTC234
        id: media_id(identity, "sticker"),
96fdf90aacd6atagen2026-09-08 08:24 UTC235
        kind: MediaKind::Sticker,
96fdf90aacd6atagen2026-09-08 08:24 UTC236
        full: MediaSourceInfo {
96fdf90aacd6atagen2026-09-08 08:24 UTC237
            source: content.source()?,
96fdf90aacd6atagen2026-09-08 08:24 UTC238
            mimetype: content.info.mimetype.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC239
            declared_size: content.info.size.map(u64::from),
96fdf90aacd6atagen2026-09-08 08:24 UTC240
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC241
        thumbnail: thumbnail_media_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC242
            content.info.thumbnail_source.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC243
            content.info.thumbnail_info.as_deref(),
96fdf90aacd6atagen2026-09-08 08:24 UTC244
        ),
96fdf90aacd6atagen2026-09-08 08:24 UTC245
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC246
}
96fdf90aacd6atagen2026-09-08 08:24 UTC247
cbb715e11963atagen2026-10-24 23:24 UTC248
pub(super) fn media_registration(
cbb715e11963atagen2026-10-24 23:24 UTC249
    identity: &str,
cbb715e11963atagen2026-10-24 23:24 UTC250
    message: &MessageType,
cbb715e11963atagen2026-10-24 23:24 UTC251
) -> Option<MediaRegistration> {
96fdf90aacd6atagen2026-09-08 08:24 UTC252
    match message {
96fdf90aacd6atagen2026-09-08 08:24 UTC253
        MessageType::Image(content) => media_registration_with_info(
96fdf90aacd6atagen2026-09-08 08:24 UTC254
            identity,
96fdf90aacd6atagen2026-09-08 08:24 UTC255
            MediaKind::Image,
96fdf90aacd6atagen2026-09-08 08:24 UTC256
            content.source(),
96fdf90aacd6atagen2026-09-08 08:24 UTC257
            content.info.as_deref(),
96fdf90aacd6atagen2026-09-08 08:24 UTC258
        ),
96fdf90aacd6atagen2026-09-08 08:24 UTC259
        MessageType::Video(content) => media_registration_with_info(
96fdf90aacd6atagen2026-09-08 08:24 UTC260
            identity,
96fdf90aacd6atagen2026-09-08 08:24 UTC261
            MediaKind::Video,
96fdf90aacd6atagen2026-09-08 08:24 UTC262
            content.source(),
96fdf90aacd6atagen2026-09-08 08:24 UTC263
            content.info.as_deref(),
96fdf90aacd6atagen2026-09-08 08:24 UTC264
        ),
96fdf90aacd6atagen2026-09-08 08:24 UTC265
        MessageType::File(content) => media_registration_with_info(
96fdf90aacd6atagen2026-09-08 08:24 UTC266
            identity,
96fdf90aacd6atagen2026-09-08 08:24 UTC267
            MediaKind::File,
96fdf90aacd6atagen2026-09-08 08:24 UTC268
            content.source(),
96fdf90aacd6atagen2026-09-08 08:24 UTC269
            content.info.as_deref(),
96fdf90aacd6atagen2026-09-08 08:24 UTC270
        ),
96fdf90aacd6atagen2026-09-08 08:24 UTC271
        MessageType::Audio(content) => media_registration_with_parts(
96fdf90aacd6atagen2026-09-08 08:24 UTC272
            identity,
96fdf90aacd6atagen2026-09-08 08:24 UTC273
            MediaKind::Audio,
96fdf90aacd6atagen2026-09-08 08:24 UTC274
            content.source(),
96fdf90aacd6atagen2026-09-08 08:24 UTC275
            content.info.as_ref().and_then(|info| info.mimetype.clone()),
96fdf90aacd6atagen2026-09-08 08:24 UTC276
            content
96fdf90aacd6atagen2026-09-08 08:24 UTC277
                .info
96fdf90aacd6atagen2026-09-08 08:24 UTC278
                .as_ref()
96fdf90aacd6atagen2026-09-08 08:24 UTC279
                .and_then(|info| info.size)
96fdf90aacd6atagen2026-09-08 08:24 UTC280
                .map(u64::from),
96fdf90aacd6atagen2026-09-08 08:24 UTC281
            None,
96fdf90aacd6atagen2026-09-08 08:24 UTC282
        ),
96fdf90aacd6atagen2026-09-08 08:24 UTC283
        _ => None,
96fdf90aacd6atagen2026-09-08 08:24 UTC284
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC285
}
96fdf90aacd6atagen2026-09-08 08:24 UTC286
96fdf90aacd6atagen2026-09-08 08:24 UTC287
fn media_registration_with_info<T: MediaInfo>(
96fdf90aacd6atagen2026-09-08 08:24 UTC288
    identity: &str,
96fdf90aacd6atagen2026-09-08 08:24 UTC289
    kind: MediaKind,
96fdf90aacd6atagen2026-09-08 08:24 UTC290
    source: Option<MediaSource>,
96fdf90aacd6atagen2026-09-08 08:24 UTC291
    info: Option<&T>,
96fdf90aacd6atagen2026-09-08 08:24 UTC292
) -> Option<MediaRegistration> {
96fdf90aacd6atagen2026-09-08 08:24 UTC293
    media_registration_with_parts(
96fdf90aacd6atagen2026-09-08 08:24 UTC294
        identity,
96fdf90aacd6atagen2026-09-08 08:24 UTC295
        kind,
96fdf90aacd6atagen2026-09-08 08:24 UTC296
        source,
96fdf90aacd6atagen2026-09-08 08:24 UTC297
        info.and_then(MediaInfo::mimetype),
96fdf90aacd6atagen2026-09-08 08:24 UTC298
        info.and_then(MediaInfo::declared_size),
96fdf90aacd6atagen2026-09-08 08:24 UTC299
        info.and_then(MediaInfo::thumbnail),
96fdf90aacd6atagen2026-09-08 08:24 UTC300
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC301
}
96fdf90aacd6atagen2026-09-08 08:24 UTC302
96fdf90aacd6atagen2026-09-08 08:24 UTC303
fn media_registration_with_parts(
96fdf90aacd6atagen2026-09-08 08:24 UTC304
    identity: &str,
96fdf90aacd6atagen2026-09-08 08:24 UTC305
    kind: MediaKind,
96fdf90aacd6atagen2026-09-08 08:24 UTC306
    source: Option<MediaSource>,
96fdf90aacd6atagen2026-09-08 08:24 UTC307
    mimetype: Option<String>,
96fdf90aacd6atagen2026-09-08 08:24 UTC308
    declared_size: Option<u64>,
96fdf90aacd6atagen2026-09-08 08:24 UTC309
    thumbnail: Option<MediaSourceInfo>,
96fdf90aacd6atagen2026-09-08 08:24 UTC310
) -> Option<MediaRegistration> {
96fdf90aacd6atagen2026-09-08 08:24 UTC311
    Some(MediaRegistration {
96fdf90aacd6atagen2026-09-08 08:24 UTC312
        id: media_id(identity, "media"),
96fdf90aacd6atagen2026-09-08 08:24 UTC313
        kind,
96fdf90aacd6atagen2026-09-08 08:24 UTC314
        full: MediaSourceInfo {
96fdf90aacd6atagen2026-09-08 08:24 UTC315
            source: source?,
96fdf90aacd6atagen2026-09-08 08:24 UTC316
            mimetype,
96fdf90aacd6atagen2026-09-08 08:24 UTC317
            declared_size,
96fdf90aacd6atagen2026-09-08 08:24 UTC318
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC319
        thumbnail,
96fdf90aacd6atagen2026-09-08 08:24 UTC320
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC321
}
96fdf90aacd6atagen2026-09-08 08:24 UTC322
96fdf90aacd6atagen2026-09-08 08:24 UTC323
trait MediaInfo {
96fdf90aacd6atagen2026-09-08 08:24 UTC324
    fn mimetype(&self) -> Option<String>;
96fdf90aacd6atagen2026-09-08 08:24 UTC325
    fn declared_size(&self) -> Option<u64>;
96fdf90aacd6atagen2026-09-08 08:24 UTC326
    fn thumbnail(&self) -> Option<MediaSourceInfo>;
96fdf90aacd6atagen2026-09-08 08:24 UTC327
}
96fdf90aacd6atagen2026-09-08 08:24 UTC328
96fdf90aacd6atagen2026-09-08 08:24 UTC329
macro_rules! impl_media_info {
96fdf90aacd6atagen2026-09-08 08:24 UTC330
    ($type:ty) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC331
        impl MediaInfo for $type {
96fdf90aacd6atagen2026-09-08 08:24 UTC332
            fn mimetype(&self) -> Option<String> {
96fdf90aacd6atagen2026-09-08 08:24 UTC333
                self.mimetype.clone()
96fdf90aacd6atagen2026-09-08 08:24 UTC334
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC335
96fdf90aacd6atagen2026-09-08 08:24 UTC336
            fn declared_size(&self) -> Option<u64> {
96fdf90aacd6atagen2026-09-08 08:24 UTC337
                self.size.map(u64::from)
96fdf90aacd6atagen2026-09-08 08:24 UTC338
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC339
96fdf90aacd6atagen2026-09-08 08:24 UTC340
            fn thumbnail(&self) -> Option<MediaSourceInfo> {
96fdf90aacd6atagen2026-09-08 08:24 UTC341
                thumbnail_media_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC342
                    self.thumbnail_source.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC343
                    self.thumbnail_info.as_deref(),
96fdf90aacd6atagen2026-09-08 08:24 UTC344
                )
96fdf90aacd6atagen2026-09-08 08:24 UTC345
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC346
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC347
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC348
}
96fdf90aacd6atagen2026-09-08 08:24 UTC349
96fdf90aacd6atagen2026-09-08 08:24 UTC350
impl_media_info!(ImageInfo);
96fdf90aacd6atagen2026-09-08 08:24 UTC351
impl_media_info!(VideoInfo);
96fdf90aacd6atagen2026-09-08 08:24 UTC352
impl_media_info!(FileInfo);
96fdf90aacd6atagen2026-09-08 08:24 UTC353
96fdf90aacd6atagen2026-09-08 08:24 UTC354
fn thumbnail_media_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC355
    source: Option<MediaSource>,
96fdf90aacd6atagen2026-09-08 08:24 UTC356
    info: Option<&ThumbnailInfo>,
96fdf90aacd6atagen2026-09-08 08:24 UTC357
) -> Option<MediaSourceInfo> {
96fdf90aacd6atagen2026-09-08 08:24 UTC358
    source.map(|source| MediaSourceInfo {
96fdf90aacd6atagen2026-09-08 08:24 UTC359
        source,
96fdf90aacd6atagen2026-09-08 08:24 UTC360
        mimetype: info.and_then(|info| info.mimetype.clone()),
96fdf90aacd6atagen2026-09-08 08:24 UTC361
        declared_size: info.and_then(|info| info.size).map(u64::from),
96fdf90aacd6atagen2026-09-08 08:24 UTC362
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC363
}
96fdf90aacd6atagen2026-09-08 08:24 UTC364
96fdf90aacd6atagen2026-09-08 08:24 UTC365
#[cfg(test)]
96fdf90aacd6atagen2026-09-08 08:24 UTC366
mod tests {
96fdf90aacd6atagen2026-09-08 08:24 UTC367
    use matrix_sdk::ruma::{
96fdf90aacd6atagen2026-09-08 08:24 UTC368
        events::room::{ImageInfo, message::ImageMessageEventContent},
96fdf90aacd6atagen2026-09-08 08:24 UTC369
        mxc_uri,
96fdf90aacd6atagen2026-09-08 08:24 UTC370
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC371
96fdf90aacd6atagen2026-09-08 08:24 UTC372
    use super::*;
96fdf90aacd6atagen2026-09-08 08:24 UTC373
96fdf90aacd6atagen2026-09-08 08:24 UTC374
    #[test]
96fdf90aacd6atagen2026-09-08 08:24 UTC375
    fn image_messages_keep_media_metadata() {
96fdf90aacd6atagen2026-09-08 08:24 UTC376
        let mut info = ImageInfo::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC377
        info.mimetype = Some("image/png".to_owned());
96fdf90aacd6atagen2026-09-08 08:24 UTC378
        info.size = Some(65_536_u32.into());
96fdf90aacd6atagen2026-09-08 08:24 UTC379
        info.width = Some(640_u32.into());
96fdf90aacd6atagen2026-09-08 08:24 UTC380
        info.height = Some(480_u32.into());
96fdf90aacd6atagen2026-09-08 08:24 UTC381
        let content = ImageMessageEventContent::plain(
96fdf90aacd6atagen2026-09-08 08:24 UTC382
            "screen.png".to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC383
            mxc_uri!("mxc://example.org/screen").to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC384
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC385
        .info(Box::new(info));
96fdf90aacd6atagen2026-09-08 08:24 UTC386
96fdf90aacd6atagen2026-09-08 08:24 UTC387
        let media = media_from_message("$event:example.org", &MessageType::Image(content)).unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC388
        assert_eq!(media.id.as_str(), "$event:example.org#media");
96fdf90aacd6atagen2026-09-08 08:24 UTC389
        assert_eq!(media.kind, MediaKind::Image);
96fdf90aacd6atagen2026-09-08 08:24 UTC390
        assert_eq!(media.filename, "screen.png");
96fdf90aacd6atagen2026-09-08 08:24 UTC391
        assert_eq!(media.caption, None);
96fdf90aacd6atagen2026-09-08 08:24 UTC392
        assert_eq!(media.mimetype.as_deref(), Some("image/png"));
96fdf90aacd6atagen2026-09-08 08:24 UTC393
        assert_eq!(media.size_bytes, Some(65_536));
96fdf90aacd6atagen2026-09-08 08:24 UTC394
        assert_eq!((media.width, media.height), (Some(640), Some(480)));
96fdf90aacd6atagen2026-09-08 08:24 UTC395
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC396
}