Skip to content

Commit 8675b4b

Browse files
committed
feat(composer): add support for attachments in drafts
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
1 parent 430304f commit 8675b4b

6 files changed

Lines changed: 367 additions & 16 deletions

File tree

bindings/matrix-sdk-ffi/src/room/mod.rs

Lines changed: 250 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{collections::HashMap, pin::pin, sync::Arc};
1+
use std::{collections::HashMap, fs, path::PathBuf, pin::pin, sync::Arc};
22

33
use anyhow::{Context, Result};
44
use futures_util::{pin_mut, StreamExt};
@@ -9,7 +9,8 @@ use matrix_sdk::{
99
TryFromReportedContentScoreError,
1010
},
1111
send_queue::RoomSendQueueUpdate as SdkRoomSendQueueUpdate,
12-
ComposerDraft as SdkComposerDraft, ComposerDraftType as SdkComposerDraftType, EncryptionState,
12+
ComposerDraft as SdkComposerDraft, ComposerDraftType as SdkComposerDraftType,
13+
DraftAttachment as SdkDraftAttachment, DraftAttachmentContent, DraftThumbnail, EncryptionState,
1314
PredecessorRoom as SdkPredecessorRoom, RoomHero as SdkRoomHero, RoomMemberships, RoomState,
1415
SuccessorRoom as SdkSuccessorRoom,
1516
};
@@ -45,11 +46,14 @@ use crate::{
4546
live_location_share::{LastLocation, LiveLocationShare},
4647
room_member::{RoomMember, RoomMemberWithSenderInfo},
4748
room_preview::RoomPreview,
48-
ruma::{ImageInfo, LocationContent, MediaSource},
49+
ruma::{
50+
AudioInfo, FileInfo, ImageInfo, LocationContent, MediaSource, ThumbnailInfo, VideoInfo,
51+
},
4952
runtime::get_runtime_handle,
5053
timeline::{
5154
configuration::{TimelineConfiguration, TimelineFilter},
5255
AbstractProgress, EventTimelineItem, LatestEventValue, ReceiptType, SendHandle, Timeline,
56+
UploadSource,
5357
},
5458
utils::{u64_to_uint, AsyncRuntimeDropped},
5559
TaskHandle,
@@ -1442,21 +1446,259 @@ pub struct ComposerDraft {
14421446
pub html_text: Option<String>,
14431447
/// The type of draft.
14441448
pub draft_type: ComposerDraftType,
1449+
/// Attachments associated with this draft.
1450+
pub attachments: Vec<DraftAttachment>,
14451451
}
14461452

14471453
impl From<SdkComposerDraft> for ComposerDraft {
14481454
fn from(value: SdkComposerDraft) -> Self {
1449-
let SdkComposerDraft { plain_text, html_text, draft_type } = value;
1450-
Self { plain_text, html_text, draft_type: draft_type.into() }
1455+
let SdkComposerDraft { plain_text, html_text, draft_type, attachments } = value;
1456+
Self {
1457+
plain_text,
1458+
html_text,
1459+
draft_type: draft_type.into(),
1460+
attachments: attachments.into_iter().map(|a| a.into()).collect(),
1461+
}
14511462
}
14521463
}
14531464

14541465
impl TryFrom<ComposerDraft> for SdkComposerDraft {
1455-
type Error = ruma::IdParseError;
1466+
type Error = ClientError;
14561467

14571468
fn try_from(value: ComposerDraft) -> std::result::Result<Self, Self::Error> {
1458-
let ComposerDraft { plain_text, html_text, draft_type } = value;
1459-
Ok(Self { plain_text, html_text, draft_type: draft_type.try_into()? })
1469+
let ComposerDraft { plain_text, html_text, draft_type, attachments } = value;
1470+
Ok(Self {
1471+
plain_text,
1472+
html_text,
1473+
draft_type: draft_type.try_into()?,
1474+
attachments: attachments
1475+
.into_iter()
1476+
.map(|a| a.try_into())
1477+
.collect::<std::result::Result<Vec<_>, _>>()?,
1478+
})
1479+
}
1480+
}
1481+
1482+
/// An attachment stored with a composer draft.
1483+
#[derive(uniffi::Enum)]
1484+
pub enum DraftAttachment {
1485+
Audio { audio_info: AudioInfo, source: UploadSource },
1486+
File { file_info: FileInfo, source: UploadSource },
1487+
Image { image_info: ImageInfo, source: UploadSource, thumbnail_source: Option<UploadSource> },
1488+
Video { video_info: VideoInfo, source: UploadSource, thumbnail_source: Option<UploadSource> },
1489+
}
1490+
1491+
#[cfg(feature = "unstable-msc4274")]
1492+
impl From<SdkDraftAttachment> for DraftAttachment {
1493+
fn from(value: SdkDraftAttachment) -> Self {
1494+
match value.content {
1495+
DraftAttachmentContent::Image {
1496+
data,
1497+
mimetype,
1498+
size,
1499+
width,
1500+
height,
1501+
blurhash,
1502+
thumbnail,
1503+
} => {
1504+
let thumbnail_source = thumbnail.as_ref().map(|t| UploadSource::Data {
1505+
bytes: t.data.clone(),
1506+
filename: t.filename.clone(),
1507+
});
1508+
let thumbnail_info = thumbnail.map(|t| ThumbnailInfo {
1509+
width: t.width,
1510+
height: t.height,
1511+
mimetype: t.mimetype,
1512+
size: t.size,
1513+
});
1514+
DraftAttachment::Image {
1515+
image_info: ImageInfo {
1516+
height,
1517+
width,
1518+
mimetype,
1519+
size,
1520+
thumbnail_info,
1521+
thumbnail_source: None,
1522+
blurhash,
1523+
is_animated: None,
1524+
},
1525+
source: UploadSource::Data { bytes: data, filename: value.filename },
1526+
thumbnail_source,
1527+
}
1528+
}
1529+
DraftAttachmentContent::Video {
1530+
data,
1531+
mimetype,
1532+
size,
1533+
width,
1534+
height,
1535+
duration,
1536+
blurhash,
1537+
thumbnail,
1538+
} => {
1539+
let thumbnail_source = thumbnail.as_ref().map(|t| UploadSource::Data {
1540+
bytes: t.data.clone(),
1541+
filename: t.filename.clone(),
1542+
});
1543+
let thumbnail_info = thumbnail.map(|t| ThumbnailInfo {
1544+
width: t.width,
1545+
height: t.height,
1546+
mimetype: t.mimetype,
1547+
size: t.size,
1548+
});
1549+
DraftAttachment::Video {
1550+
video_info: VideoInfo {
1551+
duration,
1552+
height,
1553+
width,
1554+
mimetype,
1555+
size,
1556+
thumbnail_info,
1557+
thumbnail_source: None,
1558+
blurhash,
1559+
},
1560+
source: UploadSource::Data { bytes: data, filename: value.filename },
1561+
thumbnail_source,
1562+
}
1563+
}
1564+
DraftAttachmentContent::Audio { data, mimetype, size, duration } => {
1565+
DraftAttachment::Audio {
1566+
audio_info: AudioInfo { duration, size, mimetype },
1567+
source: UploadSource::Data { bytes: data, filename: value.filename },
1568+
}
1569+
}
1570+
DraftAttachmentContent::File { data, mimetype, size } => DraftAttachment::File {
1571+
file_info: FileInfo {
1572+
mimetype,
1573+
size,
1574+
thumbnail_info: None,
1575+
thumbnail_source: None,
1576+
},
1577+
source: UploadSource::Data { bytes: data, filename: value.filename },
1578+
},
1579+
}
1580+
}
1581+
}
1582+
1583+
/// Resolve the bytes and filename from an `UploadSource`, reading the file
1584+
/// contents if needed.
1585+
fn read_upload_source(source: UploadSource) -> Result<(Vec<u8>, String), ClientError> {
1586+
match source {
1587+
UploadSource::Data { bytes, filename } => Ok((bytes, filename)),
1588+
UploadSource::File { filename } => {
1589+
let path: PathBuf = filename.into();
1590+
let filename = path
1591+
.file_name()
1592+
.ok_or(ClientError::Generic {
1593+
msg: "Invalid attachment path".to_owned(),
1594+
details: None,
1595+
})?
1596+
.to_str()
1597+
.ok_or(ClientError::Generic {
1598+
msg: "Invalid attachment path".to_owned(),
1599+
details: None,
1600+
})?
1601+
.to_owned();
1602+
1603+
let bytes = fs::read(&path).map_err(|_| ClientError::Generic {
1604+
msg: "Could not load file".to_owned(),
1605+
details: None,
1606+
})?;
1607+
1608+
Ok((bytes, filename))
1609+
}
1610+
}
1611+
}
1612+
1613+
#[cfg(feature = "unstable-msc4274")]
1614+
impl TryFrom<DraftAttachment> for SdkDraftAttachment {
1615+
type Error = ClientError;
1616+
1617+
fn try_from(value: DraftAttachment) -> Result<Self, Self::Error> {
1618+
match value {
1619+
DraftAttachment::Image { image_info, source, thumbnail_source, .. } => {
1620+
let (data, filename) = read_upload_source(source)?;
1621+
let thumbnail = match (image_info.thumbnail_info, thumbnail_source) {
1622+
(Some(info), Some(source)) => {
1623+
let (data, filename) = read_upload_source(source)?;
1624+
Some(DraftThumbnail {
1625+
filename,
1626+
data,
1627+
mimetype: info.mimetype,
1628+
width: info.width,
1629+
height: info.height,
1630+
size: info.size,
1631+
})
1632+
}
1633+
_ => None,
1634+
};
1635+
Ok(Self {
1636+
filename,
1637+
content: DraftAttachmentContent::Image {
1638+
data,
1639+
mimetype: image_info.mimetype,
1640+
size: image_info.size,
1641+
width: image_info.width,
1642+
height: image_info.height,
1643+
blurhash: image_info.blurhash,
1644+
thumbnail,
1645+
},
1646+
})
1647+
}
1648+
DraftAttachment::Video { video_info, source, thumbnail_source, .. } => {
1649+
let (data, filename) = read_upload_source(source)?;
1650+
let thumbnail = match (video_info.thumbnail_info, thumbnail_source) {
1651+
(Some(info), Some(source)) => {
1652+
let (data, filename) = read_upload_source(source)?;
1653+
Some(DraftThumbnail {
1654+
filename,
1655+
data,
1656+
mimetype: info.mimetype,
1657+
width: info.width,
1658+
height: info.height,
1659+
size: info.size,
1660+
})
1661+
}
1662+
_ => None,
1663+
};
1664+
Ok(Self {
1665+
filename,
1666+
content: DraftAttachmentContent::Video {
1667+
data,
1668+
mimetype: video_info.mimetype,
1669+
size: video_info.size,
1670+
width: video_info.width,
1671+
height: video_info.height,
1672+
duration: video_info.duration,
1673+
blurhash: video_info.blurhash,
1674+
thumbnail,
1675+
},
1676+
})
1677+
}
1678+
DraftAttachment::Audio { audio_info, source, .. } => {
1679+
let (data, filename) = read_upload_source(source)?;
1680+
Ok(Self {
1681+
filename,
1682+
content: DraftAttachmentContent::Audio {
1683+
data,
1684+
mimetype: audio_info.mimetype,
1685+
size: audio_info.size,
1686+
duration: audio_info.duration,
1687+
},
1688+
})
1689+
}
1690+
DraftAttachment::File { file_info, source, .. } => {
1691+
let (data, filename) = read_upload_source(source)?;
1692+
Ok(Self {
1693+
filename,
1694+
content: DraftAttachmentContent::File {
1695+
data,
1696+
mimetype: file_info.mimetype,
1697+
size: file_info.size,
1698+
},
1699+
})
1700+
}
1701+
}
14601702
}
14611703
}
14621704

crates/matrix-sdk-base/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,9 @@ pub use room::{
6464
RoomState, RoomStateFilter, SuccessorRoom, apply_redaction,
6565
};
6666
pub use store::{
67-
ComposerDraft, ComposerDraftType, QueueWedgeError, StateChanges, StateStore, StateStoreDataKey,
68-
StateStoreDataValue, StoreError, ThreadSubscriptionCatchupToken,
67+
ComposerDraft, ComposerDraftType, DraftAttachment, DraftAttachmentContent, DraftThumbnail,
68+
QueueWedgeError, StateChanges, StateStore, StateStoreDataKey, StateStoreDataValue, StoreError,
69+
ThreadSubscriptionCatchupToken,
6970
};
7071
pub use utils::{
7172
MinimalRoomMemberEvent, MinimalStateEvent, OriginalMinimalStateEvent, RedactedMinimalStateEvent,

crates/matrix-sdk-base/src/store/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,9 @@ pub use self::{
9494
SentMediaInfo, SentRequestKey, SerializableEventContent,
9595
},
9696
traits::{
97-
ComposerDraft, ComposerDraftType, DynStateStore, IntoStateStore, ServerInfo, StateStore,
98-
StateStoreDataKey, StateStoreDataValue, StateStoreExt, ThreadSubscriptionCatchupToken,
99-
WellKnownResponse,
97+
ComposerDraft, ComposerDraftType, DraftAttachment, DraftAttachmentContent, DraftThumbnail,
98+
DynStateStore, IntoStateStore, ServerInfo, StateStore, StateStoreDataKey,
99+
StateStoreDataValue, StateStoreExt, ThreadSubscriptionCatchupToken, WellKnownResponse,
100100
},
101101
};
102102

0 commit comments

Comments
 (0)