You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
485 lines
13 KiB
485 lines
13 KiB
//! Collection of common methods and types. |
|
|
|
mod dummy_object; |
|
pub mod expression; |
|
mod expression_list_model; |
|
pub mod macros; |
|
pub mod matrix; |
|
pub mod media; |
|
pub mod message_dialog; |
|
pub mod notifications; |
|
pub mod sourceview; |
|
pub mod template_callbacks; |
|
|
|
use std::{ |
|
cell::{OnceCell, RefCell}, |
|
rc::{Rc, Weak}, |
|
}; |
|
|
|
use futures_util::{ |
|
future::{self, Either, Future}, |
|
pin_mut, |
|
}; |
|
use gtk::{gio, glib, prelude::*}; |
|
use matrix_sdk::ruma::UInt; |
|
use once_cell::sync::Lazy; |
|
use regex::Regex; |
|
use tracing::error; |
|
|
|
pub use self::{dummy_object::DummyObject, expression_list_model::ExpressionListModel}; |
|
use crate::RUNTIME; |
|
|
|
/// Converts a `UInt` to `i32`. |
|
/// |
|
/// Returns `-1` if the conversion didn't work. |
|
pub fn uint_to_i32(u: Option<UInt>) -> i32 { |
|
u.and_then(|ui| { |
|
let u: Option<u16> = ui.try_into().ok(); |
|
u |
|
}) |
|
.map(|u| { |
|
let i: i32 = u.into(); |
|
i |
|
}) |
|
.unwrap_or(-1) |
|
} |
|
|
|
pub enum TimeoutFuture { |
|
Timeout, |
|
} |
|
|
|
/// Executes the given future with the given timeout. |
|
/// |
|
/// If the future didn't resolve before the timeout was reached, this returns |
|
/// an `Err(TimeoutFuture)`. |
|
pub async fn timeout_future<T>( |
|
timeout: std::time::Duration, |
|
fut: impl Future<Output = T>, |
|
) -> Result<T, TimeoutFuture> { |
|
let timeout = glib::timeout_future(timeout); |
|
pin_mut!(fut); |
|
|
|
match future::select(fut, timeout).await { |
|
Either::Left((x, _)) => Ok(x), |
|
_ => Err(TimeoutFuture::Timeout), |
|
} |
|
} |
|
|
|
/// Replace variables in the given string with the given dictionary. |
|
/// |
|
/// The expected format to replace is `{name}`, where `name` is the first string |
|
/// in the dictionary entry tuple. |
|
pub fn freplace(s: String, args: &[(&str, &str)]) -> String { |
|
let mut s = s; |
|
|
|
for (k, v) in args { |
|
s = s.replace(&format!("{{{k}}}"), v); |
|
} |
|
|
|
s |
|
} |
|
|
|
/// Check if the given hostname is reachable. |
|
pub async fn check_if_reachable(hostname: &impl AsRef<str>) -> bool { |
|
let address = gio::NetworkAddress::parse_uri(hostname.as_ref(), 80).unwrap(); |
|
let monitor = gio::NetworkMonitor::default(); |
|
match monitor.can_reach_future(&address).await { |
|
Ok(()) => true, |
|
Err(error) => { |
|
error!("Homeserver {} isn't reachable: {error}", hostname.as_ref()); |
|
false |
|
} |
|
} |
|
} |
|
|
|
/// Regex that matches a string that only includes emojis. |
|
pub static EMOJI_REGEX: Lazy<Regex> = Lazy::new(|| { |
|
Regex::new( |
|
r"(?x) |
|
^ |
|
[\p{White_Space}\p{Emoji_Component}]* |
|
[\p{Emoji}--\p{Decimal_Number}]+ |
|
[\p{White_Space}\p{Emoji}\p{Emoji_Component}--\p{Decimal_Number}]* |
|
$ |
|
# That string is made of at least one emoji, except digits, possibly more, |
|
# possibly with modifiers, possibly with spaces, but nothing else |
|
", |
|
) |
|
.unwrap() |
|
}); |
|
|
|
/// Inner to manage a bound object. |
|
#[derive(Debug)] |
|
pub struct BoundObjectInner<T: glib::ObjectType> { |
|
obj: T, |
|
signal_handler_ids: Vec<glib::SignalHandlerId>, |
|
} |
|
|
|
/// Wrapper to manage a bound object. |
|
/// |
|
/// This keeps a strong reference to the object. |
|
#[derive(Debug)] |
|
pub struct BoundObject<T: glib::ObjectType> { |
|
inner: RefCell<Option<BoundObjectInner<T>>>, |
|
} |
|
|
|
impl<T: glib::ObjectType> BoundObject<T> { |
|
/// Creates a new empty `BoundObject`. |
|
pub fn new() -> Self { |
|
Self::default() |
|
} |
|
|
|
/// Set the given object and signal handlers IDs. |
|
/// |
|
/// Calls `disconnect_signals` first to drop the previous strong reference |
|
/// and disconnect the previous signal handlers. |
|
pub fn set(&self, obj: T, signal_handler_ids: Vec<glib::SignalHandlerId>) { |
|
self.disconnect_signals(); |
|
|
|
let inner = BoundObjectInner { |
|
obj, |
|
signal_handler_ids, |
|
}; |
|
|
|
self.inner.replace(Some(inner)); |
|
} |
|
|
|
/// Get the object, if any. |
|
pub fn obj(&self) -> Option<T> { |
|
self.inner.borrow().as_ref().map(|inner| inner.obj.clone()) |
|
} |
|
|
|
/// Disconnect the signal handlers and drop the strong reference. |
|
pub fn disconnect_signals(&self) { |
|
if let Some(inner) = self.inner.take() { |
|
for signal_handler_id in inner.signal_handler_ids { |
|
inner.obj.disconnect(signal_handler_id) |
|
} |
|
} |
|
} |
|
} |
|
|
|
impl<T: glib::ObjectType> Default for BoundObject<T> { |
|
fn default() -> Self { |
|
Self { |
|
inner: Default::default(), |
|
} |
|
} |
|
} |
|
|
|
impl<T: glib::ObjectType> Drop for BoundObject<T> { |
|
fn drop(&mut self) { |
|
self.disconnect_signals(); |
|
} |
|
} |
|
|
|
impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::Property for BoundObject<T> { |
|
type Value = Option<T>; |
|
} |
|
|
|
impl<T: IsA<glib::Object>> glib::PropertyGet for BoundObject<T> { |
|
type Value = Option<T>; |
|
|
|
fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R { |
|
f(&self.obj()) |
|
} |
|
} |
|
|
|
/// Wrapper to manage a bound object. |
|
/// |
|
/// This keeps a weak reference to the object. |
|
#[derive(Debug)] |
|
pub struct BoundObjectWeakRef<T: glib::ObjectType> { |
|
weak_obj: glib::WeakRef<T>, |
|
signal_handler_ids: RefCell<Vec<glib::SignalHandlerId>>, |
|
} |
|
|
|
impl<T: glib::ObjectType> BoundObjectWeakRef<T> { |
|
/// Creates a new empty `BoundObjectWeakRef`. |
|
pub fn new() -> Self { |
|
Self::default() |
|
} |
|
|
|
/// Set the given object and signal handlers IDs. |
|
/// |
|
/// Calls `disconnect_signals` first to remove the previous weak reference |
|
/// and disconnect the previous signal handlers. |
|
pub fn set(&self, obj: &T, signal_handler_ids: Vec<glib::SignalHandlerId>) { |
|
self.disconnect_signals(); |
|
|
|
self.weak_obj.set(Some(obj)); |
|
self.signal_handler_ids.replace(signal_handler_ids); |
|
} |
|
|
|
/// Get a strong reference to the object. |
|
pub fn obj(&self) -> Option<T> { |
|
self.weak_obj.upgrade() |
|
} |
|
|
|
/// Disconnect the signal handlers and drop the weak reference. |
|
pub fn disconnect_signals(&self) { |
|
let signal_handler_ids = self.signal_handler_ids.take(); |
|
|
|
if let Some(obj) = self.weak_obj.upgrade() { |
|
for signal_handler_id in signal_handler_ids { |
|
obj.disconnect(signal_handler_id) |
|
} |
|
} |
|
|
|
self.weak_obj.set(None); |
|
} |
|
} |
|
|
|
impl<T: glib::ObjectType> Default for BoundObjectWeakRef<T> { |
|
fn default() -> Self { |
|
Self { |
|
weak_obj: Default::default(), |
|
signal_handler_ids: Default::default(), |
|
} |
|
} |
|
} |
|
|
|
impl<T: glib::ObjectType> Drop for BoundObjectWeakRef<T> { |
|
fn drop(&mut self) { |
|
self.disconnect_signals(); |
|
} |
|
} |
|
|
|
impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::Property for BoundObjectWeakRef<T> { |
|
type Value = Option<T>; |
|
} |
|
|
|
impl<T: IsA<glib::Object>> glib::PropertyGet for BoundObjectWeakRef<T> { |
|
type Value = Option<T>; |
|
|
|
fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R { |
|
f(&self.obj()) |
|
} |
|
} |
|
|
|
/// Wrapper to manage a bound construct-only object. |
|
/// |
|
/// This keeps a strong reference to the object. |
|
#[derive(Debug)] |
|
pub struct BoundConstructOnlyObject<T: glib::ObjectType> { |
|
obj: OnceCell<T>, |
|
signal_handler_ids: RefCell<Vec<glib::SignalHandlerId>>, |
|
} |
|
|
|
impl<T: glib::ObjectType> BoundConstructOnlyObject<T> { |
|
/// Creates a new empty `BoundConstructOnlyObject`. |
|
pub fn new() -> Self { |
|
Self::default() |
|
} |
|
|
|
/// Set the given object and signal handlers IDs. |
|
/// |
|
/// Panics if the object was already set. |
|
pub fn set(&self, obj: T, signal_handler_ids: Vec<glib::SignalHandlerId>) { |
|
self.obj.set(obj).unwrap(); |
|
self.signal_handler_ids.replace(signal_handler_ids); |
|
} |
|
|
|
/// Get a strong reference to the object. |
|
/// |
|
/// Panics if the object has not been set yet. |
|
pub fn obj(&self) -> &T { |
|
self.obj.get().unwrap() |
|
} |
|
} |
|
|
|
impl<T: glib::ObjectType> Default for BoundConstructOnlyObject<T> { |
|
fn default() -> Self { |
|
Self { |
|
obj: Default::default(), |
|
signal_handler_ids: Default::default(), |
|
} |
|
} |
|
} |
|
|
|
impl<T: glib::ObjectType> Drop for BoundConstructOnlyObject<T> { |
|
fn drop(&mut self) { |
|
let signal_handler_ids = self.signal_handler_ids.take(); |
|
|
|
if let Some(obj) = self.obj.get() { |
|
for signal_handler_id in signal_handler_ids { |
|
obj.disconnect(signal_handler_id) |
|
} |
|
} |
|
} |
|
} |
|
|
|
impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::Property for BoundConstructOnlyObject<T> { |
|
type Value = T; |
|
} |
|
|
|
impl<T: IsA<glib::Object>> glib::PropertyGet for BoundConstructOnlyObject<T> { |
|
type Value = T; |
|
|
|
fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R { |
|
f(self.obj()) |
|
} |
|
} |
|
|
|
/// Helper type to keep track of ongoing async actions that can succeed in |
|
/// different functions. |
|
/// |
|
/// This type can only have one strong reference and many weak references. |
|
/// |
|
/// The strong reference should be dropped in the first function where the |
|
/// action succeeds. Then other functions can drop the weak references when |
|
/// they can't be upgraded. |
|
#[derive(Debug)] |
|
pub struct OngoingAsyncAction<T> { |
|
strong: Rc<AsyncAction<T>>, |
|
} |
|
|
|
impl<T> OngoingAsyncAction<T> { |
|
/// Create a new async action that sets the given value. |
|
/// |
|
/// Returns both a strong and a weak reference. |
|
pub fn set(value: T) -> (Self, WeakOngoingAsyncAction<T>) { |
|
let strong = Rc::new(AsyncAction::Set(value)); |
|
let weak = Rc::downgrade(&strong); |
|
(Self { strong }, WeakOngoingAsyncAction { weak }) |
|
} |
|
|
|
/// Create a new async action that removes a value. |
|
/// |
|
/// Returns both a strong and a weak reference. |
|
pub fn remove() -> (Self, WeakOngoingAsyncAction<T>) { |
|
let strong = Rc::new(AsyncAction::Remove); |
|
let weak = Rc::downgrade(&strong); |
|
(Self { strong }, WeakOngoingAsyncAction { weak }) |
|
} |
|
|
|
/// Create a new weak reference to this async action. |
|
pub fn downgrade(&self) -> WeakOngoingAsyncAction<T> { |
|
let weak = Rc::downgrade(&self.strong); |
|
WeakOngoingAsyncAction { weak } |
|
} |
|
|
|
/// The inner action. |
|
pub fn action(&self) -> &AsyncAction<T> { |
|
&self.strong |
|
} |
|
|
|
/// Get the inner value, if any. |
|
pub fn as_value(&self) -> Option<&T> { |
|
self.strong.as_value() |
|
} |
|
} |
|
|
|
/// A weak reference to an `OngoingAsyncAction`. |
|
#[derive(Debug, Clone)] |
|
pub struct WeakOngoingAsyncAction<T> { |
|
weak: Weak<AsyncAction<T>>, |
|
} |
|
|
|
impl<T> WeakOngoingAsyncAction<T> { |
|
/// Whether this async action is still ongoing (i.e. whether the strong |
|
/// reference still exists). |
|
pub fn is_ongoing(&self) -> bool { |
|
self.weak.strong_count() > 0 |
|
} |
|
} |
|
|
|
/// An async action. |
|
#[derive(Debug, Clone, PartialEq, Eq)] |
|
pub enum AsyncAction<T> { |
|
/// An async action is ongoing to set this value. |
|
Set(T), |
|
|
|
/// An async action is ongoing to remove a value. |
|
Remove, |
|
} |
|
|
|
impl<T> AsyncAction<T> { |
|
/// Get the inner value, if any. |
|
pub fn as_value(&self) -> Option<&T> { |
|
match self { |
|
Self::Set(value) => Some(value), |
|
_ => None, |
|
} |
|
} |
|
} |
|
|
|
/// A type that requires the tokio runtime to be running when dropped. |
|
/// |
|
/// This is basically usable as a [`OnceCell`]. |
|
#[derive(Debug, Clone)] |
|
pub struct TokioDrop<T>(OnceCell<T>); |
|
|
|
impl<T> TokioDrop<T> { |
|
/// Create a new empty `TokioDrop`; |
|
pub fn new() -> Self { |
|
Self::default() |
|
} |
|
|
|
/// Gets a reference to the underlying value. |
|
/// |
|
/// Returns `None` if the cell is empty. |
|
pub fn get(&self) -> Option<&T> { |
|
self.0.get() |
|
} |
|
|
|
/// Sets the contents of this cell to `value`. |
|
/// |
|
/// Returns `Ok(())` if the cell was empty and `Err(value)` if it was full. |
|
pub fn set(&self, value: T) -> Result<(), T> { |
|
self.0.set(value) |
|
} |
|
} |
|
|
|
impl<T> Default for TokioDrop<T> { |
|
fn default() -> Self { |
|
Self(Default::default()) |
|
} |
|
} |
|
|
|
impl<T> Drop for TokioDrop<T> { |
|
fn drop(&mut self) { |
|
let _guard = RUNTIME.enter(); |
|
|
|
if let Some(inner) = self.0.take() { |
|
drop(inner) |
|
} |
|
} |
|
} |
|
|
|
impl<T: glib::Property> glib::Property for TokioDrop<T> { |
|
type Value = T::Value; |
|
} |
|
|
|
impl<T> glib::PropertyGet for TokioDrop<T> { |
|
type Value = T; |
|
|
|
fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R { |
|
f(self.get().unwrap()) |
|
} |
|
} |
|
|
|
impl<T> glib::PropertySet for TokioDrop<T> { |
|
type SetValue = T; |
|
|
|
fn set(&self, v: Self::SetValue) { |
|
if self.set(v).is_err() { |
|
panic!("TokioDrop value was already set"); |
|
} |
|
} |
|
} |
|
|
|
/// The state of a resource that can be loaded. |
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, glib::Enum)] |
|
#[enum_type(name = "LoadingState")] |
|
pub enum LoadingState { |
|
/// It hasn't been loaded yet. |
|
#[default] |
|
Initial, |
|
/// It is currently loading. |
|
Loading, |
|
/// It has been fully loaded. |
|
Ready, |
|
/// An error occurred while loading it. |
|
Error, |
|
}
|
|
|