events: Avoid unnecessary allocations during conversion

This commit is contained in:
Kévin Commaille 2022-07-02 17:34:10 +02:00 committed by Kévin Commaille
parent 96155915fe
commit bda17c3662
11 changed files with 232 additions and 165 deletions

View File

@ -89,12 +89,17 @@ impl AudioEventContent {
#[cfg(feature = "unstable-msc3245")]
voice: _,
} = content;
let AudioInfo { duration, mimetype, size } = info.map(|info| *info).unwrap_or_default();
let message = message.unwrap_or_else(|| MessageContent::plain(body));
let file = file.unwrap_or_else(|| {
FileContent::from_room_message_content(source, info.as_deref(), None)
FileContent::from_room_message_content(source, None, mimetype, size)
});
let audio = audio.unwrap_or_else(|| {
let mut content = AudioContent::new();
content.duration = duration;
content
});
let audio = audio.or_else(|| info.as_deref().map(Into::into)).unwrap_or_default();
Self { message, file, audio, relates_to }
}
@ -136,19 +141,17 @@ impl AudioContent {
Self::default()
}
/// Creates a new `AudioContent` with the given duration.
pub(crate) fn from_room_message_content(duration: Duration) -> Self {
Self { duration: Some(duration), ..Default::default() }
}
/// Whether this `AudioContent` is empty.
pub fn is_empty(&self) -> bool {
self.duration.is_none() && self.waveform.is_none()
}
}
impl From<&AudioInfo> for AudioContent {
fn from(info: &AudioInfo) -> Self {
let AudioInfo { duration, .. } = info;
Self { duration: duration.to_owned(), ..Default::default() }
}
}
/// The waveform representation of audio content.
///
/// Must include between 30 and 120 `Amplitude`s.

View File

@ -12,10 +12,9 @@ use super::{
message::MessageContent,
room::{
message::{
AudioInfo, FileInfo, FileMessageEventContent, MessageType, Relation,
RoomMessageEventContent, VideoInfo,
FileInfo, FileMessageEventContent, MessageType, Relation, RoomMessageEventContent,
},
EncryptedFile, ImageInfo, JsonWebKey, MediaSource,
EncryptedFile, JsonWebKey, MediaSource,
},
};
use crate::{serde::Base64, OwnedMxcUri};
@ -108,10 +107,11 @@ impl FileEventContent {
relates_to: Option<Relation>,
) -> Self {
let FileMessageEventContent { body, filename, source, info, message, file } = content;
let FileInfo { mimetype, size, .. } = info.map(|info| *info).unwrap_or_default();
let message = message.unwrap_or_else(|| MessageContent::plain(body));
let file = file.unwrap_or_else(|| {
FileContent::from_room_message_content(source, info.as_deref(), filename)
FileContent::from_room_message_content(source, filename, mimetype, size)
});
Self { message, file, relates_to }
@ -167,16 +167,15 @@ impl FileContent {
/// Create a new `FileContent` with the given media source, file info and filename.
pub fn from_room_message_content(
source: MediaSource,
info: Option<impl Into<FileContentInfo>>,
filename: Option<String>,
mimetype: Option<String>,
size: Option<UInt>,
) -> Self {
let (url, encryption_info) = match source {
MediaSource::Plain(url) => (url, None),
MediaSource::Encrypted(file) => (file.url.clone(), Some(Box::new((&*file).into()))),
};
let info = FileContentInfo::from_room_message_content(info, filename).map(Box::new);
let (url, encryption_info) = source.into_extensible_content();
let info =
FileContentInfo::from_room_message_content(filename, mimetype, size).map(Box::new);
Self { url, encryption_info, info }
Self { url, encryption_info: encryption_info.map(Box::new), info }
}
/// Whether the file is encrypted.
@ -208,51 +207,22 @@ impl FileContentInfo {
Self::default()
}
/// Create a new `FileContentInfo` with the given file info and filename.
/// Create a new `FileContentInfo` with the given filename, mimetype and size.
///
/// Returns `None` if both parameters are `None`.
/// Returns `None` if all parameters are `None`.
pub fn from_room_message_content(
info: Option<impl Into<FileContentInfo>>,
filename: Option<String>,
mimetype: Option<String>,
size: Option<UInt>,
) -> Option<Self> {
if filename.is_none() && info.is_none() {
if filename.is_none() && mimetype.is_none() && size.is_none() {
None
} else {
let mut info: Self = info.map(Into::into).unwrap_or_default();
info.name = filename;
Some(info)
Some(Self { name: filename, mimetype, size })
}
}
}
impl From<&AudioInfo> for FileContentInfo {
fn from(info: &AudioInfo) -> Self {
let AudioInfo { mimetype, size, .. } = info;
Self { mimetype: mimetype.to_owned(), size: size.to_owned(), ..Default::default() }
}
}
impl From<&FileInfo> for FileContentInfo {
fn from(info: &FileInfo) -> Self {
let FileInfo { mimetype, size, .. } = info;
Self { mimetype: mimetype.to_owned(), size: size.to_owned(), ..Default::default() }
}
}
impl From<&ImageInfo> for FileContentInfo {
fn from(info: &ImageInfo) -> Self {
let ImageInfo { mimetype, size, .. } = info;
Self { mimetype: mimetype.to_owned(), size: size.to_owned(), ..Default::default() }
}
}
impl From<&VideoInfo> for FileContentInfo {
fn from(info: &VideoInfo) -> Self {
let VideoInfo { mimetype, size, .. } = info;
Self { mimetype: mimetype.to_owned(), size: size.to_owned(), ..Default::default() }
}
}
/// The encryption info of a file sent to a room with end-to-end encryption enabled.
///
/// To create an instance of this type, first create a `EncryptedContentInit` and convert it via
@ -310,7 +280,7 @@ impl From<EncryptedContentInit> for EncryptedContent {
impl From<&EncryptedFile> for EncryptedContent {
fn from(encrypted: &EncryptedFile) -> Self {
let EncryptedFile { key, iv, hashes, v, .. } = encrypted.to_owned();
Self { key, iv, hashes, v }
let EncryptedFile { key, iv, hashes, v, .. } = encrypted;
Self { key: key.to_owned(), iv: iv.to_owned(), hashes: hashes.to_owned(), v: v.to_owned() }
}
}

View File

@ -104,25 +104,21 @@ impl ImageEventContent {
thumbnail,
caption,
} = content;
let ImageInfo { height, width, mimetype, size, thumbnail_info, thumbnail_source, .. } =
info.map(|info| *info).unwrap_or_default();
let message = message.unwrap_or_else(|| MessageContent::plain(body));
let file = file.unwrap_or_else(|| {
FileContent::from_room_message_content(source, info.as_deref(), None)
FileContent::from_room_message_content(source, None, mimetype, size)
});
let image =
image.or_else(|| info.as_deref().map(|info| Box::new(info.into()))).unwrap_or_default();
let thumbnail = thumbnail
.or_else(|| {
info.as_deref()
.and_then(|info| {
ThumbnailContent::from_room_message_content(
info.thumbnail_source.as_ref(),
info.thumbnail_info.as_deref(),
)
})
.map(|thumbnail| vec![thumbnail])
})
let image = image
.or_else(|| ImageContent::from_room_message_content(width, height).map(Box::new))
.unwrap_or_default();
let thumbnail = thumbnail.unwrap_or_else(|| {
ThumbnailContent::from_room_message_content(thumbnail_source, thumbnail_info)
.into_iter()
.collect()
});
Self { message, file, image, thumbnail, caption, relates_to }
}
@ -165,26 +161,26 @@ impl ImageContent {
Self { height: Some(height), width: Some(width) }
}
/// Creates a new `ImageContent` with the given optional width and height.
///
/// Returns `None` if both parameters are `None`.
pub(crate) fn from_room_message_content(
width: Option<UInt>,
height: Option<UInt>,
) -> Option<Self> {
if width.is_none() && height.is_none() {
None
} else {
Some(Self { width, height })
}
}
/// Whether this `ImageContent` is empty.
pub fn is_empty(&self) -> bool {
self.height.is_none() && self.width.is_none()
}
}
impl From<&ImageInfo> for ImageContent {
fn from(info: &ImageInfo) -> Self {
let ImageInfo { height, width, .. } = info;
Self { height: height.to_owned(), width: width.to_owned() }
}
}
impl From<&ThumbnailInfo> for ImageContent {
fn from(info: &ThumbnailInfo) -> Self {
let ThumbnailInfo { height, width, .. } = info;
Self { height: height.to_owned(), width: width.to_owned() }
}
}
/// Thumbnail content.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
@ -208,13 +204,14 @@ impl ThumbnailContent {
///
/// Returns `None` if no thumbnail was found.
pub fn from_room_message_content(
thumbnail_source: Option<&MediaSource>,
thumbnail_info: Option<&ThumbnailInfo>,
source: Option<MediaSource>,
info: Option<Box<ThumbnailInfo>>,
) -> Option<Self> {
thumbnail_source.map(|thumbnail_source| {
let file =
ThumbnailFileContent::from_room_message_content(thumbnail_source, thumbnail_info);
let image = thumbnail_info.map(|info| Box::new(info.into()));
source.map(|source| {
let ThumbnailInfo { height, width, mimetype, size } = *info.unwrap_or_default();
let file = ThumbnailFileContent::from_room_message_content(source, mimetype, size);
let image = ImageContent::from_room_message_content(width, height).map(Box::new);
Self { file, image }
})
@ -259,18 +256,15 @@ impl ThumbnailFileContent {
///
/// Returns `None` if no thumbnail was found.
fn from_room_message_content(
thumbnail_source: &MediaSource,
thumbnail_info: Option<&ThumbnailInfo>,
source: MediaSource,
mimetype: Option<String>,
size: Option<UInt>,
) -> Self {
match thumbnail_source {
MediaSource::Plain(url) => {
Self::plain(url.to_owned(), thumbnail_info.map(|info| Box::new(info.into())))
}
MediaSource::Encrypted(file) => Self::encrypted(
file.url.clone(),
(&**file).into(),
thumbnail_info.map(|info| Box::new(info.into())),
),
let info =
ThumbnailFileContentInfo::from_room_message_content(mimetype, size).map(Box::new);
match source.into_extensible_content() {
(url, None) => Self::plain(url, info),
(url, Some(encryption_info)) => Self::encrypted(url, encryption_info, info),
}
}
@ -298,11 +292,15 @@ impl ThumbnailFileContentInfo {
pub fn new() -> Self {
Self::default()
}
}
impl From<&ThumbnailInfo> for ThumbnailFileContentInfo {
fn from(info: &ThumbnailInfo) -> Self {
let ThumbnailInfo { mimetype, size, .. } = info;
Self { mimetype: mimetype.to_owned(), size: size.to_owned() }
/// Creates a new `ThumbnailFileContentInfo` with the given optional MIME type and size.
///
/// Returns `None` if both the mimetype and the size are `None`.
fn from_room_message_content(mimetype: Option<String>, size: Option<UInt>) -> Option<Self> {
if mimetype.is_none() && size.is_none() {
None
} else {
Some(Self { mimetype, size })
}
}
}

View File

@ -8,7 +8,7 @@ use js_int::UInt;
use serde::{de, Deserialize, Serialize};
#[cfg(feature = "unstable-msc3551")]
use super::file::{EncryptedContent, FileContent};
use super::file::{EncryptedContent, EncryptedContentInit, FileContent};
#[cfg(feature = "unstable-msc3552")]
use super::{
file::FileContentInfo,
@ -55,6 +55,19 @@ pub enum MediaSource {
Encrypted(Box<EncryptedFile>),
}
#[cfg(feature = "unstable-msc3551")]
impl MediaSource {
pub(crate) fn into_extensible_content(self) -> (OwnedMxcUri, Option<EncryptedContent>) {
match self {
MediaSource::Plain(url) => (url, None),
MediaSource::Encrypted(encrypted_file) => {
let EncryptedFile { url, key, iv, hashes, v } = *encrypted_file;
(url, Some(EncryptedContentInit { key, iv, hashes, v }.into()))
}
}
}
}
// Custom implementation of `Deserialize`, because serde doesn't guarantee what variant will be
// deserialized for "externally tagged"¹ enums where multiple "tag" fields exist.
//

View File

@ -8,7 +8,7 @@ use crate::events::voice::VoiceContent;
#[cfg(feature = "unstable-msc3246")]
use crate::events::{
audio::AudioContent,
file::{FileContent, FileContentInfo},
file::{EncryptedContent, FileContent, FileContentInfo},
message::MessageContent,
};
use crate::{
@ -84,10 +84,22 @@ impl AudioMessageEventContent {
#[cfg(feature = "unstable-msc3246")]
file: Some(FileContent::plain(
url.clone(),
info.as_deref().map(|info| Box::new(info.into())),
info.as_deref().and_then(|info| {
FileContentInfo::from_room_message_content(
None,
info.mimetype.to_owned(),
info.size.to_owned(),
)
.map(Box::new)
}),
)),
#[cfg(feature = "unstable-msc3246")]
audio: Some(info.as_deref().map_or_else(AudioContent::default, Into::into)),
audio: Some(
info.as_deref()
.and_then(|info| info.duration)
.map(AudioContent::from_room_message_content)
.unwrap_or_default(),
),
#[cfg(feature = "unstable-msc3245")]
voice: None,
body,
@ -103,7 +115,11 @@ impl AudioMessageEventContent {
#[cfg(feature = "unstable-msc3246")]
message: Some(MessageContent::plain(body.clone())),
#[cfg(feature = "unstable-msc3246")]
file: Some(FileContent::encrypted(file.url.clone(), (&file).into(), None)),
file: Some(FileContent::encrypted(
file.url.clone(),
EncryptedContent::from(&file),
None,
)),
#[cfg(feature = "unstable-msc3246")]
audio: Some(AudioContent::default()),
#[cfg(feature = "unstable-msc3245")]
@ -184,19 +200,24 @@ impl AudioInfo {
}
/// Create an `AudioInfo` from the given file info and audio info.
///
/// Returns `None` if the `AudioInfo` would be empty.
#[cfg(feature = "unstable-msc3246")]
pub fn from_extensible_content(
file_info: Option<&FileContentInfo>,
audio: &AudioContent,
) -> Option<Self> {
if file_info.is_none() && audio.is_empty() {
let (mimetype, size) = file_info
.map(|info| {
let FileContentInfo { mimetype, size, .. } = info;
(mimetype.to_owned(), size.to_owned())
})
.unwrap_or_default();
let AudioContent { duration, .. } = audio;
if duration.is_none() && mimetype.is_none() && size.is_none() {
None
} else {
let (mimetype, size) = file_info
.map(|info| (info.mimetype.to_owned(), info.size.to_owned()))
.unwrap_or_default();
let AudioContent { duration, .. } = audio;
Some(Self { duration: duration.to_owned(), mimetype, size })
}
}

View File

@ -68,7 +68,14 @@ impl FileMessageEventContent {
#[cfg(feature = "unstable-msc3551")]
file: Some(FileContent::plain(
url.clone(),
info.as_deref().map(|info| Box::new(info.into())),
info.as_deref().and_then(|info| {
FileContentInfo::from_room_message_content(
None,
info.mimetype.to_owned(),
info.size.to_owned(),
)
.map(Box::new)
}),
)),
body,
filename: None,
@ -101,7 +108,10 @@ impl FileMessageEventContent {
message[0].body.clone()
};
let filename = file.info.as_deref().and_then(|info| info.name.clone());
let info = file.info.as_deref().map(|info| Box::new(info.into()));
let info = file.info.as_deref().and_then(|info| {
FileInfo::from_extensible_content(info.mimetype.to_owned(), info.size.to_owned())
.map(Box::new)
});
let source = (&file).into();
Self { message: Some(message), file: Some(file), body, filename, source, info }
@ -138,12 +148,16 @@ impl FileInfo {
pub fn new() -> Self {
Self::default()
}
}
#[cfg(feature = "unstable-msc3551")]
impl From<&FileContentInfo> for FileInfo {
fn from(info: &FileContentInfo) -> Self {
let FileContentInfo { mimetype, size, .. } = info;
Self { mimetype: mimetype.to_owned(), size: size.to_owned(), ..Default::default() }
/// Creates a `FileInfo` with the given optional mimetype and size.
///
/// Returns `None` if the `FileInfo` would be empty.
#[cfg(feature = "unstable-msc3551")]
fn from_extensible_content(mimetype: Option<String>, size: Option<UInt>) -> Option<Self> {
if mimetype.is_none() && size.is_none() {
None
} else {
Some(Self { mimetype, size, ..Default::default() })
}
}
}

View File

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "unstable-msc3552")]
use crate::events::{
file::FileContent,
file::{FileContent, FileContentInfo},
image::{ImageContent, ThumbnailContent},
message::MessageContent,
};
@ -89,17 +89,30 @@ impl ImageMessageEventContent {
#[cfg(feature = "unstable-msc3552")]
file: Some(FileContent::plain(
url.clone(),
info.as_deref().map(|info| Box::new(info.into())),
info.as_deref().and_then(|info| {
FileContentInfo::from_room_message_content(
None,
info.mimetype.to_owned(),
info.size,
)
.map(Box::new)
}),
)),
#[cfg(feature = "unstable-msc3552")]
image: Some(Box::new(info.as_deref().map_or_else(ImageContent::default, Into::into))),
image: Some(Box::new(
info.as_deref()
.and_then(|info| {
ImageContent::from_room_message_content(info.width, info.height)
})
.unwrap_or_default(),
)),
#[cfg(feature = "unstable-msc3552")]
thumbnail: info
.as_deref()
.and_then(|info| {
ThumbnailContent::from_room_message_content(
info.thumbnail_source.as_ref(),
info.thumbnail_info.as_deref(),
info.thumbnail_source.to_owned(),
info.thumbnail_info.to_owned(),
)
})
.map(|thumbnail| vec![thumbnail]),

View File

@ -91,17 +91,34 @@ impl VideoMessageEventContent {
#[cfg(feature = "unstable-msc3553")]
file: Some(FileContent::plain(
url.clone(),
info.as_deref().map(|info| Box::new(info.into())),
info.as_deref().and_then(|info| {
FileContentInfo::from_room_message_content(
None,
info.mimetype.to_owned(),
info.size.to_owned(),
)
.map(Box::new)
}),
)),
#[cfg(feature = "unstable-msc3553")]
video: Some(Box::new(info.as_deref().map_or_else(VideoContent::default, Into::into))),
video: Some(Box::new(
info.as_deref()
.map(|info| {
VideoContent::from_room_message_content(
info.height,
info.width,
info.duration,
)
})
.unwrap_or_default(),
)),
#[cfg(feature = "unstable-msc3553")]
thumbnail: info
.as_deref()
.and_then(|info| {
ThumbnailContent::from_room_message_content(
info.thumbnail_source.as_ref(),
info.thumbnail_info.as_deref(),
info.thumbnail_source.to_owned(),
info.thumbnail_info.to_owned(),
)
})
.map(|thumbnail| vec![thumbnail]),

View File

@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "unstable-msc3552")]
use super::{
file::FileContent,
file::{FileContent, FileContentInfo},
image::{ImageContent, ThumbnailContent},
message::MessageContent,
};
@ -96,13 +96,20 @@ impl StickerEventContent {
#[cfg(feature = "unstable-msc3552")]
message: Some(MessageContent::plain(body.clone())),
#[cfg(feature = "unstable-msc3552")]
file: Some(FileContent::plain(url.clone(), Some(Box::new((&info).into())))),
file: Some(FileContent::plain(
url.clone(),
FileContentInfo::from_room_message_content(None, info.mimetype.clone(), info.size)
.map(Box::new),
)),
#[cfg(feature = "unstable-msc3552")]
image: Some(Box::new((&info).into())),
image: Some(Box::new(
ImageContent::from_room_message_content(info.width, info.height)
.unwrap_or_default(),
)),
#[cfg(feature = "unstable-msc3552")]
thumbnail: ThumbnailContent::from_room_message_content(
info.thumbnail_source.as_ref(),
info.thumbnail_info.as_deref(),
info.thumbnail_source.clone(),
info.thumbnail_info.clone(),
)
.map(|thumbnail| vec![thumbnail]),
#[cfg(feature = "unstable-msc3552")]

View File

@ -105,25 +105,29 @@ impl VideoEventContent {
thumbnail,
caption,
} = content;
let VideoInfo {
duration,
height,
width,
mimetype,
size,
thumbnail_info,
thumbnail_source,
..
} = info.map(|info| *info).unwrap_or_default();
let message = message.unwrap_or_else(|| MessageContent::plain(body));
let file = file.unwrap_or_else(|| {
FileContent::from_room_message_content(source, info.as_deref(), None)
FileContent::from_room_message_content(source, None, mimetype, size)
});
let video = video.unwrap_or_else(|| {
Box::new(VideoContent::from_room_message_content(height, width, duration))
});
let thumbnail = thumbnail.unwrap_or_else(|| {
ThumbnailContent::from_room_message_content(thumbnail_source, thumbnail_info)
.into_iter()
.collect()
});
let video =
video.or_else(|| info.as_deref().map(|info| Box::new(info.into()))).unwrap_or_default();
let thumbnail = thumbnail
.or_else(|| {
info.as_deref()
.and_then(|info| {
ThumbnailContent::from_room_message_content(
info.thumbnail_source.as_ref(),
info.thumbnail_info.as_deref(),
)
})
.map(|thumbnail| vec![thumbnail])
})
.unwrap_or_default();
Self { message, file, video, thumbnail, caption, relates_to }
}
@ -169,15 +173,17 @@ impl VideoContent {
Self::default()
}
/// Creates a new `VideoContent` with the given optional height, width and duration.
pub(crate) fn from_room_message_content(
height: Option<UInt>,
width: Option<UInt>,
duration: Option<Duration>,
) -> Self {
Self { height, width, duration }
}
/// Whether this `VideoContent` is empty.
pub fn is_empty(&self) -> bool {
self.height.is_none() && self.width.is_none() && self.duration.is_none()
}
}
impl From<&VideoInfo> for VideoContent {
fn from(info: &VideoInfo) -> Self {
let VideoInfo { height, width, duration, .. } = info;
Self { height: height.to_owned(), width: width.to_owned(), duration: duration.to_owned() }
}
}

View File

@ -9,7 +9,9 @@ use super::{
audio::AudioContent,
file::FileContent,
message::{MessageContent, TryFromExtensibleError},
room::message::{AudioMessageEventContent, MessageType, Relation, RoomMessageEventContent},
room::message::{
AudioInfo, AudioMessageEventContent, MessageType, Relation, RoomMessageEventContent,
},
};
/// The payload for an extensible voice message.
@ -82,12 +84,15 @@ impl VoiceEventContent {
relates_to: Option<Relation>,
) -> Result<Self, TryFromExtensibleError> {
let AudioMessageEventContent { body, source, info, message, file, audio, voice } = content;
let AudioInfo { duration, mimetype, size } = info.map(|info| *info).unwrap_or_default();
let message = message.unwrap_or_else(|| MessageContent::plain(body));
let file = file.unwrap_or_else(|| {
FileContent::from_room_message_content(source, info.as_deref(), None)
FileContent::from_room_message_content(source, None, mimetype, size)
});
let audio = audio.or_else(|| info.as_deref().map(Into::into)).unwrap_or_default();
let audio = audio
.or_else(|| duration.map(AudioContent::from_room_message_content))
.unwrap_or_default();
let voice = if let Some(voice) = voice {
voice
} else {