1use anyhow::{Context as _, Result};
2use num_traits::ToPrimitive;
3use serde::{Deserialize, Serialize};
4
5use crate::context::Context;
6use crate::log::warn;
7use crate::message::Message;
8use crate::message::Viewtype;
9use crate::param::{Param, Params};
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13pub struct PostMsgMetadata {
14 pub(crate) size: u64,
16 pub(crate) viewtype: Viewtype,
18 pub(crate) filename: String,
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub(crate) wh: Option<(i32, i32)>,
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub(crate) duration: Option<i32>,
26}
27
28impl PostMsgMetadata {
29 pub(crate) async fn from_msg(context: &Context, message: &Message) -> Result<Option<Self>> {
31 if !message.viewtype.has_file() {
32 return Ok(None);
33 }
34
35 let size = message
36 .get_filebytes(context)
37 .await?
38 .context("Unexpected: file has no size")?;
39 let filename = message
40 .param
41 .get(Param::Filename)
42 .unwrap_or_default()
43 .to_owned();
44 let filename = match message.viewtype {
45 Viewtype::Webxdc => message
46 .get_webxdc_info(context)
47 .await
48 .map(|info| info.name)
49 .unwrap_or_else(|_| filename),
50 _ => filename,
51 };
52 let wh = {
53 match (
54 message.param.get_int(Param::Width),
55 message.param.get_int(Param::Height),
56 ) {
57 (None, None) => None,
58 (Some(width), Some(height)) => Some((width, height)),
59 wh => {
60 warn!(
61 context,
62 "Message {} misses width or height: {:?}.", message.id, wh
63 );
64 None
65 }
66 }
67 };
68 let duration = message.param.get_int(Param::Duration);
69
70 Ok(Some(Self {
71 size,
72 filename,
73 viewtype: message.viewtype,
74 wh,
75 duration,
76 }))
77 }
78
79 pub(crate) fn to_header_value(&self) -> Result<String> {
80 Ok(serde_json::to_string(&self)?)
81 }
82
83 pub(crate) fn try_from_header_value(value: &str) -> Result<Self> {
84 Ok(serde_json::from_str(value)?)
85 }
86}
87
88impl Params {
89 pub(crate) fn apply_post_msg_metadata(
91 &mut self,
92 post_msg_metadata: &PostMsgMetadata,
93 ) -> &mut Self {
94 self.set(Param::PostMessageFileBytes, post_msg_metadata.size);
95 if !post_msg_metadata.filename.is_empty() {
96 self.set(Param::Filename, &post_msg_metadata.filename);
97 }
98 self.set_i64(
99 Param::PostMessageViewtype,
100 post_msg_metadata.viewtype.to_i64().unwrap_or_default(),
101 );
102 if let Some((width, height)) = post_msg_metadata.wh {
103 self.set(Param::Width, width);
104 self.set(Param::Height, height);
105 }
106 if let Some(duration) = post_msg_metadata.duration {
107 self.set(Param::Duration, duration);
108 }
109
110 self
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use anyhow::Result;
117 use pretty_assertions::assert_eq;
118
119 use crate::{
120 message::{Message, Viewtype},
121 test_utils::{TestContextManager, create_test_image},
122 };
123
124 use super::PostMsgMetadata;
125
126 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
128 async fn test_build_from_file_msg() -> Result<()> {
129 let mut tcm = TestContextManager::new();
130 let alice = &tcm.alice().await;
131
132 let mut file_msg = Message::new(Viewtype::File);
133 file_msg.set_file_from_bytes(alice, "test.bin", &vec![0u8; 1_000_000], None)?;
134 let post_msg_metadata = PostMsgMetadata::from_msg(alice, &file_msg).await?;
135 assert_eq!(
136 post_msg_metadata,
137 Some(PostMsgMetadata {
138 size: 1_000_000,
139 viewtype: Viewtype::File,
140 filename: "test.bin".to_string(),
141 wh: None,
142 duration: None,
143 })
144 );
145 Ok(())
146 }
147
148 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
150 async fn test_build_from_image_msg() -> Result<()> {
151 let mut tcm = TestContextManager::new();
152 let alice = &tcm.alice().await;
153 let mut image_msg = Message::new(Viewtype::Image);
154
155 let (width, height) = (1080, 1920);
156 let test_img = create_test_image(width, height)?;
157 image_msg.set_file_from_bytes(alice, "vacation.png", &test_img, None)?;
158 image_msg.try_calc_and_set_dimensions(alice).await?;
161 let post_msg_metadata = PostMsgMetadata::from_msg(alice, &image_msg).await?;
162 assert_eq!(
163 post_msg_metadata,
164 Some(PostMsgMetadata {
165 size: 1816098,
166 viewtype: Viewtype::Image,
167 filename: "vacation.png".to_string(),
168 wh: Some((width as i32, height as i32)),
169 duration: None,
170 })
171 );
172
173 Ok(())
174 }
175
176 #[test]
178 fn test_serialize_to_header() -> Result<()> {
179 assert_eq!(
180 PostMsgMetadata {
181 size: 1_000_000,
182 viewtype: Viewtype::File,
183 filename: "test.bin".to_string(),
184 wh: None,
185 duration: None,
186 }
187 .to_header_value()?,
188 "{\"size\":1000000,\"viewtype\":\"File\",\"filename\":\"test.bin\"}"
189 );
190 assert_eq!(
191 PostMsgMetadata {
192 size: 5_342_765,
193 viewtype: Viewtype::Image,
194 filename: "vacation.png".to_string(),
195 wh: Some((1080, 1920)),
196 duration: None,
197 }
198 .to_header_value()?,
199 "{\"size\":5342765,\"viewtype\":\"Image\",\"filename\":\"vacation.png\",\"wh\":[1080,1920]}"
200 );
201 assert_eq!(
202 PostMsgMetadata {
203 size: 5_000,
204 viewtype: Viewtype::Audio,
205 filename: "audio-DD-MM-YY.ogg".to_string(),
206 wh: None,
207 duration: Some(152_310),
208 }
209 .to_header_value()?,
210 "{\"size\":5000,\"viewtype\":\"Audio\",\"filename\":\"audio-DD-MM-YY.ogg\",\"duration\":152310}"
211 );
212
213 Ok(())
214 }
215
216 #[test]
219 fn test_deserialize_from_header() -> Result<()> {
220 assert_eq!(
221 serde_json::from_str::<PostMsgMetadata>(
222 "{\"size\":1000000,\"viewtype\":\"File\",\"filename\":\"test.bin\",\"wh\":null,\"duration\":null}"
223 )?,
224 PostMsgMetadata {
225 size: 1_000_000,
226 viewtype: Viewtype::File,
227 filename: "test.bin".to_string(),
228 wh: None,
229 duration: None,
230 }
231 );
232 assert_eq!(
233 serde_json::from_str::<PostMsgMetadata>(
234 "{\"size\":5342765,\"viewtype\":\"Image\",\"filename\":\"vacation.png\",\"wh\":[1080,1920]}"
235 )?,
236 PostMsgMetadata {
237 size: 5_342_765,
238 viewtype: Viewtype::Image,
239 filename: "vacation.png".to_string(),
240 wh: Some((1080, 1920)),
241 duration: None,
242 }
243 );
244 assert_eq!(
245 serde_json::from_str::<PostMsgMetadata>(
246 "{\"size\":5000,\"viewtype\":\"Audio\",\"filename\":\"audio-DD-MM-YY.ogg\",\"duration\":152310}"
247 )?,
248 PostMsgMetadata {
249 size: 5_000,
250 viewtype: Viewtype::Audio,
251 filename: "audio-DD-MM-YY.ogg".to_string(),
252 wh: None,
253 duration: Some(152_310),
254 }
255 );
256
257 Ok(())
258 }
259}