fractal/utils/
mod.rs

1//! Collection of common methods and types.
2
3mod dummy_object;
4pub mod expression;
5mod expression_list_model;
6pub(crate) mod key_bindings;
7mod location;
8pub mod macros;
9pub mod matrix;
10pub mod media;
11pub mod notifications;
12pub(crate) mod oauth;
13mod single_item_list_model;
14pub mod sourceview;
15pub mod string;
16pub mod template_callbacks;
17
18use std::{
19    cell::{Cell, OnceCell, RefCell},
20    fmt, fs,
21    io::{self, Write},
22    path::{Path, PathBuf},
23    rc::{Rc, Weak},
24    sync::{Arc, LazyLock},
25};
26
27use futures_util::{
28    future::{self, Either, Future},
29    pin_mut,
30};
31use gtk::{gio, glib, prelude::*};
32use regex::Regex;
33use tempfile::NamedTempFile;
34
35pub(crate) use self::{
36    dummy_object::DummyObject,
37    expression_list_model::ExpressionListModel,
38    location::{Location, LocationError, LocationExt},
39    single_item_list_model::SingleItemListModel,
40};
41use crate::{PROFILE, RUNTIME};
42
43/// The path of the directory where data should be stored, depending on its
44/// type.
45pub fn data_dir_path(data_type: DataType) -> PathBuf {
46    let mut path = match data_type {
47        DataType::Persistent => glib::user_data_dir(),
48        DataType::Cache => glib::user_cache_dir(),
49    };
50    path.push(PROFILE.dir_name().as_ref());
51
52    path
53}
54
55/// The type of data.
56#[derive(Debug, Clone, Copy)]
57pub enum DataType {
58    /// Data that should not be deleted.
59    Persistent,
60    /// Cache that can be deleted freely.
61    Cache,
62}
63
64pub enum TimeoutFuture {
65    Timeout,
66}
67
68/// Executes the given future with the given timeout.
69///
70/// If the future didn't resolve before the timeout was reached, this returns
71/// an `Err(TimeoutFuture)`.
72pub async fn timeout_future<T>(
73    timeout: std::time::Duration,
74    fut: impl Future<Output = T>,
75) -> Result<T, TimeoutFuture> {
76    let timeout = glib::timeout_future(timeout);
77    pin_mut!(fut);
78
79    match future::select(fut, timeout).await {
80        Either::Left((x, _)) => Ok(x),
81        Either::Right(_) => Err(TimeoutFuture::Timeout),
82    }
83}
84
85/// Replace variables in the given string with the given dictionary.
86///
87/// The expected format to replace is `{name}`, where `name` is the first string
88/// in the dictionary entry tuple.
89pub fn freplace(s: String, args: &[(&str, &str)]) -> String {
90    let mut s = s;
91
92    for (k, v) in args {
93        s = s.replace(&format!("{{{k}}}"), v);
94    }
95
96    s
97}
98
99/// Regex that matches a string that only includes emojis.
100pub static EMOJI_REGEX: LazyLock<Regex> = LazyLock::new(|| {
101    Regex::new(
102        r"(?x)
103        ^
104        [\p{White_Space}\p{Emoji_Component}]*
105        [\p{Emoji}--\p{Decimal_Number}]+
106        [\p{White_Space}\p{Emoji}\p{Emoji_Component}--\p{Decimal_Number}]*
107        $
108        # That string is made of at least one emoji, except digits, possibly more,
109        # possibly with modifiers, possibly with spaces, but nothing else
110        ",
111    )
112    .unwrap()
113});
114
115/// Inner to manage a bound object.
116#[derive(Debug)]
117pub struct BoundObjectInner<T: ObjectType> {
118    obj: T,
119    signal_handler_ids: Vec<glib::SignalHandlerId>,
120}
121
122/// Wrapper to manage a bound object.
123///
124/// This keeps a strong reference to the object.
125#[derive(Debug)]
126pub struct BoundObject<T: ObjectType> {
127    inner: RefCell<Option<BoundObjectInner<T>>>,
128}
129
130impl<T: ObjectType> BoundObject<T> {
131    /// Creates a new empty `BoundObject`.
132    pub fn new() -> Self {
133        Self::default()
134    }
135
136    /// Set the given object and signal handlers IDs.
137    ///
138    /// Calls `disconnect_signals` first to drop the previous strong reference
139    /// and disconnect the previous signal handlers.
140    pub fn set(&self, obj: T, signal_handler_ids: Vec<glib::SignalHandlerId>) {
141        self.disconnect_signals();
142
143        let inner = BoundObjectInner {
144            obj,
145            signal_handler_ids,
146        };
147
148        self.inner.replace(Some(inner));
149    }
150
151    /// Get the object, if any.
152    pub fn obj(&self) -> Option<T> {
153        self.inner.borrow().as_ref().map(|inner| inner.obj.clone())
154    }
155
156    /// Disconnect the signal handlers and drop the strong reference.
157    pub fn disconnect_signals(&self) {
158        if let Some(inner) = self.inner.take() {
159            for signal_handler_id in inner.signal_handler_ids {
160                inner.obj.disconnect(signal_handler_id);
161            }
162        }
163    }
164}
165
166impl<T: ObjectType> Default for BoundObject<T> {
167    fn default() -> Self {
168        Self {
169            inner: Default::default(),
170        }
171    }
172}
173
174impl<T: ObjectType> Drop for BoundObject<T> {
175    fn drop(&mut self) {
176        self.disconnect_signals();
177    }
178}
179
180impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::property::Property for BoundObject<T> {
181    type Value = Option<T>;
182}
183
184impl<T: IsA<glib::Object>> glib::property::PropertyGet for BoundObject<T> {
185    type Value = Option<T>;
186
187    fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
188        f(&self.obj())
189    }
190}
191
192/// Wrapper to manage a bound object.
193///
194/// This keeps a weak reference to the object.
195#[derive(Debug)]
196pub struct BoundObjectWeakRef<T: ObjectType> {
197    weak_obj: glib::WeakRef<T>,
198    signal_handler_ids: RefCell<Vec<glib::SignalHandlerId>>,
199}
200
201impl<T: ObjectType> BoundObjectWeakRef<T> {
202    /// Creates a new empty `BoundObjectWeakRef`.
203    pub fn new() -> Self {
204        Self::default()
205    }
206
207    /// Set the given object and signal handlers IDs.
208    ///
209    /// Calls `disconnect_signals` first to remove the previous weak reference
210    /// and disconnect the previous signal handlers.
211    pub fn set(&self, obj: &T, signal_handler_ids: Vec<glib::SignalHandlerId>) {
212        self.disconnect_signals();
213
214        self.weak_obj.set(Some(obj));
215        self.signal_handler_ids.replace(signal_handler_ids);
216    }
217
218    /// Get a strong reference to the object.
219    pub fn obj(&self) -> Option<T> {
220        self.weak_obj.upgrade()
221    }
222
223    /// Disconnect the signal handlers and drop the weak reference.
224    pub fn disconnect_signals(&self) {
225        let signal_handler_ids = self.signal_handler_ids.take();
226
227        if let Some(obj) = self.weak_obj.upgrade() {
228            for signal_handler_id in signal_handler_ids {
229                obj.disconnect(signal_handler_id);
230            }
231        }
232
233        self.weak_obj.set(None);
234    }
235}
236
237impl<T: ObjectType> Default for BoundObjectWeakRef<T> {
238    fn default() -> Self {
239        Self {
240            weak_obj: Default::default(),
241            signal_handler_ids: Default::default(),
242        }
243    }
244}
245
246impl<T: ObjectType> Drop for BoundObjectWeakRef<T> {
247    fn drop(&mut self) {
248        self.disconnect_signals();
249    }
250}
251
252impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::property::Property for BoundObjectWeakRef<T> {
253    type Value = Option<T>;
254}
255
256impl<T: IsA<glib::Object>> glib::property::PropertyGet for BoundObjectWeakRef<T> {
257    type Value = Option<T>;
258
259    fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
260        f(&self.obj())
261    }
262}
263
264/// Wrapper to manage a bound construct-only object.
265///
266/// This keeps a strong reference to the object.
267#[derive(Debug)]
268pub struct BoundConstructOnlyObject<T: ObjectType> {
269    obj: OnceCell<T>,
270    signal_handler_ids: RefCell<Vec<glib::SignalHandlerId>>,
271}
272
273impl<T: ObjectType> BoundConstructOnlyObject<T> {
274    /// Creates a new empty `BoundConstructOnlyObject`.
275    pub fn new() -> Self {
276        Self::default()
277    }
278
279    /// Set the given object and signal handlers IDs.
280    ///
281    /// Panics if the object was already set.
282    pub fn set(&self, obj: T, signal_handler_ids: Vec<glib::SignalHandlerId>) {
283        self.obj.set(obj).unwrap();
284        self.signal_handler_ids.replace(signal_handler_ids);
285    }
286
287    /// Get a strong reference to the object.
288    ///
289    /// Panics if the object has not been set yet.
290    pub fn obj(&self) -> &T {
291        self.obj.get().unwrap()
292    }
293}
294
295impl<T: ObjectType> Default for BoundConstructOnlyObject<T> {
296    fn default() -> Self {
297        Self {
298            obj: Default::default(),
299            signal_handler_ids: Default::default(),
300        }
301    }
302}
303
304impl<T: ObjectType> Drop for BoundConstructOnlyObject<T> {
305    fn drop(&mut self) {
306        let signal_handler_ids = self.signal_handler_ids.take();
307
308        if let Some(obj) = self.obj.get() {
309            for signal_handler_id in signal_handler_ids {
310                obj.disconnect(signal_handler_id);
311            }
312        }
313    }
314}
315
316impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::property::Property
317    for BoundConstructOnlyObject<T>
318{
319    type Value = T;
320}
321
322impl<T: IsA<glib::Object>> glib::property::PropertyGet for BoundConstructOnlyObject<T> {
323    type Value = T;
324
325    fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
326        f(self.obj())
327    }
328}
329
330/// Helper type to keep track of ongoing async actions that can succeed in
331/// different functions.
332///
333/// This type can only have one strong reference and many weak references.
334///
335/// The strong reference should be dropped in the first function where the
336/// action succeeds. Then other functions can drop the weak references when
337/// they can't be upgraded.
338#[derive(Debug)]
339pub struct OngoingAsyncAction<T> {
340    strong: Rc<AsyncAction<T>>,
341}
342
343impl<T> OngoingAsyncAction<T> {
344    /// Create a new async action that sets the given value.
345    ///
346    /// Returns both a strong and a weak reference.
347    pub fn set(value: T) -> (Self, WeakOngoingAsyncAction<T>) {
348        let strong = Rc::new(AsyncAction::Set(value));
349        let weak = Rc::downgrade(&strong);
350        (Self { strong }, WeakOngoingAsyncAction { weak })
351    }
352
353    /// Create a new async action that removes a value.
354    ///
355    /// Returns both a strong and a weak reference.
356    pub fn remove() -> (Self, WeakOngoingAsyncAction<T>) {
357        let strong = Rc::new(AsyncAction::Remove);
358        let weak = Rc::downgrade(&strong);
359        (Self { strong }, WeakOngoingAsyncAction { weak })
360    }
361
362    /// Create a new weak reference to this async action.
363    pub fn downgrade(&self) -> WeakOngoingAsyncAction<T> {
364        let weak = Rc::downgrade(&self.strong);
365        WeakOngoingAsyncAction { weak }
366    }
367
368    /// The inner action.
369    pub fn action(&self) -> &AsyncAction<T> {
370        &self.strong
371    }
372
373    /// Get the inner value, if any.
374    pub fn as_value(&self) -> Option<&T> {
375        self.strong.as_value()
376    }
377}
378
379/// A weak reference to an `OngoingAsyncAction`.
380#[derive(Debug, Clone)]
381pub struct WeakOngoingAsyncAction<T> {
382    weak: Weak<AsyncAction<T>>,
383}
384
385impl<T> WeakOngoingAsyncAction<T> {
386    /// Whether this async action is still ongoing (i.e. whether the strong
387    /// reference still exists).
388    pub fn is_ongoing(&self) -> bool {
389        self.weak.strong_count() > 0
390    }
391}
392
393/// An async action.
394#[derive(Debug, Clone, PartialEq, Eq)]
395pub enum AsyncAction<T> {
396    /// An async action is ongoing to set this value.
397    Set(T),
398
399    /// An async action is ongoing to remove a value.
400    Remove,
401}
402
403impl<T> AsyncAction<T> {
404    /// Get the inner value, if any.
405    pub fn as_value(&self) -> Option<&T> {
406        match self {
407            Self::Set(value) => Some(value),
408            Self::Remove => None,
409        }
410    }
411}
412
413/// A type that requires the tokio runtime to be running when dropped.
414///
415/// This is basically usable as a [`OnceCell`].
416#[derive(Debug, Clone)]
417pub struct TokioDrop<T>(OnceCell<T>);
418
419impl<T> TokioDrop<T> {
420    /// Create a new empty `TokioDrop`;
421    pub fn new() -> Self {
422        Self::default()
423    }
424
425    /// Gets a reference to the underlying value.
426    ///
427    /// Returns `None` if the cell is empty.
428    pub fn get(&self) -> Option<&T> {
429        self.0.get()
430    }
431
432    /// Sets the contents of this cell to `value`.
433    ///
434    /// Returns `Ok(())` if the cell was empty and `Err(value)` if it was full.
435    pub fn set(&self, value: T) -> Result<(), T> {
436        self.0.set(value)
437    }
438}
439
440impl<T> Default for TokioDrop<T> {
441    fn default() -> Self {
442        Self(Default::default())
443    }
444}
445
446impl<T> Drop for TokioDrop<T> {
447    fn drop(&mut self) {
448        let _guard = RUNTIME.enter();
449
450        if let Some(inner) = self.0.take() {
451            drop(inner);
452        }
453    }
454}
455
456impl<T: glib::property::Property> glib::property::Property for TokioDrop<T> {
457    type Value = T::Value;
458}
459
460impl<T> glib::property::PropertyGet for TokioDrop<T> {
461    type Value = T;
462
463    fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
464        f(self.get().unwrap())
465    }
466}
467
468impl<T> glib::property::PropertySet for TokioDrop<T> {
469    type SetValue = T;
470
471    fn set(&self, v: Self::SetValue) {
472        assert!(
473            self.set(v).is_ok(),
474            "TokioDrop value was already initialized"
475        );
476    }
477}
478
479/// The state of a resource that can be loaded.
480#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, glib::Enum)]
481#[enum_type(name = "LoadingState")]
482pub enum LoadingState {
483    /// It hasn't been loaded yet.
484    #[default]
485    Initial,
486    /// It is currently loading.
487    Loading,
488    /// It has been fully loaded.
489    Ready,
490    /// An error occurred while loading it.
491    Error,
492}
493
494/// Convert the given checked `bool` to a `GtkAccessibleTristate`.
495pub fn bool_to_accessible_tristate(checked: bool) -> gtk::AccessibleTristate {
496    if checked {
497        gtk::AccessibleTristate::True
498    } else {
499        gtk::AccessibleTristate::False
500    }
501}
502
503/// A wrapper around several sources of files.
504#[derive(Debug, Clone)]
505pub enum File {
506    /// A `GFile`.
507    Gio(gio::File),
508    /// A temporary file.
509    ///
510    /// When all strong references to this file are destroyed, the file will be
511    /// destroyed too.
512    Temp(Arc<NamedTempFile>),
513}
514
515impl File {
516    /// The path to the file.
517    pub(crate) fn path(&self) -> Option<PathBuf> {
518        match self {
519            Self::Gio(file) => file.path(),
520            Self::Temp(file) => Some(file.path().to_owned()),
521        }
522    }
523
524    /// Get a `GFile` for this file.
525    pub(crate) fn as_gfile(&self) -> gio::File {
526        match self {
527            Self::Gio(file) => file.clone(),
528            Self::Temp(file) => gio::File::for_path(file.path()),
529        }
530    }
531}
532
533impl From<gio::File> for File {
534    fn from(value: gio::File) -> Self {
535        Self::Gio(value)
536    }
537}
538
539impl From<NamedTempFile> for File {
540    fn from(value: NamedTempFile) -> Self {
541        Self::Temp(value.into())
542    }
543}
544
545/// The directory where to put temporary files.
546static TMP_DIR: LazyLock<Box<Path>> = LazyLock::new(|| {
547    let mut dir = glib::user_runtime_dir();
548    dir.push(PROFILE.dir_name().as_ref());
549    dir.into_boxed_path()
550});
551
552/// Save the given data to a temporary file.
553///
554/// When all strong references to the returned file are destroyed, the file will
555/// be destroyed too.
556pub(crate) async fn save_data_to_tmp_file(data: Vec<u8>) -> Result<File, std::io::Error> {
557    RUNTIME
558        .spawn_blocking(move || {
559            let dir = TMP_DIR.as_ref();
560            if !dir.exists() {
561                if let Err(error) = fs::create_dir(dir) {
562                    if !matches!(error.kind(), io::ErrorKind::AlreadyExists) {
563                        return Err(error);
564                    }
565                }
566            }
567            let mut file = NamedTempFile::new_in(dir)?;
568            file.write_all(&data)?;
569
570            Ok(file.into())
571        })
572        .await
573        .expect("task was not aborted")
574}
575
576/// A counted reference.
577///
578/// Can be used to perform some actions when the count is 0 or non-zero.
579pub struct CountedRef(Rc<InnerCountedRef>);
580
581struct InnerCountedRef {
582    /// The count of the reference
583    count: Cell<usize>,
584    /// The function to call when the count decreases to zero.
585    on_zero: Box<dyn Fn()>,
586    /// The function to call when the count increases from zero.
587    on_non_zero: Box<dyn Fn()>,
588}
589
590impl CountedRef {
591    /// Construct a counted reference.
592    pub fn new<F1, F2>(on_zero: F1, on_non_zero: F2) -> Self
593    where
594        F1: Fn() + 'static,
595        F2: Fn() + 'static,
596    {
597        Self(
598            InnerCountedRef {
599                count: Default::default(),
600                on_zero: Box::new(on_zero),
601                on_non_zero: Box::new(on_non_zero),
602            }
603            .into(),
604        )
605    }
606
607    /// The current count of the reference.
608    pub fn count(&self) -> usize {
609        self.0.count.get()
610    }
611}
612
613impl fmt::Debug for CountedRef {
614    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
615        f.debug_struct("CountedRef")
616            .field("count", &self.count())
617            .finish_non_exhaustive()
618    }
619}
620
621impl Clone for CountedRef {
622    fn clone(&self) -> Self {
623        let count = self.count();
624        self.0.count.set(count.saturating_add(1));
625
626        if count == 0 {
627            (self.0.on_non_zero)();
628        }
629
630        Self(self.0.clone())
631    }
632}
633
634impl Drop for CountedRef {
635    fn drop(&mut self) {
636        let count = self.count();
637        self.0.count.set(count.saturating_sub(1));
638
639        if count == 1 {
640            (self.0.on_zero)();
641        }
642    }
643}