mirror of
https://github.com/youwen5/iamb.git
synced 2025-06-20 05:39:52 -07:00
173 lines
5.3 KiB
Rust
173 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 modalkit::tui::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)
|
||
|
.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)
|
||
|
})
|
||
|
},
|
||
|
}
|
||
|
}
|