fractal/utils/
mod.rs

1//! Collection of common methods and types.
2
3use std::{
4    borrow::Cow,
5    cell::{Cell, OnceCell, RefCell},
6    fmt, fs,
7    io::{self, Write},
8    ops::Deref,
9    path::{Path, PathBuf},
10    rc::{Rc, Weak},
11    sync::{Arc, LazyLock, Mutex},
12};
13
14use adw::prelude::*;
15use futures_channel::oneshot;
16use futures_util::future::BoxFuture;
17use gtk::{gio, glib};
18use regex::Regex;
19use tempfile::NamedTempFile;
20use tokio::task::{AbortHandle, JoinHandle};
21use tracing::error;
22
23pub(crate) mod expression;
24mod expression_list_model;
25mod fixed_selection;
26mod grouping_list_model;
27pub(crate) mod key_bindings;
28mod location;
29mod macros;
30pub(crate) mod matrix;
31pub(crate) mod media;
32pub(crate) mod notifications;
33mod placeholder_object;
34mod single_item_list_model;
35pub(crate) mod sourceview;
36pub(crate) mod string;
37mod template_callbacks;
38pub(crate) mod toast;
39
40pub(crate) use self::{
41    expression_list_model::ExpressionListModel,
42    fixed_selection::FixedSelection,
43    grouping_list_model::*,
44    location::{Location, LocationError, LocationExt},
45    placeholder_object::PlaceholderObject,
46    single_item_list_model::SingleItemListModel,
47    template_callbacks::TemplateCallbacks,
48};
49use crate::{PROFILE, RUNTIME};
50
51/// The type of data.
52#[derive(Debug, Clone, Copy)]
53pub(crate) enum DataType {
54    /// Data that should not be deleted.
55    Persistent,
56    /// Cache that can be deleted freely.
57    Cache,
58}
59
60impl DataType {
61    /// The path of the directory where data should be stored, depending on this
62    /// type.
63    pub(crate) fn dir_path(self) -> PathBuf {
64        let mut path = match self {
65            DataType::Persistent => glib::user_data_dir(),
66            DataType::Cache => glib::user_cache_dir(),
67        };
68        path.push(PROFILE.dir_name().as_ref());
69
70        path
71    }
72}
73
74/// Replace variables in the given string with the given dictionary.
75///
76/// The expected format to replace is `{name}`, where `name` is the first string
77/// in the dictionary entry tuple.
78pub(crate) fn freplace<'a>(s: &'a str, args: &[(&str, &str)]) -> Cow<'a, str> {
79    let mut s = Cow::Borrowed(s);
80
81    for (k, v) in args {
82        s = Cow::Owned(s.replace(&format!("{{{k}}}"), v));
83    }
84
85    s
86}
87
88/// Regex that matches a string that only includes emojis.
89pub(crate) static EMOJI_REGEX: LazyLock<Regex> = LazyLock::new(|| {
90    Regex::new(
91        r"(?x)
92        ^
93        [\p{White_Space}\p{Emoji_Component}]*
94        [\p{Emoji}--\p{Decimal_Number}]+
95        [\p{White_Space}\p{Emoji}\p{Emoji_Component}--\p{Decimal_Number}]*
96        $
97        # That string is made of at least one emoji, except digits, possibly more,
98        # possibly with modifiers, possibly with spaces, but nothing else
99        ",
100    )
101    .unwrap()
102});
103
104/// Inner to manage a bound object.
105#[derive(Debug)]
106struct BoundObjectInner<T: ObjectType> {
107    obj: T,
108    signal_handler_ids: Vec<glib::SignalHandlerId>,
109}
110
111/// Wrapper to manage a bound object.
112///
113/// This keeps a strong reference to the object.
114#[derive(Debug)]
115pub struct BoundObject<T: ObjectType> {
116    inner: RefCell<Option<BoundObjectInner<T>>>,
117}
118
119impl<T: ObjectType> BoundObject<T> {
120    /// Creates a new empty `BoundObject`.
121    pub fn new() -> Self {
122        Self::default()
123    }
124
125    /// Set the given object and signal handlers IDs.
126    ///
127    /// Calls `disconnect_signals` first to drop the previous strong reference
128    /// and disconnect the previous signal handlers.
129    pub(crate) fn set(&self, obj: T, signal_handler_ids: Vec<glib::SignalHandlerId>) {
130        self.disconnect_signals();
131
132        let inner = BoundObjectInner {
133            obj,
134            signal_handler_ids,
135        };
136
137        self.inner.replace(Some(inner));
138    }
139
140    /// Get the object, if any.
141    pub fn obj(&self) -> Option<T> {
142        self.inner.borrow().as_ref().map(|inner| inner.obj.clone())
143    }
144
145    /// Disconnect the signal handlers and drop the strong reference.
146    pub fn disconnect_signals(&self) {
147        if let Some(inner) = self.inner.take() {
148            for signal_handler_id in inner.signal_handler_ids {
149                inner.obj.disconnect(signal_handler_id);
150            }
151        }
152    }
153}
154
155impl<T: ObjectType> Default for BoundObject<T> {
156    fn default() -> Self {
157        Self {
158            inner: Default::default(),
159        }
160    }
161}
162
163impl<T: ObjectType> Drop for BoundObject<T> {
164    fn drop(&mut self) {
165        self.disconnect_signals();
166    }
167}
168
169impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::property::Property for BoundObject<T> {
170    type Value = Option<T>;
171}
172
173impl<T: IsA<glib::Object>> glib::property::PropertyGet for BoundObject<T> {
174    type Value = Option<T>;
175
176    fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
177        f(&self.obj())
178    }
179}
180
181/// Wrapper to manage a bound object.
182///
183/// This keeps a weak reference to the object.
184#[derive(Debug)]
185pub struct BoundObjectWeakRef<T: ObjectType> {
186    weak_obj: glib::WeakRef<T>,
187    signal_handler_ids: RefCell<Vec<glib::SignalHandlerId>>,
188}
189
190impl<T: ObjectType> BoundObjectWeakRef<T> {
191    /// Creates a new empty `BoundObjectWeakRef`.
192    pub fn new() -> Self {
193        Self::default()
194    }
195
196    /// Set the given object and signal handlers IDs.
197    ///
198    /// Calls `disconnect_signals` first to remove the previous weak reference
199    /// and disconnect the previous signal handlers.
200    pub(crate) fn set(&self, obj: &T, signal_handler_ids: Vec<glib::SignalHandlerId>) {
201        self.disconnect_signals();
202
203        self.weak_obj.set(Some(obj));
204        self.signal_handler_ids.replace(signal_handler_ids);
205    }
206
207    /// Get a strong reference to the object.
208    pub fn obj(&self) -> Option<T> {
209        self.weak_obj.upgrade()
210    }
211
212    /// Disconnect the signal handlers and drop the weak reference.
213    pub fn disconnect_signals(&self) {
214        let signal_handler_ids = self.signal_handler_ids.take();
215
216        if let Some(obj) = self.weak_obj.upgrade() {
217            for signal_handler_id in signal_handler_ids {
218                obj.disconnect(signal_handler_id);
219            }
220        }
221
222        self.weak_obj.set(None);
223    }
224}
225
226impl<T: ObjectType> Default for BoundObjectWeakRef<T> {
227    fn default() -> Self {
228        Self {
229            weak_obj: Default::default(),
230            signal_handler_ids: Default::default(),
231        }
232    }
233}
234
235impl<T: ObjectType> Drop for BoundObjectWeakRef<T> {
236    fn drop(&mut self) {
237        self.disconnect_signals();
238    }
239}
240
241impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::property::Property for BoundObjectWeakRef<T> {
242    type Value = Option<T>;
243}
244
245impl<T: IsA<glib::Object>> glib::property::PropertyGet for BoundObjectWeakRef<T> {
246    type Value = Option<T>;
247
248    fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
249        f(&self.obj())
250    }
251}
252
253/// Wrapper to manage a bound construct-only object.
254///
255/// This keeps a strong reference to the object.
256#[derive(Debug)]
257pub struct BoundConstructOnlyObject<T: ObjectType> {
258    obj: OnceCell<T>,
259    signal_handler_ids: RefCell<Vec<glib::SignalHandlerId>>,
260}
261
262impl<T: ObjectType> BoundConstructOnlyObject<T> {
263    /// Creates a new empty `BoundConstructOnlyObject`.
264    pub fn new() -> Self {
265        Self::default()
266    }
267
268    /// Set the given object and signal handlers IDs.
269    ///
270    /// Panics if the object was already set.
271    pub(crate) fn set(&self, obj: T, signal_handler_ids: Vec<glib::SignalHandlerId>) {
272        self.obj.set(obj).unwrap();
273        self.signal_handler_ids.replace(signal_handler_ids);
274    }
275
276    /// Get a strong reference to the object.
277    ///
278    /// Panics if the object has not been set yet.
279    pub fn obj(&self) -> &T {
280        self.obj.get().unwrap()
281    }
282}
283
284impl<T: ObjectType> Default for BoundConstructOnlyObject<T> {
285    fn default() -> Self {
286        Self {
287            obj: Default::default(),
288            signal_handler_ids: Default::default(),
289        }
290    }
291}
292
293impl<T: ObjectType> Drop for BoundConstructOnlyObject<T> {
294    fn drop(&mut self) {
295        let signal_handler_ids = self.signal_handler_ids.take();
296
297        if let Some(obj) = self.obj.get() {
298            for signal_handler_id in signal_handler_ids {
299                obj.disconnect(signal_handler_id);
300            }
301        }
302    }
303}
304
305impl<T: IsA<glib::Object> + glib::HasParamSpec> glib::property::Property
306    for BoundConstructOnlyObject<T>
307{
308    type Value = T;
309}
310
311impl<T: IsA<glib::Object>> glib::property::PropertyGet for BoundConstructOnlyObject<T> {
312    type Value = T;
313
314    fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
315        f(self.obj())
316    }
317}
318
319/// Helper type to keep track of ongoing async actions that can succeed in
320/// different functions.
321///
322/// This type can only have one strong reference and many weak references.
323///
324/// The strong reference should be dropped in the first function where the
325/// action succeeds. Then other functions can drop the weak references when
326/// they can't be upgraded.
327#[derive(Debug)]
328pub struct OngoingAsyncAction<T> {
329    strong: Rc<AsyncAction<T>>,
330}
331
332impl<T> OngoingAsyncAction<T> {
333    /// Create a new async action that sets the given value.
334    ///
335    /// Returns both a strong and a weak reference.
336    pub(crate) fn set(value: T) -> (Self, WeakOngoingAsyncAction<T>) {
337        let strong = Rc::new(AsyncAction::Set(value));
338        let weak = Rc::downgrade(&strong);
339        (Self { strong }, WeakOngoingAsyncAction { weak })
340    }
341
342    /// Create a new async action that removes a value.
343    ///
344    /// Returns both a strong and a weak reference.
345    pub(crate) fn remove() -> (Self, WeakOngoingAsyncAction<T>) {
346        let strong = Rc::new(AsyncAction::Remove);
347        let weak = Rc::downgrade(&strong);
348        (Self { strong }, WeakOngoingAsyncAction { weak })
349    }
350
351    /// Get the inner value, if any.
352    pub(crate) fn as_value(&self) -> Option<&T> {
353        self.strong.as_value()
354    }
355}
356
357/// A weak reference to an `OngoingAsyncAction`.
358#[derive(Debug, Clone)]
359pub struct WeakOngoingAsyncAction<T> {
360    weak: Weak<AsyncAction<T>>,
361}
362
363impl<T> WeakOngoingAsyncAction<T> {
364    /// Whether this async action is still ongoing (i.e. whether the strong
365    /// reference still exists).
366    pub fn is_ongoing(&self) -> bool {
367        self.weak.strong_count() > 0
368    }
369}
370
371/// An async action.
372#[derive(Debug, Clone, PartialEq, Eq)]
373pub(crate) enum AsyncAction<T> {
374    /// An async action is ongoing to set this value.
375    Set(T),
376
377    /// An async action is ongoing to remove a value.
378    Remove,
379}
380
381impl<T> AsyncAction<T> {
382    /// Get the inner value, if any.
383    pub fn as_value(&self) -> Option<&T> {
384        match self {
385            Self::Set(value) => Some(value),
386            Self::Remove => None,
387        }
388    }
389}
390
391/// A wrapper that requires the tokio runtime to be running when dropped.
392#[derive(Debug, Clone)]
393pub struct TokioDrop<T>(Option<T>);
394
395impl<T> TokioDrop<T> {
396    /// Create a new `TokioDrop` wrapping the given type.
397    pub fn new(value: T) -> Self {
398        Self(Some(value))
399    }
400}
401
402impl<T> Deref for TokioDrop<T> {
403    type Target = T;
404
405    fn deref(&self) -> &Self::Target {
406        self.0
407            .as_ref()
408            .expect("TokioDrop should always contain a value")
409    }
410}
411
412impl<T> From<T> for TokioDrop<T> {
413    fn from(value: T) -> Self {
414        Self::new(value)
415    }
416}
417
418impl<T> Drop for TokioDrop<T> {
419    fn drop(&mut self) {
420        let _guard = RUNTIME.enter();
421
422        if let Some(value) = self.0.take() {
423            drop(value);
424        }
425    }
426}
427
428/// The state of a resource that can be loaded.
429#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, glib::Enum)]
430#[enum_type(name = "LoadingState")]
431pub enum LoadingState {
432    /// It hasn't been loaded yet.
433    #[default]
434    Initial,
435    /// It is currently loading.
436    Loading,
437    /// It has been fully loaded.
438    Ready,
439    /// An error occurred while loading it.
440    Error,
441}
442
443/// Convert the given checked `bool` to a `GtkAccessibleTristate`.
444pub(crate) fn bool_to_accessible_tristate(checked: bool) -> gtk::AccessibleTristate {
445    if checked {
446        gtk::AccessibleTristate::True
447    } else {
448        gtk::AccessibleTristate::False
449    }
450}
451
452/// A wrapper around several sources of files.
453#[derive(Debug, Clone)]
454pub enum File {
455    /// A `GFile`.
456    Gio(gio::File),
457    /// A temporary file.
458    ///
459    /// When all strong references to this file are destroyed, the file will be
460    /// destroyed too.
461    Temp(Arc<NamedTempFile>),
462}
463
464impl File {
465    /// The path to the file.
466    pub(crate) fn path(&self) -> Option<PathBuf> {
467        match self {
468            Self::Gio(file) => file.path(),
469            Self::Temp(file) => Some(file.path().to_owned()),
470        }
471    }
472
473    /// Get a `GFile` for this file.
474    pub(crate) fn as_gfile(&self) -> gio::File {
475        match self {
476            Self::Gio(file) => file.clone(),
477            Self::Temp(file) => gio::File::for_path(file.path()),
478        }
479    }
480}
481
482impl From<gio::File> for File {
483    fn from(value: gio::File) -> Self {
484        Self::Gio(value)
485    }
486}
487
488impl From<NamedTempFile> for File {
489    fn from(value: NamedTempFile) -> Self {
490        Self::Temp(value.into())
491    }
492}
493
494/// The directory where to put temporary files.
495static TMP_DIR: LazyLock<Box<Path>> = LazyLock::new(|| {
496    let mut dir = glib::user_runtime_dir();
497    dir.push(PROFILE.dir_name().as_ref());
498    dir.into_boxed_path()
499});
500
501/// Save the given data to a temporary file.
502///
503/// When all strong references to the returned file are destroyed, the file will
504/// be destroyed too.
505pub(crate) async fn save_data_to_tmp_file(data: Vec<u8>) -> Result<File, std::io::Error> {
506    RUNTIME
507        .spawn_blocking(move || {
508            let dir = TMP_DIR.as_ref();
509            if !dir.exists()
510                && let Err(error) = fs::create_dir(dir)
511                && !matches!(error.kind(), io::ErrorKind::AlreadyExists)
512            {
513                return Err(error);
514            }
515
516            let mut file = NamedTempFile::new_in(dir)?;
517            file.write_all(&data)?;
518
519            Ok(file.into())
520        })
521        .await
522        .expect("task was not aborted")
523}
524
525/// A counted reference.
526///
527/// Can be used to perform some actions when the count is 0 or non-zero.
528pub struct CountedRef(Rc<InnerCountedRef>);
529
530struct InnerCountedRef {
531    /// The count of the reference
532    count: Cell<usize>,
533    /// The function to call when the count decreases to zero.
534    on_zero: Box<dyn Fn()>,
535    /// The function to call when the count increases from zero.
536    on_non_zero: Box<dyn Fn()>,
537}
538
539impl CountedRef {
540    /// Construct a counted reference.
541    pub(crate) fn new<F1, F2>(on_zero: F1, on_non_zero: F2) -> Self
542    where
543        F1: Fn() + 'static,
544        F2: Fn() + 'static,
545    {
546        Self(
547            InnerCountedRef {
548                count: Default::default(),
549                on_zero: Box::new(on_zero),
550                on_non_zero: Box::new(on_non_zero),
551            }
552            .into(),
553        )
554    }
555
556    /// The current count of the reference.
557    pub fn count(&self) -> usize {
558        self.0.count.get()
559    }
560}
561
562impl fmt::Debug for CountedRef {
563    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
564        f.debug_struct("CountedRef")
565            .field("count", &self.count())
566            .finish_non_exhaustive()
567    }
568}
569
570impl Clone for CountedRef {
571    fn clone(&self) -> Self {
572        let count = self.count();
573        self.0.count.set(count.saturating_add(1));
574
575        if count == 0 {
576            (self.0.on_non_zero)();
577        }
578
579        Self(self.0.clone())
580    }
581}
582
583impl Drop for CountedRef {
584    fn drop(&mut self) {
585        let count = self.count();
586        self.0.count.set(count.saturating_sub(1));
587
588        if count == 1 {
589            (self.0.on_zero)();
590        }
591    }
592}
593
594/// Extensions trait for types with a `child` property.
595pub(crate) trait ChildPropertyExt {
596    /// The child of this widget, is any.
597    fn child_property(&self) -> Option<gtk::Widget>;
598
599    /// Set the child of this widget.
600    fn set_child_property(&self, child: Option<&impl IsA<gtk::Widget>>);
601
602    /// Get the child if it is of the proper type, or construct it with the
603    /// given function and set is as the child of this widget before returning
604    /// it.
605    fn child_or_else<W>(&self, f: impl FnOnce() -> W) -> W
606    where
607        W: IsA<gtk::Widget>,
608    {
609        if let Some(child) = self.child_property().and_downcast() {
610            child
611        } else {
612            let child = f();
613            self.set_child_property(Some(&child));
614            child
615        }
616    }
617
618    /// Get the child if it is of the proper type, or construct it with its
619    /// `Default` implementation and set is as the child of this widget before
620    /// returning it.
621    fn child_or_default<W>(&self) -> W
622    where
623        W: IsA<gtk::Widget> + Default,
624    {
625        self.child_or_else(Default::default)
626    }
627}
628
629impl<W> ChildPropertyExt for W
630where
631    W: IsABin,
632{
633    fn child_property(&self) -> Option<gtk::Widget> {
634        self.child()
635    }
636
637    fn set_child_property(&self, child: Option<&impl IsA<gtk::Widget>>) {
638        self.set_child(child);
639    }
640}
641
642impl ChildPropertyExt for gtk::ListItem {
643    fn child_property(&self) -> Option<gtk::Widget> {
644        self.child()
645    }
646
647    fn set_child_property(&self, child: Option<&impl IsA<gtk::Widget>>) {
648        self.set_child(child);
649    }
650}
651
652/// Helper trait to implement for widgets that subclass `AdwBin`, to be able to
653/// use the `ChildPropertyExt` trait.
654///
655/// This trait is to circumvent conflicts in Rust's type system, where if we try
656/// to implement `ChildPropertyExt for W where W: IsA<adw::Bin>` it complains
657/// that the other external types that implement `ChildPropertyExt` might
658/// implement `IsA<adw::Bin>` in the future… So instead of reimplementing
659/// `ChildPropertyExt` for every type where we need it, which requires to
660/// implement two methods, we only implement this which requires nothing.
661pub(crate) trait IsABin: IsA<adw::Bin> {}
662
663impl IsABin for adw::Bin {}
664
665/// A wrapper around [`JoinHandle`] that aborts the future if it is dropped
666/// before the task ends.
667///
668/// The main API for this type is [`AbortableHandle::await_task()`].
669#[derive(Debug, Default)]
670pub(crate) struct AbortableHandle {
671    abort_handle: RefCell<Option<AbortHandle>>,
672}
673
674impl AbortableHandle {
675    /// Await the task of the given `JoinHandle`.
676    ///
677    /// Aborts the previous task that was running, if any.
678    ///
679    /// Returns `None` if the task was aborted before completion.
680    pub(crate) async fn await_task<T>(&self, join_handle: JoinHandle<T>) -> Option<T> {
681        self.abort();
682
683        self.abort_handle.replace(Some(join_handle.abort_handle()));
684
685        let result = join_handle.await.ok();
686
687        self.abort_handle.take();
688
689        result
690    }
691
692    /// Abort the current task, if possible.
693    pub(crate) fn abort(&self) {
694        if let Some(abort_handle) = self.abort_handle.take() {
695            abort_handle.abort();
696        }
697    }
698}
699
700impl Drop for AbortableHandle {
701    fn drop(&mut self) {
702        self.abort();
703    }
704}
705
706/// Resample the given slice to the given length, using linear interpolation.
707///
708/// Returns the slice as-is if it is of the correct length. Returns a `Vec` of
709/// zeroes if the slice is empty.
710pub(crate) fn resample_slice(slice: &[f32], new_len: usize) -> Cow<'_, [f32]> {
711    let len = slice.len();
712
713    if len == new_len {
714        // The slice has the correct length, return it.
715        return Cow::Borrowed(slice);
716    }
717
718    if new_len == 0 {
719        // We do not need values, return an empty slice.
720        return Cow::Borrowed(&[]);
721    }
722
723    if len <= 1
724        || slice
725            .iter()
726            .all(|value| (*value - slice[0]).abs() < 0.000_001)
727    {
728        // There is a single value so we do not need to interpolate, return a `Vec`
729        // containing that value.
730        let value = slice.first().copied().unwrap_or_default();
731        return Cow::Owned(std::iter::repeat_n(value, new_len).collect());
732    }
733
734    // We need to interpolate the values.
735    let mut result = Vec::with_capacity(new_len);
736    let ratio = (len - 1) as f32 / (new_len - 1) as f32;
737
738    for i in 0..new_len {
739        let position_abs = i as f32 * ratio;
740        let position_before = position_abs.floor();
741        let position_after = position_abs.ceil();
742        let position_rel = position_abs % 1.0;
743
744        // We are sure that the positions are positive.
745        #[allow(clippy::cast_sign_loss)]
746        let value_before = slice[position_before as usize];
747        #[allow(clippy::cast_sign_loss)]
748        let value_after = slice[(position_after as usize).min(slice.len().saturating_sub(1))];
749
750        let value = (1.0 - position_rel) * value_before + position_rel * value_after;
751        result.push(value);
752    }
753
754    Cow::Owned(result)
755}
756
757/// A helper type to wait for a notification that can occur only one time.
758///
759/// [`OneshotNotifier::listen()`] must be called to initialize it and get a
760/// receiver. The receiver must then be `.await`ed and the future will resolve
761/// when it is notified.
762///
763/// The receiver will receive a signal the first time that
764/// [`OneshotNotifier::notify_value()`] is called. Further calls to this
765/// function will be noops until a new receiver is created.The value to return
766/// must implement `Default`, as this is the value that will be sent to the
767/// receiver when the notifier is dropped before a value is notified.
768///
769/// This notifier can be cloned freely and moved between threads.
770///
771/// It is also possible to share this notifier between tasks to make sure that a
772/// single task is running at a time. If [`OneshotNotifier::listen()`] is called
773/// while there is already a receiver waiting, it will be notified as if the
774/// notifier was dropped.
775#[derive(Debug, Clone)]
776pub(crate) struct OneshotNotifier<T = ()> {
777    /// The context used to identify the notifier in logs.
778    context: &'static str,
779    /// The sender for the notification signal.
780    sender: Arc<Mutex<Option<oneshot::Sender<T>>>>,
781}
782
783impl<T> OneshotNotifier<T> {
784    /// Get a new `OneshotNotifier` for the given context.
785    pub(crate) fn new(context: &'static str) -> Self {
786        Self {
787            sender: Default::default(),
788            context,
789        }
790    }
791}
792
793impl<T> OneshotNotifier<T>
794where
795    T: Default + Send + 'static,
796{
797    /// Initialize this `OneshotNotifier` and get a receiver.
798    pub(crate) fn listen(&self) -> OneshotNotifierReceiver<T> {
799        let (sender, receiver) = oneshot::channel();
800
801        match self.sender.lock() {
802            Ok(mut guard) => *guard = Some(sender),
803            Err(error) => {
804                error!(
805                    context = self.context,
806                    "Failed to lock oneshot notifier: {error}"
807                );
808            }
809        }
810
811        OneshotNotifierReceiver(receiver)
812    }
813
814    /// Notify the receiver with the given value, if any receiver is still
815    /// listening.
816    pub(crate) fn notify_value(&self, value: T) {
817        match self.sender.lock() {
818            Ok(mut guard) => {
819                if let Some(sender) = guard.take() {
820                    let _ = sender.send(value);
821                }
822            }
823            Err(error) => {
824                error!(
825                    context = self.context,
826                    "Failed to lock oneshot notifier: {error}"
827                );
828            }
829        }
830    }
831
832    /// Notify the receiver with the default value, if any receiver is still
833    /// listening.
834    pub(crate) fn notify(&self) {
835        self.notify_value(T::default());
836    }
837}
838
839/// A notification receiver associated to a [`OneshotNotifier`].
840///
841/// This should be `.await`ed to wait for a notification.
842#[derive(Debug)]
843pub(crate) struct OneshotNotifierReceiver<T = ()>(oneshot::Receiver<T>);
844
845impl<T> IntoFuture for OneshotNotifierReceiver<T>
846where
847    T: Default + Send + 'static,
848{
849    type Output = T;
850    type IntoFuture = BoxFuture<'static, Self::Output>;
851
852    fn into_future(self) -> Self::IntoFuture {
853        Box::pin(async move { self.0.await.unwrap_or_default() })
854    }
855}