1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! # Monitor disk usage on a device
//!
//! In order to persist data on a device, Ditto can create a local database in a folder specified
//! with [`DittoBuilder::with_root`](crate::prelude::DittoBuilder::with_root).
//!
//! To monitor this folder size, [`DiskUsage`] offers two methods:
//!   - [`exec`](DiskUsage::exec) to retrieve an immediate `DiskUsageTree` and
//!   - [`observe`](DiskUsage::observe) to call a callback every time changes occurs in the Ditto
//!     folder.

use_prelude!();
use core::ffi::c_void;
use std::sync::{Arc, Mutex, Weak};

use ffi_sdk::{BoxedDitto, FsComponent};
use serde::Deserialize;

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
/// Existing type in most file system.
pub enum FileSystemType {
    Directory = 0,
    File,
    SymLink,
}

#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
/// Entry in a file system
pub struct DiskUsageChild {
    /// Type of the entry.
    pub fs_type: FileSystemType,
    /// Relative path in the [`DittoRoot`].
    pub path: String,
    /// Size of the entry.
    pub size_in_bytes: usize,
    /// If the entry is a directory, it can contain children.
    pub children: Option<Vec<DiskUsageChild>>,
}

/// Handle returned by `DiskUsage::observe`.
/// Keep it in scope as long you want to receive updates on a given Ditto Subdirectory.
pub struct DiskUsageObserverCtx<F: ?Sized + DiskUsageCallback> {
    // Strong pointer to the inner callback
    // This will release the callback when dropped
    handle: Option<repr_c::Box<ffi_sdk::DiskUsageObserver>>,
    // callback
    on_update: F,
}

#[doc(hidden)]
#[deprecated(note = "This is now named `DiskUsageObserverCtx`")]
pub type DiskUsageObserver = DiskUsageObserverCtx<dyn DiskUsageCallback>;

trait_alias! {
    pub trait DiskUsageCallback = 'static + Send + FnMut(DiskUsageChild)
}

#[allow(type_alias_bounds)]
pub type DiskUsageObserverHandle<F: ?Sized + DiskUsageCallback = dyn DiskUsageCallback> =
    Mutex<DiskUsageObserverCtx<F>>;

const _ASSERT_IMPLS: &(dyn Send + Sync) = &::core::marker::PhantomData::<DiskUsageObserverHandle>;

impl<F: DiskUsageCallback> DiskUsageObserverCtx<F> {
    fn set_handle(&mut self, handle: repr_c::Box<ffi_sdk::DiskUsageObserver>) {
        self.handle = Some(handle);
    }

    /// `ctx` is, conceptually-speaking a `&'short_lived Weak< DiskUsageObserverCtx<F> >`.
    ///
    /// This scoped/callback API embodies that.
    #[track_caller]
    unsafe fn borrowing_from_ctx(
        ctx: *const c_void,
        yielding: impl FnOnce(&Weak<DiskUsageObserverHandle<F>>),
    ) {
        let weak_ctx = ::core::mem::ManuallyDrop::new(Weak::from_raw(
            ctx.cast::<DiskUsageObserverHandle<F>>(),
        ));
        yielding(&weak_ctx)
    }

    unsafe extern "C" fn on_event(ctx: *mut c_void, cbor: c_slice::Ref<'_, u8>) {
        Self::borrowing_from_ctx(ctx, |weak_ctx| {
            if let Some(strong_ctx) = weak_ctx.upgrade() {
                let tree = ::serde_cbor::from_slice(cbor.as_slice()).unwrap();
                (strong_ctx.lock().unwrap().on_update)(tree);
            }
        })
    }

    unsafe extern "C" fn retain(ctx: *mut c_void) {
        // No `Weak::increment_weak_count()`, so we do it ourselves.
        Self::borrowing_from_ctx(ctx, |weak_ctx| {
            _ = Weak::into_raw(weak_ctx.clone());
        });
    }

    unsafe extern "C" fn release(ctx: *mut c_void) {
        // No `Weak::decrement_weak_count()`, so we do it ourselves.
        drop(Weak::from_raw(ctx.cast::<DiskUsageObserverHandle<F>>()));
    }
}

/// Used to monitor the disk usage of Ditto or of its sub-components.
pub struct DiskUsage {
    ditto: Arc<BoxedDitto>,
    component: FsComponent,
}

impl DiskUsage {
    /// Create a new DiskUsage for the given component.
    pub(crate) fn new(ditto: Arc<BoxedDitto>, component: FsComponent) -> Self {
        Self { ditto, component }
    }

    /// Return the tree representation of the ditto disk usage of the component
    pub fn exec(&self) -> DiskUsageChild {
        let cval = ffi_sdk::ditto_disk_usage(&self.ditto, self.component);
        ::serde_cbor::from_slice(cval.as_slice()).unwrap()
    }

    /// Register a callback to monitor the disk usage of the component
    pub fn observe<F: DiskUsageCallback>(&self, callback: F) -> Arc<DiskUsageObserverHandle> {
        let observer: DiskUsageObserverHandle<F> = Mutex::new(DiskUsageObserverCtx::<F> {
            on_update: callback,
            handle: None,
        });
        let observer: Arc<DiskUsageObserverHandle<F>> = Arc::new(observer);

        // We will be using `Weak`s to get the "legacy" observer life-cycle pattern:
        // the caller must hold onto this to keep it alive, otherwise the strong references die,
        // so `DiskUsageObserver` dies, and drops the `Box<ffi_sdk::DiskUsageCbObserver>`, which
        // unregisters the callback.
        //
        // It would have been way less self-referential to have only the `on_event` part be behind
        // `Arc` indirection, and return a pair `(Arc<OnEvent>,
        // repr_c::Box<ffi_sdk::DiskUsageCbObserver>)` (properly prettified behind a layer
        // of `struct` abstraction), since it would have avoided the need for `Weak`s and the _a
        // posteriori_ `set_handle()`, but our hands are tied until the next breaking version.
        let weak_observer = Arc::downgrade(&observer);

        let handle = unsafe {
            ffi_sdk::ditto_register_disk_usage_callback(
                &self.ditto,
                self.component,
                weak_observer.as_ptr() as *mut _,
                Some(DiskUsageObserverCtx::<F>::retain),
                Some(DiskUsageObserverCtx::<F>::release),
                Some(<unsafe extern "C" fn(_, c_slice::Ref<'_, _>)>::into(
                    DiskUsageObserverCtx::<F>::on_event,
                )),
            )
        };
        // Would happen implicitly, but writing this down avoids mistakes (_e.g._, incorrectly
        // using `into_raw()` rather than `as_ptr()`).
        drop(weak_observer);

        observer.lock().unwrap().set_handle(handle.unwrap());
        observer
    }
}