[fractal] Separate the use of URLs and local paths
- From: Daniel Garcia Moreno <danigm src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal] Separate the use of URLs and local paths
- Date: Wed, 22 Jul 2020 07:19:38 +0000 (UTC)
commit 66a642530feadd44cf91552544acabb6532d87fd
Author: Alejandro Domínguez <adomu net-c com>
Date: Wed Jul 15 19:29:11 2020 +0200
Separate the use of URLs and local paths
Cargo.lock | 13 +-
fractal-gtk/Cargo.toml | 5 +
fractal-gtk/src/actions/message.rs | 142 +++++++++++----------
fractal-gtk/src/appop/member.rs | 18 +--
fractal-gtk/src/appop/message.rs | 180 +++++++++++++-------------
fractal-gtk/src/appop/mod.rs | 4 +-
fractal-gtk/src/appop/notify.rs | 5 +-
fractal-gtk/src/appop/room.rs | 2 +-
fractal-gtk/src/appop/user.rs | 5 +-
fractal-gtk/src/backend/media.rs | 17 +--
fractal-gtk/src/backend/mod.rs | 68 +++++-----
fractal-gtk/src/backend/room.rs | 12 +-
fractal-gtk/src/backend/user.rs | 26 ++--
fractal-gtk/src/cache/mod.rs | 7 +-
fractal-gtk/src/cache/state.rs | 8 +-
fractal-gtk/src/error.rs | 5 -
fractal-gtk/src/model/fileinfo.rs | 3 +-
fractal-gtk/src/model/member.rs | 8 +-
fractal-gtk/src/model/message.rs | 46 +++----
fractal-gtk/src/model/room.rs | 26 ++--
fractal-gtk/src/uitypes.rs | 11 +-
fractal-gtk/src/util.rs | 10 +-
fractal-gtk/src/widgets/avatar.rs | 3 +-
fractal-gtk/src/widgets/image.rs | 107 ++++++++--------
fractal-gtk/src/widgets/inline_player.rs | 62 ++++-----
fractal-gtk/src/widgets/media_viewer.rs | 27 ++--
fractal-gtk/src/widgets/message.rs | 210 ++++++++++++++++---------------
fractal-gtk/src/widgets/room_history.rs | 26 ++--
fractal-gtk/src/widgets/roomlist.rs | 4 +-
fractal-gtk/src/widgets/roomrow.rs | 3 +-
30 files changed, 555 insertions(+), 508 deletions(-)
---
diff --git a/Cargo.lock b/Cargo.lock
index 2eab7ae8..f9057630 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -495,6 +495,14 @@ version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
+[[package]]
+name = "either"
+version = "1.5.3"
+source =
"git+https://github.com/aledomu/either.git?rev=111c9bd4905c6b51d3d904ba42625f2b32246b59#111c9bd4905c6b51d3d904ba42625f2b32246b59"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "either"
version = "1.5.3"
@@ -587,6 +595,7 @@ dependencies = [
"comrak",
"directories",
"dirs",
+ "either 1.5.3 (git+https://github.com/aledomu/either.git?rev=111c9bd4905c6b51d3d904ba42625f2b32246b59)",
"failure",
"fractal-matrix-api",
"fragile",
@@ -1377,7 +1386,7 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
dependencies = [
- "either",
+ "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -2727,7 +2736,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1997788a0e25e09300e44680ba1ef9d44d6f634a883641f80109e8b59c928daf"
dependencies = [
"bytes 0.4.12",
- "either",
+ "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"futures",
"thiserror",
"tokio",
diff --git a/fractal-gtk/Cargo.toml b/fractal-gtk/Cargo.toml
index 2ca72e5f..f3c09e8c 100644
--- a/fractal-gtk/Cargo.toml
+++ b/fractal-gtk/Cargo.toml
@@ -75,3 +75,8 @@ features = ["derive"]
[dependencies.gio]
version = "0.8.1"
features = ["v2_56"]
+
+[dependencies.either]
+git = "https://github.com/aledomu/either.git"
+rev = "111c9bd4905c6b51d3d904ba42625f2b32246b59"
+features = ["serde_untagged"]
diff --git a/fractal-gtk/src/actions/message.rs b/fractal-gtk/src/actions/message.rs
index 17f6ae66..5abf33e8 100644
--- a/fractal-gtk/src/actions/message.rs
+++ b/fractal-gtk/src/actions/message.rs
@@ -114,21 +114,21 @@ pub fn new(
open_with.connect_activate(clone!(@strong server_url => move |_, data| {
if let Some(m) = get_message(data) {
- let url = m.url.unwrap_or_default();
- let server_url = server_url.clone();
- thread::spawn(move || {
- match dw_media(server_url, &url, ContentType::Download, None) {
- Ok(fname) => {
- Command::new("xdg-open")
- .arg(&fname)
- .spawn()
- .expect("failed to execute process");
- }
- Err(err) => {
- err.handle_error()
+ if let Some(url) = m.url {
+ thread::spawn(clone!(@strong server_url => move || {
+ match dw_media(server_url, &url, ContentType::Download, None) {
+ Ok(fname) => {
+ Command::new("xdg-open")
+ .arg(&fname)
+ .spawn()
+ .expect("failed to execute process");
+ }
+ Err(err) => {
+ err.handle_error()
+ }
}
- }
- });
+ }));
+ }
}
}));
@@ -138,77 +138,83 @@ pub fn new(
@weak parent as window
=> move |_, data| {
if let Some(m) = get_message(data) {
- let name = m.body;
- let url = m.url.unwrap_or_default();
+ if let Some(url) = m.url {
+ let name = m.body;
+
+ let (tx, rx): (
+ Sender<media::MediaResult>,
+ Receiver<media::MediaResult>,
+ ) = channel();
- let (tx, rx): (Sender<media::MediaResult>, Receiver<media::MediaResult>) = channel();
- media::get_media_async(thread_pool.clone(), server_url.clone(), url, tx);
+ media::get_media_async(thread_pool.clone(), server_url.clone(), url, tx);
- gtk::timeout_add(
- 50,
- clone!(
- @strong name,
- @weak window
- => @default-return Continue(true), move || match rx.try_recv() {
- Err(TryRecvError::Empty) => Continue(true),
- Err(TryRecvError::Disconnected) => {
- let msg = i18n("Could not download the file");
- ErrorDialog::new(false, &msg);
+ gtk::timeout_add(
+ 50,
+ clone!(
+ @strong name,
+ @weak window
+ => @default-return Continue(true), move || match rx.try_recv() {
+ Err(TryRecvError::Empty) => Continue(true),
+ Err(TryRecvError::Disconnected) => {
+ let msg = i18n("Could not download the file");
+ ErrorDialog::new(false, &msg);
- Continue(true)
- },
- Ok(Ok(fname)) => {
- if let Some(path) = save(&window, &name, &[]) {
- // TODO use glib to copy file
- if fs::copy(fname, path).is_err() {
- ErrorDialog::new(false, &i18n("Couldn’t save file"));
+ Continue(true)
+ },
+ Ok(Ok(fname)) => {
+ if let Some(path) = save(&window, &name, &[]) {
+ // TODO use glib to copy file
+ if fs::copy(fname, path).is_err() {
+ ErrorDialog::new(false, &i18n("Couldn’t save file"));
+ }
}
+ Continue(false)
+ }
+ Ok(Err(err)) => {
+ error!("Media path could not be found due to error: {:?}", err);
+ Continue(false)
}
- Continue(false)
- }
- Ok(Err(err)) => {
- error!("Media path could not be found due to error: {:?}", err);
- Continue(false)
}
- }),
- );
+ ),
+ );
+ }
}
}));
copy_image.connect_activate(clone!(@strong server_url => move |_, data| {
if let Some(m) = get_message(data) {
- let url = m.url.unwrap_or_default();
+ if let Some(url) = m.url {
+ let (tx, rx): (
+ Sender<media::MediaResult>,
+ Receiver<media::MediaResult>,
+ ) = channel();
- let (tx, rx): (
- Sender<media::MediaResult>,
- Receiver<media::MediaResult>,
- ) = channel();
+ media::get_media_async(thread_pool.clone(), server_url.clone(), url, tx);
- media::get_media_async(thread_pool.clone(), server_url.clone(), url, tx);
+ gtk::timeout_add(50, move || match rx.try_recv() {
+ Err(TryRecvError::Empty) => Continue(true),
+ Err(TryRecvError::Disconnected) => {
+ let msg = i18n("Could not download the file");
+ ErrorDialog::new(false, &msg);
- gtk::timeout_add(50, move || match rx.try_recv() {
- Err(TryRecvError::Empty) => Continue(true),
- Err(TryRecvError::Disconnected) => {
- let msg = i18n("Could not download the file");
- ErrorDialog::new(false, &msg);
+ Continue(true)
+ }
+ Ok(Ok(fname)) => {
+ if let Ok(pixbuf) = gdk_pixbuf::Pixbuf::new_from_file(fname) {
+ let atom = gdk::Atom::intern("CLIPBOARD");
+ let clipboard = gtk::Clipboard::get(&atom);
- Continue(true)
- }
- Ok(Ok(fname)) => {
- if let Ok(pixbuf) = gdk_pixbuf::Pixbuf::new_from_file(fname) {
- let atom = gdk::Atom::intern("CLIPBOARD");
- let clipboard = gtk::Clipboard::get(&atom);
+ clipboard.set_image(&pixbuf);
+ }
- clipboard.set_image(&pixbuf);
+ Continue(false)
}
-
- Continue(false)
- }
- Ok(Err(err)) => {
- error!("Image path could not be found due to error: {:?}", err);
- Continue(false)
- }
- });
+ Ok(Err(err)) => {
+ error!("Image path could not be found due to error: {:?}", err);
+ Continue(false)
+ }
+ });
+ }
}
}));
diff --git a/fractal-gtk/src/appop/member.rs b/fractal-gtk/src/appop/member.rs
index a8f4926a..e929bc0f 100644
--- a/fractal-gtk/src/appop/member.rs
+++ b/fractal-gtk/src/appop/member.rs
@@ -1,5 +1,9 @@
use crate::backend::{user, HandleError};
-use fractal_api::identifiers::{RoomId, UserId};
+use either::Either;
+use fractal_api::{
+ identifiers::{RoomId, UserId},
+ url::Url,
+};
use glib::clone;
use gtk::prelude::*;
@@ -59,12 +63,12 @@ impl AppOp {
}
Some("join") => {
let m = Member {
- avatar: Some(String::from(
- ev.content["avatar_url"].as_str().unwrap_or_default(),
- )),
- alias: Some(String::from(
- ev.content["displayname"].as_str().unwrap_or_default(),
- )),
+ avatar: ev.content["avatar_url"]
+ .as_str()
+ .map(Url::parse)
+ .and_then(Result::ok)
+ .map(Either::Left),
+ alias: ev.content["displayname"].as_str().map(String::from),
uid: sender,
};
if let Some(r) = self.rooms.get_mut(&ev.room.clone()) {
diff --git a/fractal-gtk/src/appop/message.rs b/fractal-gtk/src/appop/message.rs
index a1d1365e..bc22a22f 100644
--- a/fractal-gtk/src/appop/message.rs
+++ b/fractal-gtk/src/appop/message.rs
@@ -16,7 +16,7 @@ use serde_json::json;
use serde_json::Value as JsonValue;
use std::env::temp_dir;
use std::fs;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
use std::thread;
use crate::appop::room::Force;
@@ -298,46 +298,49 @@ impl AppOp {
pub fn attach_message(&mut self, path: PathBuf) {
if let Some(room) = self.active_room.clone() {
if let Some(sender) = self.login_data.as_ref().map(|ld| ld.uid.clone()) {
- if let Ok(info) = gio::File::new_for_path(&path).query_info(
- &gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
- gio::FileQueryInfoFlags::NONE,
- gio::NONE_CANCELLABLE,
- ) {
- // This should always return a type
- let mime = info
- .get_content_type()
- .expect("Could not parse content type from file");
- let mtype = match mime.as_ref() {
- m if m.starts_with("image") => "m.image",
- m if m.starts_with("audio") => "m.audio",
- "application/x-riff" => "m.audio",
- m if m.starts_with("video") => "m.video",
- "application/x-mpegURL" => "m.video",
- _ => "m.file",
- };
- let body = path
- .file_name()
- .and_then(|s| s.to_str())
- .unwrap_or_default();
- let path_string = path.to_str().unwrap_or_default();
-
- let mut m =
- Message::new(room, sender, body.to_string(), mtype.to_string(), None);
- let info = match mtype {
- "m.image" => get_image_media_info(path_string, mime.as_ref()),
- "m.audio" => get_audio_video_media_info(path_string, mime.as_ref()),
- "m.video" => get_audio_video_media_info(path_string, mime.as_ref()),
- "m.file" => get_file_media_info(path_string, mime.as_ref()),
- _ => None,
- };
-
- m.extra_content = info;
- m.url = Some(path_string.to_string());
-
- self.add_tmp_room_message(m);
- self.dequeue_message();
+ if let Ok(uri) = Url::from_file_path(&path) {
+ if let Ok(info) = gio::File::new_for_path(&path).query_info(
+ &gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+ gio::FileQueryInfoFlags::NONE,
+ gio::NONE_CANCELLABLE,
+ ) {
+ // This should always return a type
+ let mime = info
+ .get_content_type()
+ .expect("Could not parse content type from file");
+ let mtype = match mime.as_ref() {
+ m if m.starts_with("image") => "m.image",
+ m if m.starts_with("audio") => "m.audio",
+ "application/x-riff" => "m.audio",
+ m if m.starts_with("video") => "m.video",
+ "application/x-mpegURL" => "m.video",
+ _ => "m.file",
+ };
+ let body = path
+ .file_name()
+ .and_then(|s| s.to_str())
+ .map(Into::into)
+ .unwrap_or_default();
+
+ let mut m = Message::new(room, sender, body, mtype.to_string(), None);
+ let info = match mtype {
+ "m.image" => get_image_media_info(&path, mime.as_ref()),
+ "m.audio" => get_audio_video_media_info(&uri, mime.as_ref()),
+ "m.video" => get_audio_video_media_info(&uri, mime.as_ref()),
+ "m.file" => get_file_media_info(&path, mime.as_ref()),
+ _ => None,
+ };
+
+ m.extra_content = info;
+ m.local_path = Some(path);
+
+ self.add_tmp_room_message(m);
+ self.dequeue_message();
+ } else {
+ error!("Can't send message: Could not query info");
+ }
} else {
- error!("Can't send message: Could not query info");
+ error!("Can't send message: Path is not absolute")
}
} else {
error!("Can't send message: No user is logged in");
@@ -560,6 +563,7 @@ fn create_ui_message(
},
thumb: msg.thumb,
url: msg.url,
+ local_path: msg.local_path,
formatted_body: msg.formatted_body,
format: msg.format,
last_viewed,
@@ -572,9 +576,9 @@ fn create_ui_message(
/// This function opens the image, creates a thumbnail
/// and populates the info Json with the information it has
-fn get_image_media_info(file: &str, mimetype: &str) -> Option<JsonValue> {
- let (_, w, h) = Pixbuf::get_file_info(&file)?;
- let size = fs::metadata(&file).ok()?.len();
+fn get_image_media_info(file: &Path, mimetype: &str) -> Option<JsonValue> {
+ let (_, w, h) = Pixbuf::get_file_info(file)?;
+ let size = fs::metadata(file).ok()?.len();
// make thumbnail max 800x600
let thumb = Pixbuf::new_from_file_at_scale(&file, 800, 600, true).ok()?;
@@ -608,10 +612,10 @@ fn get_image_media_info(file: &str, mimetype: &str) -> Option<JsonValue> {
Some(info)
}
-fn get_audio_video_media_info(file: &str, mimetype: &str) -> Option<JsonValue> {
- let size = fs::metadata(file).ok()?.len();
+fn get_audio_video_media_info(uri: &Url, mimetype: &str) -> Option<JsonValue> {
+ let size = fs::metadata(uri.to_file_path().ok()?).ok()?.len();
- if let Some(duration) = widgets::inline_player::get_media_duration(file)
+ if let Some(duration) = widgets::inline_player::get_media_duration(uri)
.ok()
.and_then(|d| d.mseconds())
{
@@ -632,7 +636,7 @@ fn get_audio_video_media_info(file: &str, mimetype: &str) -> Option<JsonValue> {
}
}
-fn get_file_media_info(file: &str, mimetype: &str) -> Option<JsonValue> {
+fn get_file_media_info(file: &Path, mimetype: &str) -> Option<JsonValue> {
let size = fs::metadata(file).ok()?.len();
let info = json!({
@@ -645,59 +649,63 @@ fn get_file_media_info(file: &str, mimetype: &str) -> Option<JsonValue> {
Some(info)
}
-fn attach_file(baseu: Url, tk: AccessToken, mut msg: Message) {
- let fname = msg.url.clone().unwrap_or_default();
+struct NonMediaMsg;
+
+fn attach_file(baseu: Url, tk: AccessToken, mut msg: Message) -> Result<(), NonMediaMsg> {
let mut extra_content: Option<ExtraContent> = msg
- .clone()
.extra_content
+ .clone()
.and_then(|c| serde_json::from_value(c).ok());
- let thumb = extra_content
- .clone()
- .and_then(|c| c.info.thumbnail_url)
- .unwrap_or_default();
+ let thumb_url = extra_content.clone().and_then(|c| c.info.thumbnail_url);
- if fname.starts_with("mxc://") && thumb.starts_with("mxc://") {
- send_msg_and_manage(baseu, tk, msg);
+ match (msg.url.clone(), msg.local_path.as_ref(), thumb_url) {
+ (Some(url), _, Some(thumb)) if url.scheme() == "mxc" && thumb.scheme() == "mxc" => {
+ send_msg_and_manage(baseu, tk, msg);
- return;
- }
+ Ok(())
+ }
+ (_, Some(local_path), _) => {
+ if let Some(ref local_path_thumb) = msg.local_path_thumb {
+ match room::upload_file(baseu.clone(), tk.clone(), local_path_thumb) {
+ Ok(response) => {
+ let thumb_uri = response.content_uri;
+ msg.thumb = Some(thumb_uri.clone());
+ if let Some(ref mut xctx) = extra_content {
+ xctx.info.thumbnail_url = Some(thumb_uri);
+ }
+ msg.extra_content = serde_json::to_value(&extra_content).ok();
+ }
+ Err(err) => {
+ err.handle_error();
+ }
+ }
- if !thumb.is_empty() {
- match room::upload_file(baseu.clone(), tk.clone(), &thumb) {
- Ok(response) => {
- let thumb_uri = response.content_uri.to_string();
- msg.thumb = Some(thumb_uri.clone());
- if let Some(ref mut xctx) = extra_content {
- xctx.info.thumbnail_url = Some(thumb_uri);
+ if let Err(_e) = std::fs::remove_file(local_path_thumb) {
+ error!("Can't remove thumbnail: {}", local_path_thumb.display());
}
- msg.extra_content = serde_json::to_value(&extra_content).ok();
}
- Err(err) => {
- err.handle_error();
- }
- }
- if let Err(_e) = std::fs::remove_file(&thumb) {
- error!("Can't remove thumbnail: {}", thumb);
- }
- }
+ let query = room::upload_file(baseu.clone(), tk.clone(), local_path).map(|response| {
+ msg.url = Some(response.content_uri);
+ thread::spawn(clone!(@strong msg => move || send_msg_and_manage(baseu, tk, msg)));
- let query = room::upload_file(baseu.clone(), tk.clone(), &fname).map(|response| {
- msg.url = Some(response.content_uri.to_string());
- thread::spawn(clone!(@strong msg => move || send_msg_and_manage(baseu, tk, msg)));
+ msg
+ });
- msg
- });
+ match query {
+ Ok(msg) => {
+ APPOP!(attached_file, (msg));
+ }
+ Err(err) => {
+ err.handle_error();
+ }
+ };
- match query {
- Ok(msg) => {
- APPOP!(attached_file, (msg));
- }
- Err(err) => {
- err.handle_error();
+ Ok(())
}
- };
+ _ => Err(NonMediaMsg),
+ }
}
fn send_msg_and_manage(baseu: Url, tk: AccessToken, msg: Message) {
diff --git a/fractal-gtk/src/appop/mod.rs b/fractal-gtk/src/appop/mod.rs
index 18979a0e..cb314d35 100644
--- a/fractal-gtk/src/appop/mod.rs
+++ b/fractal-gtk/src/appop/mod.rs
@@ -48,6 +48,8 @@ mod user;
use self::member::SearchType;
use self::message::TmpMsg;
+pub type UserInfoCache = Arc<Mutex<CacheMap<UserId, (String, PathBuf)>>>;
+
#[derive(Clone, Debug, PartialEq)]
pub enum RoomSearchPagination {
Initial,
@@ -116,7 +118,7 @@ pub struct AppOp {
pub leaflet: libhandy::Leaflet,
pub thread_pool: ThreadPool,
- pub user_info_cache: Arc<Mutex<CacheMap<UserId, (String, String)>>>,
+ pub user_info_cache: UserInfoCache,
}
impl PasswordStorage for AppOp {}
diff --git a/fractal-gtk/src/appop/notify.rs b/fractal-gtk/src/appop/notify.rs
index c6de2de5..eed7b68b 100644
--- a/fractal-gtk/src/appop/notify.rs
+++ b/fractal-gtk/src/appop/notify.rs
@@ -7,6 +7,7 @@ use glib::clone;
use glib::source::Continue;
use gtk::prelude::*;
use log::info;
+use std::path::Path;
use std::sync::mpsc::channel;
use std::sync::mpsc::TryRecvError;
use std::sync::mpsc::{Receiver, Sender};
@@ -107,11 +108,11 @@ fn dirty_truncate(s: &str, num_chars: usize) -> &str {
}
}
-fn create_notification(room_id: &str, title: &str, body: &str, avatar: &str) -> Notification {
+fn create_notification(room_id: &str, title: &str, body: &str, avatar: &Path) -> Notification {
let notification = Notification::new(title);
notification.set_body(Some(body));
notification.set_priority(gio::NotificationPriority::High);
- info!("Creating notification with avatar: {}", avatar);
+ info!("Creating notification with avatar: {}", avatar.display());
let file = gio::File::new_for_path(avatar);
let _ = file.load_bytes(gio::NONE_CANCELLABLE).map(|(b, _)| {
let avatar = gio::BytesIcon::new(&b);
diff --git a/fractal-gtk/src/appop/room.rs b/fractal-gtk/src/appop/room.rs
index 1231d20c..fdd7365a 100644
--- a/fractal-gtk/src/appop/room.rs
+++ b/fractal-gtk/src/appop/room.rs
@@ -485,7 +485,7 @@ impl AppOp {
}
}
}
- r.avatar = avatar.map(|s| s.into_string());
+ r.avatar = avatar;
self.roomlist
.set_room_avatar(room_id.clone(), r.avatar.clone());
}
diff --git a/fractal-gtk/src/appop/user.rs b/fractal-gtk/src/appop/user.rs
index ab37b1f4..a6c08d74 100644
--- a/fractal-gtk/src/appop/user.rs
+++ b/fractal-gtk/src/appop/user.rs
@@ -3,6 +3,7 @@ use gtk::prelude::*;
use crate::backend::{user, HandleError};
use glib::clone;
+use std::path::PathBuf;
use std::thread;
use crate::app::App;
@@ -145,10 +146,10 @@ impl AppOp {
});
}
- pub fn set_avatar(&mut self, path: String) {
+ pub fn set_avatar(&mut self, path: PathBuf) {
let login_data = unwrap_or_unit_return!(self.login_data.clone());
self.set_login_data(LoginData {
- avatar: Some(path.into()),
+ avatar: Some(path),
..login_data
});
}
diff --git a/fractal-gtk/src/backend/media.rs b/fractal-gtk/src/backend/media.rs
index a88fe1e7..166c7dd8 100644
--- a/fractal-gtk/src/backend/media.rs
+++ b/fractal-gtk/src/backend/media.rs
@@ -3,6 +3,7 @@ use crate::globals;
use fractal_api::identifiers::{Error as IdError, EventId, RoomId};
use fractal_api::reqwest::Error as ReqwestError;
use fractal_api::url::Url;
+use std::path::PathBuf;
use std::sync::mpsc::Sender;
use crate::backend::HTTP_CLIENT;
@@ -18,27 +19,17 @@ use fractal_api::r0::message::get_message_events::Response as GetMessagesEventsR
use super::{dw_media, get_prev_batch_from, ContentType, ThreadPool};
-pub type MediaResult = Result<String, MediaError>;
+pub type MediaResult = Result<PathBuf, MediaError>;
pub type MediaList = (Vec<Message>, String);
-pub fn get_thumb_async(
- thread_pool: ThreadPool,
- baseu: Url,
- media: String,
- tx: Sender<MediaResult>,
-) {
+pub fn get_thumb_async(thread_pool: ThreadPool, baseu: Url, media: Url, tx: Sender<MediaResult>) {
thread_pool.run(move || {
let fname = dw_media(baseu, &media, ContentType::default_thumbnail(), None);
tx.send(fname).expect_log("Connection closed");
});
}
-pub fn get_media_async(
- thread_pool: ThreadPool,
- baseu: Url,
- media: String,
- tx: Sender<MediaResult>,
-) {
+pub fn get_media_async(thread_pool: ThreadPool, baseu: Url, media: Url, tx: Sender<MediaResult>) {
thread_pool.run(move || {
let fname = dw_media(baseu, &media, ContentType::Download, None);
tx.send(fname).expect_log("Connection closed");
diff --git a/fractal-gtk/src/backend/mod.rs b/fractal-gtk/src/backend/mod.rs
index af2f8499..cf45abcc 100644
--- a/fractal-gtk/src/backend/mod.rs
+++ b/fractal-gtk/src/backend/mod.rs
@@ -1,19 +1,17 @@
use fractal_api::identifiers::{EventId, RoomId};
use fractal_api::reqwest::Error as ReqwestError;
-use fractal_api::url::{ParseError as UrlError, Url};
+use fractal_api::url::Url;
use lazy_static::lazy_static;
use log::error;
use regex::Regex;
use std::fmt::Debug;
use std::fs::write;
use std::io::{Error as IoError, Read};
-use std::path::Path;
+use std::path::PathBuf;
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
-use std::time::SystemTimeError;
use crate::client::Client;
-use crate::error::Error;
use crate::util::cache_dir_path;
use fractal_api::r0::context::get_context::request as get_context;
use fractal_api::r0::context::get_context::Parameters as GetContextParameters;
@@ -118,11 +116,9 @@ pub fn get_prev_batch_from(
#[derive(Debug)]
pub enum MediaError {
- InvalidMxcUrl(Option<UrlError>),
- InvalidDownloadPath(Error),
- Reqwest(ReqwestError),
+ MalformedMxcUrl,
Io(IoError),
- SystemTime(SystemTimeError),
+ Reqwest(ReqwestError),
}
impl From<ReqwestError> for MediaError {
@@ -137,35 +133,25 @@ impl From<IoError> for MediaError {
}
}
-impl From<SystemTimeError> for MediaError {
- fn from(err: SystemTimeError) -> Self {
- Self::SystemTime(err)
- }
-}
-
impl HandleError for MediaError {}
pub fn dw_media(
base: Url,
- mxc: &str,
+ mxc: &Url,
media_type: ContentType,
- dest: Option<String>,
-) -> Result<String, MediaError> {
- let mxc_url = Url::parse(mxc).map_err(|url_err| MediaError::InvalidMxcUrl(Some(url_err)))?;
-
- if mxc_url.scheme() != "mxc" {
- return Err(MediaError::InvalidMxcUrl(None));
+ dest: Option<PathBuf>,
+) -> Result<PathBuf, MediaError> {
+ if mxc.scheme() != "mxc" {
+ return Err(MediaError::MalformedMxcUrl);
}
- let server = mxc_url
- .host()
- .ok_or(MediaError::InvalidMxcUrl(None))?
- .to_owned();
- let media_id = mxc_url
+ let server = mxc.host().ok_or(MediaError::MalformedMxcUrl)?.to_owned();
+
+ let media_id = mxc
.path_segments()
.and_then(|mut ps| ps.next())
.filter(|s| !s.is_empty())
- .ok_or(MediaError::InvalidMxcUrl(None))?;
+ .ok_or(MediaError::MalformedMxcUrl)?;
let request = if let ContentType::Thumbnail(width, height) = media_type {
let params = GetContentThumbnailParameters {
@@ -180,19 +166,25 @@ pub fn dw_media(
get_content(base, ¶ms, &server, &media_id)
}?;
- let fname = match dest {
- None if media_type.is_thumbnail() => cache_dir_path(Some("thumbs"), &media_id),
- None => cache_dir_path(Some("medias"), &media_id),
- Some(ref d) => Ok(d.clone()),
- }
- .map_err(MediaError::InvalidDownloadPath)?;
-
- let fpath = Path::new(&fname);
+ let default_fname = || {
+ let dir = if media_type.is_thumbnail() {
+ "thumbs"
+ } else {
+ "medias"
+ };
+ cache_dir_path(Some(dir), &media_id)
+ };
+ let fname = dest.clone().map_or_else(default_fname, Ok)?;
// If the file is already cached and recent enough, don't download it
- if fpath.is_file()
- && (dest.is_none() || fpath.metadata()?.modified()?.elapsed()?.as_secs() < 60)
- {
+ let is_fname_recent = fname
+ .metadata()
+ .ok()
+ .and_then(|md| md.modified().ok())
+ .and_then(|modf| modf.elapsed().ok())
+ .map_or(false, |dur| dur.as_secs() < 60);
+
+ if fname.is_file() && (dest.is_none() || is_fname_recent) {
Ok(fname)
} else {
HTTP_CLIENT
diff --git a/fractal-gtk/src/backend/room.rs b/fractal-gtk/src/backend/room.rs
index 5ff84740..aec0c336 100644
--- a/fractal-gtk/src/backend/room.rs
+++ b/fractal-gtk/src/backend/room.rs
@@ -6,6 +6,7 @@ use fractal_api::reqwest::Error as ReqwestError;
use fractal_api::url::Url;
use std::fs;
use std::io::Error as IoError;
+use std::path::{Path, PathBuf};
use std::collections::HashMap;
use std::convert::TryFrom;
@@ -139,12 +140,7 @@ pub fn get_room_avatar(
let avatar = response["url"].as_str().and_then(|s| Url::parse(s).ok());
let dest = cache_dir_path(None, &room_id.to_string()).ok();
if let Some(ref avatar) = avatar {
- let _ = dw_media(
- base,
- avatar.as_str(),
- ContentType::default_thumbnail(),
- dest,
- );
+ let _ = dw_media(base, avatar, ContentType::default_thumbnail(), dest);
}
Ok((room_id, avatar))
@@ -545,7 +541,7 @@ pub fn set_room_avatar(
base: Url,
access_token: AccessToken,
room_id: RoomId,
- avatar: String,
+ avatar: PathBuf,
) -> Result<(), SetRoomAvatarError> {
let params = CreateStateEventsForKeyParameters {
access_token: access_token.clone(),
@@ -592,7 +588,7 @@ impl HandleError for AttachedFileError {
pub fn upload_file(
base: Url,
access_token: AccessToken,
- fname: &str,
+ fname: &Path,
) -> Result<CreateContentResponse, AttachedFileError> {
let params_upload = CreateContentParameters {
access_token,
diff --git a/fractal-gtk/src/backend/user.rs b/fractal-gtk/src/backend/user.rs
index 32a59799..0e3efed9 100644
--- a/fractal-gtk/src/backend/user.rs
+++ b/fractal-gtk/src/backend/user.rs
@@ -6,16 +6,15 @@ use std::io::Error as IoError;
use super::MediaError;
use crate::actions::global::activate_action;
+use crate::appop::UserInfoCache;
use crate::backend::ThreadPool;
use crate::backend::HTTP_CLIENT;
-use crate::cache::CacheMap;
use crate::util::cache_dir_path;
use crate::util::ResultExpectLog;
use log::error;
use std::convert::TryInto;
use std::path::PathBuf;
use std::sync::mpsc::Sender;
-use std::sync::{Arc, Mutex};
use std::thread;
use crate::types::Member;
@@ -78,7 +77,7 @@ use crate::app::App;
use crate::i18n::i18n;
use crate::APPOP;
-pub type UserInfo = (String, String);
+pub type UserInfo = (String, PathBuf);
#[derive(Debug)]
pub struct NameError(ReqwestError);
@@ -564,7 +563,7 @@ pub fn set_user_avatar(
pub fn get_user_info_async(
thread_pool: ThreadPool,
- user_info_cache: Arc<Mutex<CacheMap<UserId, (String, String)>>>,
+ user_info_cache: UserInfoCache,
baseu: Url,
uid: UserId,
tx: Sender<UserInfo>,
@@ -628,12 +627,18 @@ impl From<ReqwestError> for GetUserAvatarError {
}
}
+impl From<MediaError> for GetUserAvatarError {
+ fn from(err: MediaError) -> Self {
+ Self::Download(err)
+ }
+}
+
impl HandleError for GetUserAvatarError {}
pub fn get_user_avatar(
base: Url,
user_id: &UserId,
-) -> Result<(String, String), GetUserAvatarError> {
+) -> Result<(String, PathBuf), GetUserAvatarError> {
let request = get_profile(base.clone(), user_id)?;
let response: GetProfileResponse = HTTP_CLIENT.get_client().execute(request)?.json()?;
@@ -644,15 +649,10 @@ pub fn get_user_avatar(
let img = response
.avatar_url
+ .as_ref()
.map(|url| {
- let dest = cache_dir_path(None, &user_id.to_string())
- .map_err(MediaError::InvalidDownloadPath)?;
- dw_media(
- base,
- url.as_str(),
- ContentType::default_thumbnail(),
- Some(dest),
- )
+ let dest = cache_dir_path(None, &user_id.to_string())?;
+ dw_media(base, url, ContentType::default_thumbnail(), Some(dest))
})
.unwrap_or_else(|| Ok(Default::default()))
.map_err(GetUserAvatarError::Download)?;
diff --git a/fractal-gtk/src/cache/mod.rs b/fractal-gtk/src/cache/mod.rs
index f17ef22c..980f0e82 100644
--- a/fractal-gtk/src/cache/mod.rs
+++ b/fractal-gtk/src/cache/mod.rs
@@ -1,3 +1,4 @@
+use crate::appop::UserInfoCache;
use crate::backend::user;
use crate::backend::ThreadPool;
use crate::util::ResultExpectLog;
@@ -14,6 +15,7 @@ use failure::Error;
use fractal_api::identifiers::UserId;
use std::collections::HashMap;
use std::hash::Hash;
+use std::path::PathBuf;
use std::time::{Duration, Instant};
use crate::globals;
@@ -23,7 +25,6 @@ use std::sync::mpsc::channel;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::Sender;
use std::sync::mpsc::TryRecvError;
-use std::sync::{Arc, Mutex};
use crate::widgets::AvatarData;
use std::cell::RefCell;
@@ -145,12 +146,12 @@ pub fn load() -> Result<CacheData, Error> {
/// this downloads a avatar and stores it in the cache folder
pub fn download_to_cache(
thread_pool: ThreadPool,
- user_info_cache: Arc<Mutex<CacheMap<UserId, (String, String)>>>,
+ user_info_cache: UserInfoCache,
server_url: Url,
uid: UserId,
data: Rc<RefCell<AvatarData>>,
) {
- let (tx, rx) = channel::<(String, String)>();
+ let (tx, rx) = channel::<(String, PathBuf)>();
user::get_user_info_async(thread_pool, user_info_cache, server_url, uid, tx);
gtk::timeout_add(50, move || match rx.try_recv() {
diff --git a/fractal-gtk/src/cache/state.rs b/fractal-gtk/src/cache/state.rs
index f86dc1fe..699e133b 100644
--- a/fractal-gtk/src/cache/state.rs
+++ b/fractal-gtk/src/cache/state.rs
@@ -110,8 +110,12 @@ impl FCache {
fn get_store(&self) -> MutexGuard<Option<Cache>> {
let mut guard = self.cache.lock().unwrap();
if guard.is_none() {
- let db: String =
- cache_dir_path(None, "cache.mdl").expect("Fatal error: Can't start the cache");
+ let maybe_db_path = cache_dir_path(None, "cache.mdl").ok();
+ let db: String = maybe_db_path
+ .and_then(|p| p.to_str().map(Into::into))
+ .expect("Fatal error: Can't start the cache");
+ // TODO: Replace Cache with another library. Not expecting a proper
+ // Path type for the path of the DB is bonkers.
let mdl_cache = Cache::new(&db).expect("Fatal error: Can't start the cache");
*guard = Some(mdl_cache);
}
diff --git a/fractal-gtk/src/error.rs b/fractal-gtk/src/error.rs
index 6c87ffac..e9034489 100644
--- a/fractal-gtk/src/error.rs
+++ b/fractal-gtk/src/error.rs
@@ -1,5 +1,4 @@
use serde::Deserialize;
-use std::io;
#[derive(Clone, Debug, Deserialize)]
pub struct StandardErrorResponse {
@@ -23,7 +22,6 @@ macro_rules! derror {
#[derive(Debug)]
pub enum Error {
BackendError,
- CacheError,
ReqwestError(fractal_api::reqwest::Error),
NetworkError(fractal_api::reqwest::StatusCode),
MatrixError(MatrixErrorCode, String),
@@ -41,8 +39,5 @@ impl From<StandardErrorResponse> for Error {
}
}
-derror!(fractal_api::url::ParseError, Error::BackendError);
-derror!(io::Error, Error::BackendError);
derror!(glib::error::Error, Error::BackendError);
derror!(fractal_api::identifiers::Error, Error::BackendError);
-derror!(serde_json::Error, Error::CacheError);
diff --git a/fractal-gtk/src/model/fileinfo.rs b/fractal-gtk/src/model/fileinfo.rs
index ceb6bf85..f5df5305 100644
--- a/fractal-gtk/src/model/fileinfo.rs
+++ b/fractal-gtk/src/model/fileinfo.rs
@@ -1,9 +1,10 @@
+use fractal_api::url::Url;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Info {
- pub thumbnail_url: Option<String>,
+ pub thumbnail_url: Option<Url>,
pub thumbnail_info: Option<JsonValue>,
pub w: Option<u32>,
pub h: Option<u32>,
diff --git a/fractal-gtk/src/model/member.rs b/fractal-gtk/src/model/member.rs
index f8c807f8..2f9885fb 100644
--- a/fractal-gtk/src/model/member.rs
+++ b/fractal-gtk/src/model/member.rs
@@ -1,9 +1,11 @@
+use either::Either;
use fractal_api::identifiers::UserId;
use fractal_api::r0::search::user::User;
use fractal_api::r0::sync::get_joined_members::RoomMember;
use fractal_api::url::Url;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
+use std::path::PathBuf;
// TODO: Make this non-(de)serializable
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -12,7 +14,7 @@ pub struct Member {
#[serde(rename = "display_name")]
pub alias: Option<String>,
#[serde(rename = "avatar_url")]
- pub avatar: Option<String>,
+ pub avatar: Option<Either<Url, PathBuf>>,
}
impl Member {
@@ -37,7 +39,7 @@ impl From<User> for Member {
Self {
uid: user.user_id,
alias: user.display_name,
- avatar: user.avatar_url.map(Url::into_string),
+ avatar: user.avatar_url.map(Either::Left),
}
}
}
@@ -47,7 +49,7 @@ impl From<(UserId, RoomMember)> for Member {
Member {
uid,
alias: roommember.display_name,
- avatar: roommember.avatar_url.as_ref().map(ToString::to_string),
+ avatar: roommember.avatar_url.map(Either::Left),
}
}
}
diff --git a/fractal-gtk/src/model/message.rs b/fractal-gtk/src/model/message.rs
index 60bc4761..66d1d72a 100644
--- a/fractal-gtk/src/model/message.rs
+++ b/fractal-gtk/src/model/message.rs
@@ -1,12 +1,16 @@
use chrono::prelude::*;
use chrono::DateTime;
use chrono::TimeZone;
-use fractal_api::identifiers::{Error as IdError, EventId, RoomId, UserId};
+use fractal_api::{
+ identifiers::{Error as IdError, EventId, RoomId, UserId},
+ url::Url,
+};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::convert::TryInto;
+use std::path::PathBuf;
//FIXME make properties private
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -16,8 +20,10 @@ pub struct Message {
pub body: String,
pub date: DateTime<Local>,
pub room: RoomId,
- pub thumb: Option<String>,
- pub url: Option<String>,
+ pub thumb: Option<Url>,
+ pub local_path_thumb: Option<PathBuf>,
+ pub url: Option<Url>,
+ pub local_path: Option<PathBuf>,
// FIXME: This should be a required field but it is mandatory
// to do it this way because because this struct is used both
// for received messages and messages to send. At the moment
@@ -72,7 +78,9 @@ impl Message {
date,
room,
thumb: None,
+ local_path_thumb: None,
url: None,
+ local_path: None,
formatted_body: None,
format: None,
source: None,
@@ -143,7 +151,9 @@ impl Message {
mtype: type_.to_string(),
body: String::new(),
url: None,
+ local_path: None,
thumb: None,
+ local_path_thumb: None,
formatted_body: None,
format: None,
source: serde_json::to_string_pretty(&msg).ok(),
@@ -184,17 +194,12 @@ impl Message {
match mtype.as_str() {
"m.image" | "m.file" | "m.video" | "m.audio" => {
- let url = c["url"].as_str().map(String::from).unwrap_or_default();
- let mut t = c["info"]["thumbnail_url"]
+ self.url = c["url"].as_str().map(Url::parse).and_then(Result::ok);
+ self.thumb = c["info"]["thumbnail_url"]
.as_str()
- .map(String::from)
- .unwrap_or_default();
- if t.is_empty() && !url.is_empty() {
- t = url.clone();
- }
-
- self.url = Some(url);
- self.thumb = Some(t);
+ .map(Url::parse)
+ .and_then(Result::ok)
+ .or_else(|| Some(self.url.clone()?));
}
"m.text" => {
// Only m.text messages can be replies for backward compatibility
@@ -213,18 +218,13 @@ impl Message {
}
fn parse_m_sticker(&mut self, c: &JsonValue) {
- let url = c["url"].as_str().map(String::from).unwrap_or_default();
- let mut t = c["info"]["thumbnail_url"]
+ self.url = c["url"].as_str().map(Url::parse).and_then(Result::ok);
+ self.thumb = c["info"]["thumbnail_url"]
.as_str()
- .map(String::from)
- .unwrap_or_default();
- if t.is_empty() && !url.is_empty() {
- t = url.clone();
- }
-
+ .map(Url::parse)
+ .and_then(Result::ok)
+ .or_else(|| Some(self.url.clone()?));
self.body = c["body"].as_str().map(String::from).unwrap_or_default();
- self.url = Some(url);
- self.thumb = Some(t);
}
/// Create a vec of Message from a json event list
diff --git a/fractal-gtk/src/model/room.rs b/fractal-gtk/src/model/room.rs
index 80856f4e..029a5cbd 100644
--- a/fractal-gtk/src/model/room.rs
+++ b/fractal-gtk/src/model/room.rs
@@ -4,6 +4,7 @@ use crate::backend::user::get_user_avatar;
use crate::model::member::Member;
use crate::model::member::MemberList;
use crate::model::message::Message;
+use either::Either;
use fractal_api::identifiers::{Error as IdError, EventId, RoomId, UserId};
use fractal_api::r0::directory::post_public_rooms::Chunk as PublicRoomsChunk;
use fractal_api::r0::sync::sync_events::Response as SyncResponse;
@@ -12,6 +13,7 @@ use log::{debug, info};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::convert::{TryFrom, TryInto};
+use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum RoomMembership {
@@ -81,7 +83,7 @@ pub enum RoomTag {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Room {
pub id: RoomId,
- pub avatar: Option<String>, // TODO: Use Option<Url>
+ pub avatar: Option<Url>,
pub name: Option<String>,
pub topic: Option<String>,
pub alias: Option<String>,
@@ -160,7 +162,8 @@ impl Room {
let mut r = Self {
name: calculate_room_name(stevents, &user_id),
- avatar: evc(stevents, "m.room.avatar", "url"),
+ avatar: evc(stevents, "m.room.avatar", "url")
+ .and_then(|ref url| Url::parse(url).ok()),
alias: evc(stevents, "m.room.canonical_alias", "alias"),
topic: evc(stevents, "m.room.topic", "topic"),
direct: direct.contains(&k),
@@ -209,7 +212,7 @@ impl Room {
{
let kicker = Member {
alias: Some(kicker_alias),
- avatar: Some(kicker_avatar),
+ avatar: Some(Either::Right(kicker_avatar)),
uid: leave_id,
};
let reason = Reason::Kicked(
@@ -236,7 +239,7 @@ impl Room {
.iter()
.map(|(k, room)| {
let stevents = &room.invite_state.events;
- let alias_avatar: Result<Option<(String, String)>, IdError> = stevents
+ let alias_avatar: Result<Option<(String, PathBuf)>, IdError> = stevents
.iter()
.find(|x| {
x["content"]["membership"] == "invite"
@@ -252,13 +255,14 @@ impl Room {
if let Some((alias, avatar)) = alias_avatar? {
let inv_sender = Member {
alias: Some(alias),
- avatar: Some(avatar),
+ avatar: Some(Either::Right(avatar)),
uid: user_id.clone(),
};
Ok(Some(Self {
name: calculate_room_name(stevents, &user_id),
- avatar: evc(stevents, "m.room.avatar", "url"),
+ avatar: evc(stevents, "m.room.avatar", "url")
+ .and_then(|ref url| Url::parse(url).ok()),
alias: evc(stevents, "m.room.canonical_alias", "alias"),
topic: evc(stevents, "m.room.topic", "topic"),
direct: direct.contains(&k),
@@ -325,7 +329,7 @@ impl From<PublicRoomsChunk> for Room {
Self {
alias: input.canonical_alias.as_ref().map(ToString::to_string),
name: input.name,
- avatar: input.avatar_url.map(Url::into_string),
+ avatar: input.avatar_url,
topic: input.topic,
n_members: input.num_joined_members,
world_readable: input.world_readable,
@@ -429,8 +433,12 @@ fn parse_room_member(msg: &JsonValue) -> Option<Member> {
Some(Member {
uid: msg["sender"].as_str().unwrap_or_default().try_into().ok()?,
- alias: c["displayname"].as_str().map(Into::into),
- avatar: c["avatar_url"].as_str().map(Into::into),
+ alias: c["displayname"].as_str().map(String::from),
+ avatar: c["avatar_url"]
+ .as_str()
+ .map(Url::parse)
+ .and_then(Result::ok)
+ .map(Either::Left),
})
}
diff --git a/fractal-gtk/src/uitypes.rs b/fractal-gtk/src/uitypes.rs
index c6c44b1f..8721c05a 100644
--- a/fractal-gtk/src/uitypes.rs
+++ b/fractal-gtk/src/uitypes.rs
@@ -2,7 +2,11 @@ use crate::types::Message;
use crate::widgets;
use chrono::prelude::DateTime;
use chrono::prelude::Local;
-use fractal_api::identifiers::{EventId, UserId};
+use fractal_api::{
+ identifiers::{EventId, UserId},
+ url::Url,
+};
+use std::path::PathBuf;
/* MessageContent contains all data needed to display one row
* therefore it should contain only one Message body with one format
@@ -16,8 +20,9 @@ pub struct MessageContent {
pub body: String,
pub date: DateTime<Local>,
pub replace_date: Option<DateTime<Local>>,
- pub thumb: Option<String>,
- pub url: Option<String>,
+ pub thumb: Option<Url>,
+ pub url: Option<Url>,
+ pub local_path: Option<PathBuf>,
pub formatted_body: Option<String>,
pub format: Option<String>,
/* in some places we still need the backend message type (e.g. media viewer) */
diff --git a/fractal-gtk/src/util.rs b/fractal-gtk/src/util.rs
index 27ce7335..764a8e8e 100644
--- a/fractal-gtk/src/util.rs
+++ b/fractal-gtk/src/util.rs
@@ -1,4 +1,3 @@
-use crate::error::Error;
use crate::globals::CACHE_PATH;
use failure::format_err;
use failure::Error as FailError;
@@ -8,19 +7,18 @@ use gio::{Settings, SettingsExt, SettingsSchemaSource};
use html2pango::{html_escape, markup_links};
use log::error;
use std::fs::create_dir_all;
+use std::io::Error as IoError;
+use std::path::PathBuf;
use std::sync::mpsc::SendError;
-pub fn cache_dir_path(dir: Option<&str>, name: &str) -> Result<String, Error> {
+pub fn cache_dir_path(dir: Option<&str>, name: &str) -> Result<PathBuf, IoError> {
let path = CACHE_PATH.join(dir.unwrap_or_default());
if !path.is_dir() {
create_dir_all(&path)?;
}
- path.join(name)
- .to_str()
- .map(Into::into)
- .ok_or(Error::CacheError)
+ Ok(path.join(name))
}
pub fn get_pixbuf_data(pb: &Pixbuf) -> Result<Vec<u8>, FailError> {
diff --git a/fractal-gtk/src/widgets/avatar.rs b/fractal-gtk/src/widgets/avatar.rs
index fcd1fe34..198dca34 100644
--- a/fractal-gtk/src/widgets/avatar.rs
+++ b/fractal-gtk/src/widgets/avatar.rs
@@ -1,4 +1,5 @@
use std::cell::RefCell;
+use std::path::Path;
use std::rc::Rc;
use crate::util::cache_dir_path;
@@ -190,7 +191,7 @@ impl AvatarExt for gtk::Overlay {
}
}
-fn load_pixbuf(path: &str, size: i32) -> Option<Pixbuf> {
+fn load_pixbuf(path: &Path, size: i32) -> Option<Pixbuf> {
if let Ok(pixbuf) = Pixbuf::new_from_file(&path) {
// FIXME: We end up loading the file twice but we need to load the file first to find out its
dimensions to be
// able to decide wether to scale by width or height and gdk doesn't provide simple API to scale a
loaded
diff --git a/fractal-gtk/src/widgets/image.rs b/fractal-gtk/src/widgets/image.rs
index ffaafe12..1f056bb7 100644
--- a/fractal-gtk/src/widgets/image.rs
+++ b/fractal-gtk/src/widgets/image.rs
@@ -1,4 +1,5 @@
use crate::backend::{media, ThreadPool};
+use either::Either;
use fractal_api::url::Url;
use gdk::prelude::GdkContextExt;
use gdk_pixbuf::Pixbuf;
@@ -9,7 +10,7 @@ use glib::source::Continue;
use gtk::prelude::*;
use gtk::DrawingArea;
use log::error;
-use std::path::Path;
+use std::path::{Path, PathBuf};
use std::sync::mpsc::channel;
use std::sync::mpsc::TryRecvError;
use std::sync::mpsc::{Receiver, Sender};
@@ -17,8 +18,8 @@ use std::sync::{Arc, Mutex};
#[derive(Clone, Debug)]
pub struct Image {
- pub path: String,
- pub local_path: Arc<Mutex<Option<String>>>,
+ pub path: Either<Url, PathBuf>,
+ pub local_path: Arc<Mutex<Option<PathBuf>>>,
pub server_url: Url,
pub max_size: Option<(i32, i32)>,
pub widget: DrawingArea,
@@ -46,7 +47,7 @@ impl Image {
/// .size(Some((50, 50)))
/// .build();
/// ```
- pub fn new(server_url: Url, path: &str) -> Image {
+ pub fn new(server_url: Url, path: Either<Url, PathBuf>) -> Image {
let da = DrawingArea::new();
da.add_events(gdk::EventMask::ENTER_NOTIFY_MASK);
da.add_events(gdk::EventMask::LEAVE_NOTIFY_MASK);
@@ -63,7 +64,7 @@ impl Image {
});
Image {
- path: path.to_string(),
+ path,
local_path: Arc::new(Mutex::new(None)),
server_url,
max_size: None,
@@ -255,47 +256,48 @@ impl Image {
/// If `path` starts with mxc this func download the img async, in other case the image is loaded
/// in the `image` widget scaled to size
pub fn load_async(&self, thread_pool: ThreadPool) {
- if self.path.starts_with("mxc:") {
- // asyn load
- let (tx, rx): (Sender<media::MediaResult>, Receiver<media::MediaResult>) = channel();
- let command = if self.thumb {
- media::get_thumb_async
- } else {
- media::get_media_async
- };
- command(
- thread_pool,
- self.server_url.clone(),
- self.path.to_string(),
- tx,
- );
- let local_path = self.local_path.clone();
- let pix = self.pixbuf.clone();
- let scaled = self.scaled.clone();
- let da = self.widget.clone();
-
- da.get_style_context().add_class("image-spinner");
- gtk::timeout_add(50, move || match rx.try_recv() {
- Err(TryRecvError::Empty) => Continue(true),
- Err(TryRecvError::Disconnected) => Continue(false),
- Ok(Ok(fname)) => {
- *local_path.lock().unwrap() = Some(fname.clone());
- load_pixbuf(pix.clone(), scaled.clone(), da.clone(), &fname);
- da.get_style_context().remove_class("image-spinner");
- Continue(false)
- }
- Ok(Err(err)) => {
- error!("Image path could not be found due to error: {:?}", err);
- Continue(false)
- }
- });
- } else {
- load_pixbuf(
- self.pixbuf.clone(),
- self.scaled.clone(),
- self.widget.clone(),
- &self.path,
- );
+ match self.path.as_ref() {
+ Either::Left(url) if url.scheme() == "mxc" => {
+ let mxc = url.clone();
+ // asyn load
+ let (tx, rx): (Sender<media::MediaResult>, Receiver<media::MediaResult>) =
+ channel();
+ let command = if self.thumb {
+ media::get_thumb_async
+ } else {
+ media::get_media_async
+ };
+ command(thread_pool, self.server_url.clone(), mxc, tx);
+ let local_path = self.local_path.clone();
+ let pix = self.pixbuf.clone();
+ let scaled = self.scaled.clone();
+ let da = self.widget.clone();
+
+ da.get_style_context().add_class("image-spinner");
+ gtk::timeout_add(50, move || match rx.try_recv() {
+ Err(TryRecvError::Empty) => Continue(true),
+ Err(TryRecvError::Disconnected) => Continue(false),
+ Ok(Ok(fname)) => {
+ *local_path.lock().unwrap() = Some(fname.clone());
+ load_pixbuf(pix.clone(), scaled.clone(), da.clone(), &fname);
+ da.get_style_context().remove_class("image-spinner");
+ Continue(false)
+ }
+ Ok(Err(err)) => {
+ error!("Image path could not be found due to error: {:?}", err);
+ Continue(false)
+ }
+ });
+ }
+ Either::Right(path) => {
+ load_pixbuf(
+ self.pixbuf.clone(),
+ self.scaled.clone(),
+ self.widget.clone(),
+ &path,
+ );
+ }
+ _ => error!("The resource URL doesn't have the scheme mxc:"),
}
}
}
@@ -304,10 +306,10 @@ pub fn load_pixbuf(
pix: Arc<Mutex<Option<Pixbuf>>>,
scaled: Arc<Mutex<Option<Pixbuf>>>,
widget: DrawingArea,
- fname: &str,
+ fname: &Path,
) {
if is_gif(&fname) {
- load_animation(pix, scaled, widget, &fname);
+ load_animation(pix, scaled, widget, fname);
return;
}
@@ -338,7 +340,7 @@ pub fn load_animation(
pix: Arc<Mutex<Option<Pixbuf>>>,
scaled: Arc<Mutex<Option<Pixbuf>>>,
widget: DrawingArea,
- fname: &str,
+ fname: &Path,
) {
let res = PixbufAnimation::new_from_file(fname);
if res.is_err() {
@@ -362,13 +364,12 @@ pub fn load_animation(
});
}
-pub fn is_gif(fname: &str) -> bool {
- let p = &Path::new(fname);
- if !p.is_file() {
+pub fn is_gif(fname: &Path) -> bool {
+ if !fname.is_file() {
return false;
}
- if let Ok(info) = gio::File::new_for_path(&p).query_info(
+ if let Ok(info) = gio::File::new_for_path(fname).query_info(
&gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
gio::FileQueryInfoFlags::NONE,
gio::NONE_CANCELLABLE,
diff --git a/fractal-gtk/src/widgets/inline_player.rs b/fractal-gtk/src/widgets/inline_player.rs
index 8b7502f8..5fce6ed9 100644
--- a/fractal-gtk/src/widgets/inline_player.rs
+++ b/fractal-gtk/src/widgets/inline_player.rs
@@ -23,7 +23,7 @@ use glib::clone;
use gst::prelude::*;
use gst::ClockTime;
use gstreamer_pbutils::Discoverer;
-use log::{error, info, warn};
+use log::{error, warn};
use gtk::prelude::*;
use gtk::ButtonExt;
@@ -37,6 +37,7 @@ use fragile::Fragile;
use std::cell::RefCell;
use std::ops::Deref;
+use std::path::PathBuf;
use std::rc::Rc;
use std::sync::mpsc::channel;
@@ -54,8 +55,8 @@ pub trait PlayerExt {
fn stop(&self);
fn initialize_stream(
player: &Rc<Self>,
- media_url: &str,
- server_url: &Url,
+ media_url: Url,
+ server_url: Url,
thread_pool: ThreadPool,
bx: >k::Box,
start_playing: bool,
@@ -145,7 +146,7 @@ pub struct PlayerControls {
pub trait MediaPlayer {
fn get_player(&self) -> gst_player::Player;
fn get_controls(&self) -> Option<PlayerControls>;
- fn get_local_path_access(&self) -> Rc<RefCell<Option<String>>>;
+ fn get_local_path_access(&self) -> Rc<RefCell<Option<PathBuf>>>;
}
trait ControlsConnection {
@@ -158,7 +159,7 @@ trait ControlsConnection {
pub struct AudioPlayerWidget {
player: gst_player::Player,
controls: PlayerControls,
- local_path: Rc<RefCell<Option<String>>>,
+ local_path: Rc<RefCell<Option<PathBuf>>>,
}
impl Default for AudioPlayerWidget {
@@ -228,7 +229,7 @@ impl MediaPlayer for AudioPlayerWidget {
Some(self.controls.clone())
}
- fn get_local_path_access(&self) -> Rc<RefCell<Option<String>>> {
+ fn get_local_path_access(&self) -> Rc<RefCell<Option<PathBuf>>> {
self.local_path.clone()
}
}
@@ -237,7 +238,7 @@ impl MediaPlayer for AudioPlayerWidget {
pub struct VideoPlayerWidget {
player: gst_player::Player,
controls: Option<PlayerControls>,
- local_path: Rc<RefCell<Option<String>>>,
+ local_path: Rc<RefCell<Option<PathBuf>>>,
dimensions: Rc<RefCell<Option<(i32, i32)>>>,
state: Rc<RefCell<Option<gst_player::PlayerState>>>,
}
@@ -465,7 +466,7 @@ impl MediaPlayer for VideoPlayerWidget {
self.controls.clone()
}
- fn get_local_path_access(&self) -> Rc<RefCell<Option<String>>> {
+ fn get_local_path_access(&self) -> Rc<RefCell<Option<PathBuf>>> {
self.local_path.clone()
}
}
@@ -501,15 +502,15 @@ impl<T: MediaPlayer + 'static> PlayerExt for T {
fn initialize_stream(
player: &Rc<Self>,
- media_url: &str,
- server_url: &Url,
+ media_url: Url,
+ server_url: Url,
thread_pool: ThreadPool,
bx: >k::Box,
start_playing: bool,
) {
bx.set_opacity(0.3);
let (tx, rx): (Sender<media::MediaResult>, Receiver<media::MediaResult>) = channel();
- media::get_media_async(thread_pool, server_url.clone(), media_url.to_string(), tx);
+ media::get_media_async(thread_pool, server_url, media_url, tx);
let local_path = player.get_local_path_access();
gtk::timeout_add(
50,
@@ -523,23 +524,28 @@ impl<T: MediaPlayer + 'static> PlayerExt for T {
Continue(true)
},
Ok(Ok(path)) => {
- info!("MEDIA PATH: {}", &path);
- *local_path.borrow_mut() = Some(path.clone());
- if ! start_playing {
- if let Some(controls) = player.get_controls() {
- if let Ok(duration) = get_media_duration(&path) {
- controls.timer.on_duration_changed(Duration(duration))
+ match Url::from_file_path(&path) {
+ Ok(uri) => {
+ *local_path.borrow_mut() = Some(path);
+ if !start_playing {
+ if let Some(controls) = player.get_controls() {
+ if let Ok(duration) = get_media_duration(&uri) {
+ controls.timer.on_duration_changed(Duration(duration))
+ }
+ }
+ }
+ player.get_player().set_uri(uri.as_str());
+ if player.get_controls().is_some() {
+ ControlsConnection::init(&player);
+ }
+ bx.set_opacity(1.0);
+ if start_playing {
+ player.play();
}
}
- }
- let uri = format!("file://{}", path);
- player.get_player().set_uri(&uri);
- if player.get_controls().is_some() {
- ControlsConnection::init(&player);
- }
- bx.set_opacity(1.0);
- if start_playing {
- player.play();
+ Err(_) => {
+ error!("Media path is not valid: {:?}", path);
+ }
}
Continue(false)
}
@@ -702,9 +708,9 @@ fn adjust_box_margins_to_video_dimensions(bx: >k::Box, video_width: i32, video
}
}
-pub fn get_media_duration(file: &str) -> Result<ClockTime, glib::Error> {
+pub fn get_media_duration(uri: &Url) -> Result<ClockTime, glib::Error> {
let timeout = ClockTime::from_seconds(1);
let discoverer = Discoverer::new(timeout)?;
- let info = discoverer.discover_uri(&format!("file://{}", file))?;
+ let info = discoverer.discover_uri(uri.as_str())?;
Ok(info.get_duration())
}
diff --git a/fractal-gtk/src/widgets/media_viewer.rs b/fractal-gtk/src/widgets/media_viewer.rs
index e1c3069b..4d55f010 100644
--- a/fractal-gtk/src/widgets/media_viewer.rs
+++ b/fractal-gtk/src/widgets/media_viewer.rs
@@ -9,6 +9,7 @@ use std::collections::HashMap;
use std::rc::Rc;
use crate::i18n::i18n;
+use either::Either;
use fractal_api::identifiers::UserId;
use fractal_api::url::Url;
use gdk::*;
@@ -314,6 +315,8 @@ impl Data {
}
pub fn redraw_media_in_viewport(&mut self, thread_pool: ThreadPool) {
+ let msg = &self.media_list[self.current_media_index];
+ let url = unwrap_or_unit_return!(msg.url.clone());
let media_container = self
.builder
.get_object::<gtk::EventBox>("media_container")
@@ -323,11 +326,9 @@ impl Data {
media_container.remove(&child);
}
- let msg = &self.media_list[self.current_media_index];
- let url = msg.url.clone().unwrap_or_default();
match msg.mtype.as_ref() {
"m.image" => {
- let image = image::Image::new(self.server_url.clone(), &url)
+ let image = image::Image::new(self.server_url.clone(), Either::Left(url))
.shrink_to_fit(true)
.center(true)
.build(thread_pool);
@@ -336,7 +337,7 @@ impl Data {
self.widget = Widget::Image(image);
}
"m.video" => {
- let widget = self.create_video_widget(thread_pool, &url);
+ let widget = self.create_video_widget(thread_pool, url);
media_container.add(&widget.outer_box);
self.widget = Widget::Video(widget);
media_container.show_all();
@@ -348,7 +349,7 @@ impl Data {
self.set_nav_btn_visibility();
}
- fn create_video_widget(&self, thread_pool: ThreadPool, url: &str) -> VideoWidget {
+ fn create_video_widget(&self, thread_pool: ThreadPool, url: Url) -> VideoWidget {
let with_controls = true;
let player = VideoPlayerWidget::new(with_controls);
let bx = gtk::Box::new(gtk::Orientation::Vertical, 0);
@@ -356,7 +357,7 @@ impl Data {
PlayerExt::initialize_stream(
&player,
url,
- &self.server_url.clone(),
+ self.server_url.clone(),
thread_pool,
&bx,
start_playing,
@@ -661,6 +662,8 @@ impl MediaViewer {
}
pub fn display_media_viewer(&mut self, thread_pool: ThreadPool, media_msg: Message) {
+ let url = unwrap_or_unit_return!(media_msg.url.clone());
+
let previous_media_revealer = self
.builder
.get_object::<gtk::Revealer>("previous_media_revealer")
@@ -680,13 +683,13 @@ impl MediaViewer {
.get_object::<gtk::EventBox>("media_container")
.expect("Cant find media_container in ui file.");
- let url = media_msg.url.clone().unwrap_or_default();
match media_msg.mtype.as_ref() {
"m.image" => {
- let image = image::Image::new(self.data.borrow().server_url.clone(), &url)
- .shrink_to_fit(true)
- .center(true)
- .build(thread_pool);
+ let image =
+ image::Image::new(self.data.borrow().server_url.clone(), Either::Left(url))
+ .shrink_to_fit(true)
+ .center(true)
+ .build(thread_pool);
media_container.add(&image.widget);
media_container.show_all();
@@ -694,7 +697,7 @@ impl MediaViewer {
self.data.borrow_mut().widget = Widget::Image(image);
}
"m.video" => {
- let video_widget = self.data.borrow().create_video_widget(thread_pool, &url);
+ let video_widget = self.data.borrow().create_video_widget(thread_pool, url);
media_container.add(&video_widget.outer_box);
media_container.show_all();
diff --git a/fractal-gtk/src/widgets/message.rs b/fractal-gtk/src/widgets/message.rs
index 562327f2..9130ef0c 100644
--- a/fractal-gtk/src/widgets/message.rs
+++ b/fractal-gtk/src/widgets/message.rs
@@ -1,17 +1,16 @@
use crate::i18n::i18n;
use itertools::Itertools;
+use crate::appop::UserInfoCache;
use crate::backend::ThreadPool;
-use crate::cache::CacheMap;
use chrono::prelude::*;
-use fractal_api::identifiers::UserId;
+use either::Either;
use fractal_api::r0::AccessToken;
use fractal_api::url::Url;
use glib::clone;
use gtk::{prelude::*, ButtonExt, ContainerExt, LabelExt, Overlay, WidgetExt};
use std::cmp::max;
use std::rc::Rc;
-use std::sync::{Arc, Mutex};
use crate::util::markup_text;
@@ -72,7 +71,7 @@ impl MessageBox {
pub fn create(
&mut self,
thread_pool: ThreadPool,
- user_info_cache: Arc<Mutex<CacheMap<UserId, (String, String)>>>,
+ user_info_cache: UserInfoCache,
msg: &Message,
has_header: bool,
is_temp: bool,
@@ -129,7 +128,7 @@ impl MessageBox {
pub fn tmpwidget(
mut self,
thread_pool: ThreadPool,
- user_info_cache: Arc<Mutex<CacheMap<UserId, (String, String)>>>,
+ user_info_cache: UserInfoCache,
msg: &Message,
) -> MessageBox {
self.create(thread_pool, user_info_cache, msg, true, true);
@@ -143,7 +142,7 @@ impl MessageBox {
pub fn update_header(
&mut self,
thread_pool: ThreadPool,
- user_info_cache: Arc<Mutex<CacheMap<UserId, (String, String)>>>,
+ user_info_cache: UserInfoCache,
msg: Message,
has_header: bool,
) {
@@ -168,7 +167,7 @@ impl MessageBox {
fn widget(
&mut self,
thread_pool: ThreadPool,
- user_info_cache: Arc<Mutex<CacheMap<UserId, (String, String)>>>,
+ user_info_cache: UserInfoCache,
msg: &Message,
) -> gtk::Box {
// msg
@@ -265,7 +264,7 @@ impl MessageBox {
fn build_room_msg_avatar(
&self,
thread_pool: ThreadPool,
- user_info_cache: Arc<Mutex<CacheMap<UserId, (String, String)>>>,
+ user_info_cache: UserInfoCache,
msg: &Message,
) -> widgets::Avatar {
let uid = msg.sender.clone();
@@ -417,29 +416,35 @@ impl MessageBox {
fn build_room_msg_image(&mut self, thread_pool: ThreadPool, msg: &Message) -> gtk::Box {
let bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
- let img_path = match msg.thumb {
- // If the thumbnail is not a valid URL we use the msg.url
- Some(ref m) if m.starts_with("mxc:") || m.starts_with("http") => m.clone(),
- _ => msg.url.clone().unwrap_or_default(),
- };
- let image = widgets::image::Image::new(self.server_url.clone(), &img_path)
- .size(Some(globals::MAX_IMAGE_SIZE))
- .build(thread_pool);
+ // If the thumbnail is not a valid URL we use the msg.url
+ let img = msg
+ .thumb
+ .clone()
+ .filter(|m| m.scheme() == "mxc" || m.scheme().starts_with("http"))
+ .or_else(|| msg.url.clone())
+ .map(Either::Left)
+ .or_else(|| Some(Either::Right(msg.local_path.clone()?)));
+
+ if let Some(img_path) = img {
+ let image = widgets::image::Image::new(self.server_url.clone(), img_path)
+ .size(Some(globals::MAX_IMAGE_SIZE))
+ .build(thread_pool);
- image.widget.get_style_context().add_class("image-widget");
+ image.widget.get_style_context().add_class("image-widget");
- bx.pack_start(&image.widget, true, true, 0);
- bx.show_all();
- self.image = Some(image.widget);
- self.connect_media_viewer(msg);
+ bx.pack_start(&image.widget, true, true, 0);
+ bx.show_all();
+ self.image = Some(image.widget);
+ self.connect_media_viewer(msg);
+ }
bx
}
fn build_room_msg_sticker(&self, thread_pool: ThreadPool, msg: &Message) -> gtk::Box {
let bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
- if let Some(url) = msg.url.as_ref() {
- let image = widgets::image::Image::new(self.server_url.clone(), url)
+ if let Some(url) = msg.url.clone() {
+ let image = widgets::image::Image::new(self.server_url.clone(), Either::Left(url))
.size(Some(globals::MAX_STICKER_SIZE))
.build(thread_pool);
image.widget.set_tooltip_text(Some(&msg.body[..]));
@@ -452,16 +457,23 @@ impl MessageBox {
fn build_room_audio_player(&self, thread_pool: ThreadPool, msg: &Message) -> gtk::Box {
let bx = gtk::Box::new(gtk::Orientation::Horizontal, 6);
- let player = AudioPlayerWidget::new();
- let start_playing = false;
- PlayerExt::initialize_stream(
- &player,
- &msg.url.clone().unwrap_or_default(),
- &self.server_url,
- thread_pool,
- &bx,
- start_playing,
- );
+
+ if let Some(url) = msg.url.clone() {
+ let player = AudioPlayerWidget::new();
+ let start_playing = false;
+ PlayerExt::initialize_stream(
+ &player,
+ url,
+ self.server_url.clone(),
+ thread_pool,
+ &bx,
+ start_playing,
+ );
+
+ let control_box = PlayerExt::get_controls_container(&player)
+ .expect("Every AudioPlayer must have controls.");
+ bx.pack_start(&control_box, false, true, 0);
+ }
let download_btn =
gtk::Button::new_from_icon_name(Some("document-save-symbolic"), gtk::IconSize::Button);
@@ -475,10 +487,6 @@ impl MessageBox {
let data = glib::Variant::from(evid);
download_btn.set_action_target_value(Some(&data));
download_btn.set_action_name(Some("message.save_as"));
-
- let control_box = PlayerExt::get_controls_container(&player)
- .expect("Every AudioPlayer must have controls.");
- bx.pack_start(&control_box, false, true, 0);
bx.pack_start(&download_btn, false, false, 3);
let outer_box = gtk::Box::new(gtk::Orientation::Vertical, 6);
@@ -494,72 +502,76 @@ impl MessageBox {
}
fn build_room_video_player(&mut self, thread_pool: ThreadPool, msg: &Message) -> gtk::Box {
- let with_controls = false;
- let player = VideoPlayerWidget::new(with_controls);
let bx = gtk::Box::new(gtk::Orientation::Vertical, 6);
- let start_playing = false;
- PlayerExt::initialize_stream(
- &player,
- &msg.url.clone().unwrap_or_default(),
- &self.server_url,
- thread_pool,
- &bx,
- start_playing,
- );
- let overlay = Overlay::new();
- let video_widget = player.get_video_widget();
- video_widget.set_size_request(-1, 390);
- VideoPlayerWidget::auto_adjust_video_dimensions(&player);
- overlay.add(&video_widget);
+ if let Some(url) = msg.url.clone() {
+ let with_controls = false;
+ let player = VideoPlayerWidget::new(with_controls);
+ let start_playing = false;
+ PlayerExt::initialize_stream(
+ &player,
+ url,
+ self.server_url.clone(),
+ thread_pool,
+ &bx,
+ start_playing,
+ );
- let play_button = gtk::Button::new();
- let play_icon = gtk::Image::new_from_icon_name(
- Some("media-playback-start-symbolic"),
- gtk::IconSize::Dialog,
- );
- play_button.set_image(Some(&play_icon));
- play_button.set_halign(gtk::Align::Center);
- play_button.set_valign(gtk::Align::Center);
- play_button.get_style_context().add_class("osd");
- play_button.get_style_context().add_class("play-icon");
- play_button.get_style_context().add_class("flat");
- let evid = msg
- .id
- .as_ref()
- .map(|evid| evid.to_string())
- .unwrap_or_default();
- let data = glib::Variant::from(evid);
- play_button.set_action_name(Some("app.open-media-viewer"));
- play_button.set_action_target_value(Some(&data));
- overlay.add_overlay(&play_button);
-
- let menu_button = gtk::MenuButton::new();
- let three_dot_icon =
- gtk::Image::new_from_icon_name(Some("view-more-symbolic"), gtk::IconSize::Button);
- menu_button.set_image(Some(&three_dot_icon));
- menu_button.get_style_context().add_class("osd");
- menu_button.get_style_context().add_class("round-button");
- menu_button.get_style_context().add_class("flat");
- menu_button.set_margin_top(12);
- menu_button.set_margin_end(12);
- menu_button.set_opacity(0.8);
- menu_button.set_halign(gtk::Align::End);
- menu_button.set_valign(gtk::Align::Start);
- menu_button.connect_size_allocate(|button, allocation| {
- let diameter = max(allocation.width, allocation.height);
- button.set_size_request(diameter, diameter);
- });
- overlay.add_overlay(&menu_button);
-
- let evid = msg.id.as_ref();
- let redactable = msg.redactable;
- let menu = MessageMenu::new(evid, &RowType::Video, &redactable, None, None);
- menu_button.set_popover(Some(&menu.get_popover()));
+ let overlay = Overlay::new();
+ let video_widget = player.get_video_widget();
+ video_widget.set_size_request(-1, 390);
+ VideoPlayerWidget::auto_adjust_video_dimensions(&player);
+ overlay.add(&video_widget);
+
+ let play_button = gtk::Button::new();
+ let play_icon = gtk::Image::new_from_icon_name(
+ Some("media-playback-start-symbolic"),
+ gtk::IconSize::Dialog,
+ );
+ play_button.set_image(Some(&play_icon));
+ play_button.set_halign(gtk::Align::Center);
+ play_button.set_valign(gtk::Align::Center);
+ play_button.get_style_context().add_class("osd");
+ play_button.get_style_context().add_class("play-icon");
+ play_button.get_style_context().add_class("flat");
+ let evid = msg
+ .id
+ .as_ref()
+ .map(|evid| evid.to_string())
+ .unwrap_or_default();
+ let data = glib::Variant::from(evid);
+ play_button.set_action_name(Some("app.open-media-viewer"));
+ play_button.set_action_target_value(Some(&data));
+ overlay.add_overlay(&play_button);
+
+ let menu_button = gtk::MenuButton::new();
+ let three_dot_icon =
+ gtk::Image::new_from_icon_name(Some("view-more-symbolic"), gtk::IconSize::Button);
+ menu_button.set_image(Some(&three_dot_icon));
+ menu_button.get_style_context().add_class("osd");
+ menu_button.get_style_context().add_class("round-button");
+ menu_button.get_style_context().add_class("flat");
+ menu_button.set_margin_top(12);
+ menu_button.set_margin_end(12);
+ menu_button.set_opacity(0.8);
+ menu_button.set_halign(gtk::Align::End);
+ menu_button.set_valign(gtk::Align::Start);
+ menu_button.connect_size_allocate(|button, allocation| {
+ let diameter = max(allocation.width, allocation.height);
+ button.set_size_request(diameter, diameter);
+ });
+ overlay.add_overlay(&menu_button);
+
+ let evid = msg.id.as_ref();
+ let redactable = msg.redactable;
+ let menu = MessageMenu::new(evid, &RowType::Video, &redactable, None, None);
+ menu_button.set_popover(Some(&menu.get_popover()));
+
+ bx.pack_start(&overlay, true, true, 0);
+ self.connect_media_viewer(msg);
+ self.video_player = Some(player);
+ }
- bx.pack_start(&overlay, true, true, 0);
- self.connect_media_viewer(msg);
- self.video_player = Some(player);
bx
}
diff --git a/fractal-gtk/src/widgets/room_history.rs b/fractal-gtk/src/widgets/room_history.rs
index bcfd5f4a..6c1c68e9 100644
--- a/fractal-gtk/src/widgets/room_history.rs
+++ b/fractal-gtk/src/widgets/room_history.rs
@@ -7,19 +7,17 @@ use log::warn;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
-use std::sync::{Arc, Mutex};
-use crate::appop::AppOp;
+use crate::appop::{AppOp, UserInfoCache};
use crate::i18n::i18n;
use crate::uitypes::MessageContent;
use crate::uitypes::RowType;
use crate::backend::ThreadPool;
-use crate::cache::CacheMap;
use crate::globals;
use crate::widgets;
use crate::widgets::{PlayerExt, VideoPlayerWidget};
-use fractal_api::identifiers::{RoomId, UserId};
+use fractal_api::identifiers::RoomId;
use fractal_api::r0::AccessToken;
use fractal_api::url::Url;
use gio::ActionMapExt;
@@ -305,7 +303,7 @@ impl RoomHistory {
pub fn create(
&mut self,
thread_pool: ThreadPool,
- user_info_cache: Arc<Mutex<CacheMap<UserId, (String, String)>>>,
+ user_info_cache: UserInfoCache,
mut messages: Vec<MessageContent>,
) -> Option<()> {
let mut position = messages.len();
@@ -435,11 +433,7 @@ impl RoomHistory {
}));
}
- fn run_queue(
- &mut self,
- thread_pool: ThreadPool,
- user_info_cache: Arc<Mutex<CacheMap<UserId, (String, String)>>>,
- ) -> Option<()> {
+ fn run_queue(&mut self, thread_pool: ThreadPool, user_info_cache: UserInfoCache) -> Option<()> {
let queue = self.queue.clone();
let edit_buffer = self.edit_buffer.clone();
let rows = self.rows.clone();
@@ -549,7 +543,7 @@ impl RoomHistory {
pub fn add_new_message(
&mut self,
thread_pool: ThreadPool,
- user_info_cache: Arc<Mutex<CacheMap<UserId, (String, String)>>>,
+ user_info_cache: UserInfoCache,
mut item: MessageContent,
) -> Option<()> {
if item.msg.replace.is_some() {
@@ -602,7 +596,7 @@ impl RoomHistory {
pub fn replace_message(
&mut self,
thread_pool: ThreadPool,
- user_info_cache: Arc<Mutex<CacheMap<UserId, (String, String)>>>,
+ user_info_cache: UserInfoCache,
mut item: MessageContent,
) -> Option<()> {
let mut rows = self.rows.borrow_mut();
@@ -639,7 +633,7 @@ impl RoomHistory {
pub fn remove_message(
&mut self,
thread_pool: ThreadPool,
- user_info_cache: Arc<Mutex<CacheMap<UserId, (String, String)>>>,
+ user_info_cache: UserInfoCache,
item: MessageContent,
) -> Option<()> {
let mut rows = self.rows.borrow_mut();
@@ -693,7 +687,7 @@ impl RoomHistory {
pub fn add_new_messages_in_batch(
&mut self,
thread_pool: ThreadPool,
- user_info_cache: Arc<Mutex<CacheMap<UserId, (String, String)>>>,
+ user_info_cache: UserInfoCache,
messages: Vec<MessageContent>,
) -> Option<()> {
/* TODO: use lazy loading */
@@ -706,7 +700,7 @@ impl RoomHistory {
pub fn add_old_messages_in_batch(
&mut self,
thread_pool: ThreadPool,
- user_info_cache: Arc<Mutex<CacheMap<UserId, (String, String)>>>,
+ user_info_cache: UserInfoCache,
messages: Vec<MessageContent>,
) -> Option<()> {
self.rows.borrow().view.reset_request_sent();
@@ -737,7 +731,7 @@ impl RoomHistory {
/* This function creates the content for a Row based on the content of msg */
fn create_row(
thread_pool: ThreadPool,
- user_info_cache: Arc<Mutex<CacheMap<UserId, (String, String)>>>,
+ user_info_cache: UserInfoCache,
row: MessageContent,
has_header: bool,
server_url: Url,
diff --git a/fractal-gtk/src/widgets/roomlist.rs b/fractal-gtk/src/widgets/roomlist.rs
index 1a9fee5c..0ed74bfd 100644
--- a/fractal-gtk/src/widgets/roomlist.rs
+++ b/fractal-gtk/src/widgets/roomlist.rs
@@ -225,7 +225,7 @@ impl RoomListGroup {
});
}
- pub fn set_room_avatar(&mut self, room_id: RoomId, av: Option<String>) {
+ pub fn set_room_avatar(&mut self, room_id: RoomId, av: Option<Url>) {
if let Some(r) = self.rooms.get_mut(&room_id) {
r.set_avatar(av.clone());
}
@@ -635,7 +635,7 @@ impl RoomList {
});
}
- pub fn set_room_avatar(&mut self, room_id: RoomId, av: Option<String>) {
+ pub fn set_room_avatar(&mut self, room_id: RoomId, av: Option<Url>) {
run_in_group!(self, &room_id, set_room_avatar, room_id, av);
}
diff --git a/fractal-gtk/src/widgets/roomrow.rs b/fractal-gtk/src/widgets/roomrow.rs
index 444de44d..dcd0315e 100644
--- a/fractal-gtk/src/widgets/roomrow.rs
+++ b/fractal-gtk/src/widgets/roomrow.rs
@@ -1,3 +1,4 @@
+use fractal_api::url::Url;
use gtk::prelude::*;
use crate::types::Room;
@@ -116,7 +117,7 @@ impl RoomRow {
self.text.set_text(&name);
}
- pub fn set_avatar(&mut self, avatar: Option<String>) {
+ pub fn set_avatar(&mut self, avatar: Option<Url>) {
self.room.avatar = avatar;
let name = self.room.name.clone().unwrap_or_else(|| "...".to_string());
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]