mirror of
https://github.com/youwen5/iamb.git
synced 2025-06-19 21:29:52 -07:00
172 lines
5.3 KiB
Rust
172 lines
5.3 KiB
Rust
use std::{
|
|
fs::File,
|
|
io::{Read, Write},
|
|
path::{Path, PathBuf},
|
|
};
|
|
|
|
use matrix_sdk::{
|
|
media::{MediaFormat, MediaRequest},
|
|
ruma::{
|
|
events::{
|
|
room::{
|
|
message::{MessageType, RoomMessageEventContent},
|
|
MediaSource,
|
|
},
|
|
MessageLikeEvent,
|
|
},
|
|
OwnedEventId,
|
|
OwnedRoomId,
|
|
},
|
|
Media,
|
|
};
|
|
use ratatui::layout::Rect;
|
|
use ratatui_image::Resize;
|
|
|
|
use crate::{
|
|
base::{AsyncProgramStore, ChatStore, IambError},
|
|
config::ImagePreviewSize,
|
|
message::ImageStatus,
|
|
};
|
|
|
|
pub fn source_from_event(
|
|
ev: &MessageLikeEvent<RoomMessageEventContent>,
|
|
) -> Option<(OwnedEventId, MediaSource)> {
|
|
if let MessageLikeEvent::Original(ev) = &ev {
|
|
if let MessageType::Image(c) = &ev.content.msgtype {
|
|
return Some((ev.event_id.clone(), c.source.clone()));
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
impl From<ImagePreviewSize> for Rect {
|
|
fn from(value: ImagePreviewSize) -> Self {
|
|
Rect::new(0, 0, value.width as _, value.height as _)
|
|
}
|
|
}
|
|
impl From<Rect> for ImagePreviewSize {
|
|
fn from(rect: Rect) -> Self {
|
|
ImagePreviewSize { width: rect.width as _, height: rect.height as _ }
|
|
}
|
|
}
|
|
|
|
/// Download and prepare the preview, and then lock the store to insert it.
|
|
pub fn spawn_insert_preview(
|
|
store: AsyncProgramStore,
|
|
room_id: OwnedRoomId,
|
|
event_id: OwnedEventId,
|
|
source: MediaSource,
|
|
media: Media,
|
|
cache_dir: PathBuf,
|
|
) {
|
|
tokio::spawn(async move {
|
|
let img = download_or_load(event_id.to_owned(), source, media, cache_dir)
|
|
.await
|
|
.map(std::io::Cursor::new)
|
|
.map(image::io::Reader::new)
|
|
.map_err(IambError::Matrix)
|
|
.and_then(|reader| reader.with_guessed_format().map_err(IambError::IOError))
|
|
.and_then(|reader| reader.decode().map_err(IambError::Image));
|
|
|
|
match img {
|
|
Err(err) => {
|
|
try_set_msg_preview_error(
|
|
&mut store.lock().await.application,
|
|
room_id,
|
|
event_id,
|
|
err,
|
|
);
|
|
},
|
|
Ok(img) => {
|
|
let mut locked = store.lock().await;
|
|
let ChatStore { rooms, picker, settings, .. } = &mut locked.application;
|
|
|
|
match picker
|
|
.as_mut()
|
|
.ok_or_else(|| IambError::Preview("Picker is empty".to_string()))
|
|
.and_then(|picker| {
|
|
Ok((
|
|
picker,
|
|
rooms
|
|
.get_or_default(room_id.clone())
|
|
.get_event_mut(&event_id)
|
|
.ok_or_else(|| {
|
|
IambError::Preview("Message not found".to_string())
|
|
})?,
|
|
settings.tunables.image_preview.clone().ok_or_else(|| {
|
|
IambError::Preview("image_preview settings not found".to_string())
|
|
})?,
|
|
))
|
|
})
|
|
.and_then(|(picker, msg, image_preview)| {
|
|
picker
|
|
.new_protocol(img, image_preview.size.into(), Resize::Fit(None))
|
|
.map_err(|err| IambError::Preview(format!("{err:?}")))
|
|
.map(|backend| (backend, msg))
|
|
}) {
|
|
Err(err) => {
|
|
try_set_msg_preview_error(&mut locked.application, room_id, event_id, err);
|
|
},
|
|
Ok((backend, msg)) => {
|
|
msg.image_preview = ImageStatus::Loaded(backend);
|
|
},
|
|
}
|
|
},
|
|
}
|
|
});
|
|
}
|
|
|
|
fn try_set_msg_preview_error(
|
|
application: &mut ChatStore,
|
|
room_id: OwnedRoomId,
|
|
event_id: OwnedEventId,
|
|
err: IambError,
|
|
) {
|
|
let rooms = &mut application.rooms;
|
|
|
|
match rooms
|
|
.get_or_default(room_id.clone())
|
|
.get_event_mut(&event_id)
|
|
.ok_or_else(|| IambError::Preview("Message not found".to_string()))
|
|
{
|
|
Ok(msg) => msg.image_preview = ImageStatus::Error(format!("{err:?}")),
|
|
Err(err) => {
|
|
tracing::error!(
|
|
"Failed to set error on msg.image_backend for event {}, room {}: {}",
|
|
event_id,
|
|
room_id,
|
|
err
|
|
)
|
|
},
|
|
}
|
|
}
|
|
|
|
async fn download_or_load(
|
|
event_id: OwnedEventId,
|
|
source: MediaSource,
|
|
media: Media,
|
|
mut cache_path: PathBuf,
|
|
) -> Result<Vec<u8>, matrix_sdk::Error> {
|
|
cache_path.push(Path::new(event_id.localpart()));
|
|
|
|
match File::open(&cache_path) {
|
|
Ok(mut f) => {
|
|
let mut buffer = Vec::new();
|
|
f.read_to_end(&mut buffer)?;
|
|
Ok(buffer)
|
|
},
|
|
Err(_) => {
|
|
media
|
|
.get_media_content(&MediaRequest { source, format: MediaFormat::File }, true)
|
|
.await
|
|
.and_then(|buffer| {
|
|
if let Err(err) =
|
|
File::create(&cache_path).and_then(|mut f| f.write_all(&buffer))
|
|
{
|
|
return Err(err.into());
|
|
}
|
|
Ok(buffer)
|
|
})
|
|
},
|
|
}
|
|
}
|