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
}
}