dittolive_ditto/disk_usage/mod.rs
1//! Use [`ditto.disk_usage()`] to access the [`DiskUsage`] API to inspect Ditto's disk usage.
2//!
3//! # Monitor disk usage on a device
4//!
5//! In order to persist data on a device, Ditto can create a local database in a folder specified
6//! with [`.with_root(...)`].
7//!
8//! To monitor this folder size, [`DiskUsage`] offers two methods:
9//! - [`.exec()`] to retrieve an immediate `DiskUsageTree` and
10//! - [`.observe(...)`] to call a callback every time changes occur in the Ditto folder.
11//!
12//! [`ditto.disk_usage()`]: crate::Ditto::disk_usage
13//! [`.with_root(...)`]: crate::DittoBuilder::with_root
14//! [`.exec()`]: DiskUsage::exec
15//! [`.observe(...)`]: DiskUsage::observe
16
17use_prelude!();
18use core::ffi::c_void;
19use std::sync::{Arc, Mutex, Weak};
20
21use ffi_sdk::{BoxedDitto, FsComponent};
22use serde::Deserialize;
23
24#[repr(C)]
25#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
26/// Existing type in most file system.
27pub enum FileSystemType {
28 /// Item is a directory
29 Directory = 0,
30
31 /// Item is a standard file
32 File,
33
34 /// Item is a symlink
35 SymLink,
36}
37
38#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
39/// Entry in a file system
40pub struct DiskUsageChild {
41 /// Type of the entry.
42 pub fs_type: FileSystemType,
43 /// Relative path in the [`DittoRoot`].
44 pub path: String,
45 /// Size of the entry.
46 pub size_in_bytes: usize,
47 /// If the entry is a directory, it can contain children.
48 pub children: Option<Vec<DiskUsageChild>>,
49}
50
51/// Handle returned by `DiskUsage::observe`.
52/// Keep it in scope as long you want to receive updates on a given Ditto Subdirectory.
53pub struct DiskUsageObserverCtx<F: ?Sized + DiskUsageCallback> {
54 // Strong pointer to the inner callback
55 // This will release the callback when dropped
56 handle: Option<repr_c::Box<ffi_sdk::DiskUsageObserver>>,
57 // callback
58 on_update: F,
59}
60
61#[doc(hidden)]
62#[deprecated(note = "This is now named `DiskUsageObserverCtx`")]
63pub type DiskUsageObserver = DiskUsageObserverCtx<dyn DiskUsageCallback>;
64
65trait_alias! {
66 /// Callback bounds for the [`DiskUsage`] [`.observe(...)`] handler.
67 ///
68 /// [`.observe(...)`]: DiskUsage::observe
69 pub trait DiskUsageCallback = 'static + Send + FnMut(DiskUsageChild)
70}
71
72/// Alias for the observer type returned by [`.observe(...)`].
73///
74/// [`.observe(...)`]: DiskUsage::observe
75#[allow(type_alias_bounds)]
76pub type DiskUsageObserverHandle<F: ?Sized + DiskUsageCallback = dyn DiskUsageCallback> =
77 Mutex<DiskUsageObserverCtx<F>>;
78
79const _ASSERT_IMPLS: &(dyn Send + Sync) = &::core::marker::PhantomData::<DiskUsageObserverHandle>;
80
81impl<F: DiskUsageCallback> DiskUsageObserverCtx<F> {
82 fn set_handle(&mut self, handle: repr_c::Box<ffi_sdk::DiskUsageObserver>) {
83 self.handle = Some(handle);
84 }
85
86 /// `ctx` is, conceptually-speaking a `&'short_lived Weak< DiskUsageObserverCtx<F> >`.
87 ///
88 /// This scoped/callback API embodies that.
89 #[track_caller]
90 unsafe fn borrowing_from_ctx(
91 ctx: *const c_void,
92 yielding: impl FnOnce(&Weak<DiskUsageObserverHandle<F>>),
93 ) {
94 let weak_ctx = ::core::mem::ManuallyDrop::new(Weak::from_raw(
95 ctx.cast::<DiskUsageObserverHandle<F>>(),
96 ));
97 yielding(&weak_ctx)
98 }
99
100 unsafe extern "C" fn on_event(ctx: *mut c_void, cbor: c_slice::Ref<'_, u8>) {
101 Self::borrowing_from_ctx(ctx, |weak_ctx| {
102 if let Some(strong_ctx) = weak_ctx.upgrade() {
103 let tree = ::serde_cbor::from_slice(cbor.as_slice()).unwrap();
104 (strong_ctx.lock().unwrap().on_update)(tree);
105 }
106 })
107 }
108
109 unsafe extern "C" fn retain(ctx: *mut c_void) {
110 // No `Weak::increment_weak_count()`, so we do it ourselves.
111 Self::borrowing_from_ctx(ctx, |weak_ctx| {
112 _ = Weak::into_raw(weak_ctx.clone());
113 });
114 }
115
116 unsafe extern "C" fn release(ctx: *mut c_void) {
117 // No `Weak::decrement_weak_count()`, so we do it ourselves.
118 drop(Weak::from_raw(ctx.cast::<DiskUsageObserverHandle<F>>()));
119 }
120}
121
122/// Used to monitor the disk usage of Ditto or of its sub-components.
123pub struct DiskUsage {
124 ditto: Arc<BoxedDitto>,
125 component: FsComponent,
126}
127
128impl DiskUsage {
129 /// Create a new DiskUsage for the given component.
130 pub(crate) fn new(ditto: Arc<BoxedDitto>, component: FsComponent) -> Self {
131 Self { ditto, component }
132 }
133
134 /// Return the tree representation of the ditto disk usage of the component
135 pub fn exec(&self) -> DiskUsageChild {
136 let cval = ffi_sdk::ditto_disk_usage(&self.ditto, self.component);
137 ::serde_cbor::from_slice(cval.as_slice()).unwrap()
138 }
139
140 /// Register a callback to monitor the disk usage of the component
141 pub fn observe<F: DiskUsageCallback>(&self, callback: F) -> Arc<DiskUsageObserverHandle> {
142 let observer: DiskUsageObserverHandle<F> = Mutex::new(DiskUsageObserverCtx::<F> {
143 on_update: callback,
144 handle: None,
145 });
146 let observer: Arc<DiskUsageObserverHandle<F>> = Arc::new(observer);
147
148 // We will be using `Weak`s to get the "legacy" observer life-cycle pattern:
149 // the caller must hold onto this to keep it alive, otherwise the strong references die,
150 // so `DiskUsageObserver` dies, and drops the `Box<ffi_sdk::DiskUsageCbObserver>`, which
151 // unregisters the callback.
152 //
153 // It would have been way less self-referential to have only the `on_event` part be behind
154 // `Arc` indirection, and return a pair `(Arc<OnEvent>,
155 // repr_c::Box<ffi_sdk::DiskUsageCbObserver>)` (properly prettified behind a layer
156 // of `struct` abstraction), since it would have avoided the need for `Weak`s and the _a
157 // posteriori_ `set_handle()`, but our hands are tied until the next breaking version.
158 let weak_observer = Arc::downgrade(&observer);
159
160 let handle = unsafe {
161 ffi_sdk::ditto_register_disk_usage_callback(
162 &self.ditto,
163 self.component,
164 weak_observer.as_ptr() as *mut _,
165 Some(DiskUsageObserverCtx::<F>::retain),
166 Some(DiskUsageObserverCtx::<F>::release),
167 Some(<unsafe extern "C" fn(_, c_slice::Ref<'_, _>)>::into(
168 DiskUsageObserverCtx::<F>::on_event,
169 )),
170 )
171 };
172 // Would happen implicitly, but writing this down avoids mistakes (_e.g._, incorrectly
173 // using `into_raw()` rather than `as_ptr()`).
174 drop(weak_observer);
175
176 observer.lock().unwrap().set_handle(handle.unwrap());
177 observer
178 }
179}