1- use std:: { collections:: HashMap , pin:: pin, sync:: Arc } ;
1+ use std:: { collections:: HashMap , fs , path :: PathBuf , pin:: pin, sync:: Arc } ;
22
33use anyhow:: { Context , Result } ;
44use 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
14471453impl 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
14541465impl 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
0 commit comments