J7PC7UGJE64XZIJXE5YETSHYZUUHSH2B52OA5E5JVQUIF6P72JAQC checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117"
checksum = "b78a366903f506d2ad52ca8dc552102ffdd3e937ba8a227f024dc1d1eae28575"dependencies = ["tinyvec_macros",][[package]]name = "tinyvec_macros"version = "0.1.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
infer = { version = "0.3.0", default-features = false }
}pub fn infer_mimetype(data: &[u8]) -> String {infer::get(&data).map(|filetype| filetype.mime_type().to_string()).unwrap_or_else(|| String::from("application/octet-stream"))}pub fn get_filename(path: impl AsRef<Path>) -> String {path.as_ref().file_name().map(|s| s.to_string_lossy().to_string()).unwrap_or_else(|| String::from("unknown"))
fn download_thumbnail_for_event(tevent: &TimelineEvent) -> Option<(bool, Uri)> {if let Some(thumbnail_url) = tevent.thumbnail_url() {if make_content_path(&thumbnail_url).exists() {Some((true, thumbnail_url))} else {Some((false, thumbnail_url))}} else if let (Some(content_size), Some(content_url)) =(tevent.content_size(), tevent.content_url()){if make_content_path(&content_url).exists() {Some((true, content_url))} else if content_size < 1000 * 1000 {Some((false, content_url))} else {None}} else {None}}
for ev in events_after.iter().chain(events_before.iter()) {if let Some(content_url) = ev.thumbnail_url() {if make_content_path(&content_url).exists() {read_urls.push(content_url)} else {download_urls.push(content_url);}}}
thumbnails = events_after.iter().chain(events_before.iter()).flat_map(|tevent| Client::download_thumbnail_for_event(tevent)).collect::<Vec<_>>();
pub fn process_sync_response(&mut self,response: sync_events::Response,) -> (Vec<Uri>, Vec<Uri>) {let mut download_urls = vec![];let mut read_urls = vec![];
pub fn process_sync_response(&mut self, response: sync_events::Response) -> Vec<(bool, Uri)> {let mut thumbnails = vec![];
if let Some(content_url) = tevent.thumbnail_url() {if make_content_path(&content_url).exists() {read_urls.push(content_url)} else {download_urls.push(content_url);}
if let Some(thumbnail_data) = Client::download_thumbnail_for_event(&tevent) {thumbnails.push(thumbnail_data);
}}None}pub fn content_size(&self) -> Option<usize> {if let Some(content) = self.message_content() {if let AnyMessageEventContent::RoomMessage(content) = content {return match content {MessageEventContent::Image(image) => {image.info.map(|i| i.size.map(|s| u64::from(s) as usize))}MessageEventContent::Video(video) => {video.info.map(|i| i.size.map(|s| u64::from(s) as usize))}MessageEventContent::Audio(audio) => {audio.info.map(|i| i.size.map(|s| u64::from(s) as usize))}MessageEventContent::File(file) => {file.info.map(|i| i.size.map(|s| u64::from(s) as usize))}_ => None,}.flatten();
let content_url = image.url;image.info.map(|i| {let thumbnail_url = i.thumbnail_url;// TODO: check if thumbnail is below a size limit?// Look at the speclet content_size = i.size;thumbnail_url.unwrap_or_else(|| {if let Some(url) = content_url {if let Some(size) = content_size {if size < UInt::from(1000_u32 * 1000) {return url;}}}String::new()})})
image.info.map(|i| i.thumbnail_url).flatten()
if let Some(thumbnail_image) = timeline_event.thumbnail_url().map(|thumbnail_url| thumbnail_store.get_thumbnail(&thumbnail_url)).unwrap_or(None).map(|handle| Image::new(handle.clone()).width(Length::Fill))
if let Some(thumbnail_image) = {if is_thumbnail {Some(content_url.clone())} else {timeline_event.thumbnail_url()}}.map(|thumbnail_url| {thumbnail_store.get_thumbnail(&thumbnail_url).map(|handle| Image::new(handle.clone()).width(Length::Fill))}).flatten()
events::{room::message::MessageEventContent, AnyMessageEventContent},
events::room::message::FileMessageEventContent,events::room::message::VideoInfo,events::room::message::VideoMessageEventContent,events::room::ThumbnailInfo,events::{room::{message::{AudioInfo, AudioMessageEventContent, FileInfo, ImageMessageEventContent,MessageEventContent,},ImageInfo,},AnyMessageEventContent,},
let path = make_content_path(&content_url);return if path.exists() {Command::perform(async move { Ok(path) }, process_path_result)
let content_path = media::make_content_path(&content_url);return if content_path.exists() {Command::perform(async move { Ok(content_path) }, process_path_result)
tokio::fs::write(&path, raw_data.as_slice()).await?;Ok(if is_thumbnail {Some((path, content_url, raw_data))} else {None})
tokio::fs::write(&content_path, raw_data.as_slice()).await?;Ok((content_path,if is_thumbnail {Some((content_url, raw_data))} else {None},))
Message::SendFile => {/*TODO: Investigate implementing a file picker widget for iced(we just put it in as an overlay)TODO: actually implement this1. Detect what type of file this is (and create a thumbnail if it's a video / image)2. Upload the file to matrix (and the thumbnail if there is one)3. Hardlink the source to our cache (or copy if FS doesn't support)- this is so that even if the user deletes the file it will be in our cache- (and we won't need to download it again)4. Create `MessageEventContent::Image(ImageMessageEventContent {...});` for each file- set `body` field to whatever is in `self.message`?,- use the MXC URL(s) we got when we uploaded our file(s)5. Send the message(s)!*/let file_select = tokio::task::spawn_blocking(|| -> Result<Vec<PathBuf>, nfd2::error::NFDError> {let paths = match nfd2::dialog_multiple().open()? {
Message::SendMessageComposer(room_id) => {if !self.message.is_empty() {let content =MessageEventContent::text_plain(self.message.drain(..).collect::<String>());scroll_to_bottom(self, room_id.clone());self.event_history_state.scroll_to_bottom();return Command::perform(async move { (content, room_id) },|(content, room_id)| {super::Message::MainScreen(Message::SendMessage {content: vec![content],room_id,})},);}}Message::SendFile(room_id) => {let file_select_task =tokio::task::spawn_blocking(|| -> Result<Vec<PathBuf>, ClientError> {let paths = match nfd2::dialog_multiple().open().map_err(|e| ClientError::Custom(e.to_string()))?{
},);
});let inner = self.client.inner();return Command::perform(async move {let paths = file_select_task.await.map_err(|e| ClientError::Custom(e.to_string()))??;let mut content_urls_to_send = Vec::with_capacity(paths.len());for path in paths {match tokio::fs::read(&path).await {Ok(data) => {let file_mimetype = media::infer_mimetype(&data);let filesize = data.len();let filename = media::get_filename(&path);// TODO: implement video thumbnailinglet (thumbnail, image_info) = if let ContentType::Image =ContentType::new(&file_mimetype){if let Ok(image) = image::load_from_memory(&data) {let image_width = image.width();let image_height = image.height();let image_dimensions =Some((image_height, image_width));let thumbnail_scale = ((1000 * 1000) / filesize) as u32;if thumbnail_scale <= 1 {let new_width = image_width * thumbnail_scale;let new_height = image_height * thumbnail_scale;let thumbnail =image.thumbnail(new_width, new_height);let thumbnail_height = thumbnail.height();let thumbnail_width = thumbnail.width();let thumbnail_raw = thumbnail.to_bytes();let thumbnail_size = thumbnail_raw.len();let send_result = Client::send_content(inner.clone(),thumbnail_raw,Some(file_mimetype.clone()),Some(format!("thumbnail_{}", filename)),).await;match send_result {Ok(thumbnail_url) => (Some((thumbnail_url,thumbnail_size,thumbnail_height,thumbnail_width,)),image_dimensions,),Err(err) => {log::error!("An error occured while uploading a thumbnail: {}", err);(None, image_dimensions)}}} else {(None, image_dimensions)}} else {(None, None)}} else {(None, None)};let send_result = Client::send_content(inner.clone(),data,Some(file_mimetype.clone()),Some(filename.clone()),).await;
// placeholderreturn Command::perform(file_select, |result| {match result {Ok(file_picker_result) => {if let Ok(paths) = file_picker_result {println!("User selected paths: {:?}", paths);
match send_result {Ok(content_url) => {if let Err(err) = tokio::fs::create_dir_all(media::make_content_folder(&content_url),).await{log::warn!("An IO error occured while trying to create a folder to hard link a file you tried to upload: {}", err);}if let Err(err) = tokio::fs::hard_link(&path,media::make_content_path(&content_url),).await{log::warn!("An IO error occured while hard linking a file you tried to upload (this may result in a duplication of the file): {}", err);}content_urls_to_send.push((content_url,filename,file_mimetype,filesize,thumbnail,image_info,));}Err(err) => {log::error!("An error occured while trying to upload a file: {}",err);}}}Err(err) => {log::error!("An IO error occured while trying to upload a file: {}",err);}
Err(err) => {log::error!("Error occured while processing file picker task result: {}",err);
Ok((content_urls_to_send, room_id))},|result| match result {Ok((content_urls_to_send, room_id)) => {super::Message::MainScreen(Message::SendMessage {content: content_urls_to_send.into_iter().map(|(url,filename,file_mimetype,filesize,thumbnail,image_dimensions,)| {let (thumbnail_url, thumbnail_info) =if let Some((url, size, h, w)) = thumbnail {(Some(url.to_string()),Some(Box::new(ThumbnailInfo {height: Some(ruma::UInt::from(h)),width: Some(ruma::UInt::from(w)),mimetype: Some(file_mimetype.clone()),size: ruma::UInt::new(size as u64),})),)} else {(None, None)};println!("thumbnail_url: {:?}", thumbnail_url);let body = filename;let mimetype = Some(file_mimetype.clone());let url = Some(url.to_string());match ContentType::new(&file_mimetype) {ContentType::Image => MessageEventContent::Image(ImageMessageEventContent {body,info: Some(Box::new(ImageInfo {mimetype,height: image_dimensions.map(|(h, _)| ruma::UInt::from(h)),width: image_dimensions.map(|(_, w)| ruma::UInt::from(w)),size: ruma::UInt::new(filesize as u64),thumbnail_info,thumbnail_url,thumbnail_file: None,})),url,file: None,},),ContentType::Audio => MessageEventContent::Audio(AudioMessageEventContent {body,info: Some(Box::new(AudioInfo {duration: None,mimetype,size: ruma::UInt::new(filesize as u64),})),url,file: None,},),ContentType::Video => MessageEventContent::Video(VideoMessageEventContent {body,info: Some(Box::new(VideoInfo {mimetype,height: None,width: None,duration: None,size: ruma::UInt::new(filesize as u64),thumbnail_info,thumbnail_url,thumbnail_file: None,})),url,file: None,},),ContentType::Other => MessageEventContent::File(FileMessageEventContent {body: body.clone(),filename: Some(body),info: Some(Box::new(FileInfo {mimetype,size: ruma::UInt::new(filesize as u64),thumbnail_info,thumbnail_url,thumbnail_file: None,})),url,file: None,},),}},).collect(),room_id,})
Message::SendMessage => {if !self.message.is_empty() {let content =MessageEventContent::text_plain(self.message.drain(..).collect::<String>());if let Some(Some((inner, room_id))) = self.current_room_id.clone().map(|id| {if self.client.has_room(&id) {Some((self.client.inner(), id))} else {None}}) {scroll_to_bottom(self, room_id.clone());self.prev_scroll_perc = 1.0;self.event_history_state.scroll_to_bottom();
Message::SendMessage { content, room_id } => {let mut commands = Vec::with_capacity(content.len());for content in content {if self.client.has_room(&room_id) {let inner = self.client.inner();
return Command::perform(async move {(Client::send_message(inner,content,room_id.clone(),
commands.push(Command::perform({let room_id = room_id.clone();async move {(Client::send_message(inner,content,room_id.clone(),transaction_id,).await,
return Command::batch(download_urls.into_iter().map(|url| make_download_content_com(self.client.inner(), url)).chain(read_urls.into_iter().map(|url| make_read_thumbnail_com(url)),),);
return Command::batch(thumbnail_urls.into_iter().map(|(is_in_cache, thumbnail_url)| {if is_in_cache {make_read_thumbnail_com(thumbnail_url)} else {make_download_content_com(self.client.inner(), thumbnail_url)}},));
return Command::batch(download_urls.into_iter().map(|url| make_download_content_com(self.client.inner(), url)).chain(read_urls.into_iter().map(|url| make_read_thumbnail_com(url)),),);
return Command::batch(thumbnail_urls.into_iter().map(|(is_in_cache, thumbnail_url)| {if is_in_cache {make_read_thumbnail_com(thumbnail_url)} else {make_download_content_com(self.client.inner(), thumbnail_url)}},));