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//! via [`DittoConfig::persistence_directory`].
7//!
8//! To monitor this folder size, [`DiskUsage`] offers two methods:
9//! - [`.item()`] to retrieve an immediate `DiskUsageItem` tree 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//! [`DittoConfig::persistence_directory`]: crate::ditto::init::DittoConfig::persistence_directory
14//! [`.item()`]: DiskUsage::item
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/// Represents the type of file system entry (directory, file, or symbolic link).
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/// Represents an entry in the file system tree returned by [`DiskUsage`].
40///
41/// This can be a file, a directory, or a symbolic link. For directories,
42/// the [`children`][Self::children] field contains the nested entries.
43pub struct DiskUsageItem {
44 /// Type of the entry.
45 pub fs_type: FileSystemType,
46 /// Relative path in the [`DittoRoot`].
47 pub path: String,
48 /// Size of the entry in bytes. For directories, this includes the size of all children.
49 pub size_in_bytes: usize,
50 /// If the entry is a directory, it contains child items.
51 pub children: Option<Vec<DiskUsageItem>>,
52}
53
54/// Handle returned by [`DiskUsage::observe`].
55///
56/// Keep this observer in scope as long as you want to receive updates on disk usage changes.
57/// Dropping the observer will stop the observation.
58pub struct DiskUsageObserver<F: ?Sized + DiskUsageCallback> {
59 // Strong pointer to the inner callback
60 // This will release the callback when dropped
61 handle: Option<repr_c::Box<ffi_sdk::DiskUsageObserver>>,
62 // callback
63 on_update: F,
64}
65
66trait_alias! {
67 /// Callback bounds for the [`DiskUsage`] [`.observe(...)`] handler.
68 ///
69 /// [`.observe(...)`]: DiskUsage::observe
70 pub trait DiskUsageCallback = 'static + Send + FnMut(DiskUsageItem)
71}
72
73/// Alias for the observer type returned by [`.observe(...)`].
74///
75/// [`.observe(...)`]: DiskUsage::observe
76#[allow(type_alias_bounds)]
77pub type DiskUsageObserverHandle<F: ?Sized + DiskUsageCallback = dyn DiskUsageCallback> =
78 Mutex<DiskUsageObserver<F>>;
79
80const _ASSERT_IMPLS: &(dyn Send + Sync) = &::core::marker::PhantomData::<DiskUsageObserverHandle>;
81
82impl<F: DiskUsageCallback> DiskUsageObserver<F> {
83 fn set_handle(&mut self, handle: repr_c::Box<ffi_sdk::DiskUsageObserver>) {
84 self.handle = Some(handle);
85 }
86
87 /// `ctx` is, conceptually-speaking a `&'short_lived Weak< DiskUsageObserver<F> >`.
88 ///
89 /// This scoped/callback API embodies that.
90 #[track_caller]
91 unsafe fn borrowing_from_ctx(
92 ctx: *const c_void,
93 yielding: impl FnOnce(&Weak<DiskUsageObserverHandle<F>>),
94 ) {
95 let weak_ctx = ::core::mem::ManuallyDrop::new(Weak::from_raw(
96 ctx.cast::<DiskUsageObserverHandle<F>>(),
97 ));
98 yielding(&weak_ctx)
99 }
100
101 unsafe extern "C" fn on_event(ctx: *mut c_void, cbor: c_slice::Ref<'_, u8>) {
102 Self::borrowing_from_ctx(ctx, |weak_ctx| {
103 if let Some(strong_ctx) = weak_ctx.upgrade() {
104 let tree = ::serde_cbor::from_slice(cbor.as_slice()).unwrap();
105 (strong_ctx.lock().unwrap().on_update)(tree);
106 }
107 })
108 }
109
110 unsafe extern "C" fn retain(ctx: *mut c_void) {
111 // No `Weak::increment_weak_count()`, so we do it ourselves.
112 Self::borrowing_from_ctx(ctx, |weak_ctx| {
113 _ = Weak::into_raw(weak_ctx.clone());
114 });
115 }
116
117 unsafe extern "C" fn release(ctx: *mut c_void) {
118 // No `Weak::decrement_weak_count()`, so we do it ourselves.
119 drop(Weak::from_raw(ctx.cast::<DiskUsageObserverHandle<F>>()));
120 }
121}
122
123/// Used to monitor the disk usage of Ditto or of its sub-components.
124///
125/// Access this through [`Ditto::disk_usage()`][crate::Ditto::disk_usage].
126pub struct DiskUsage {
127 ditto: Arc<BoxedDitto>,
128 component: FsComponent,
129}
130
131impl DiskUsage {
132 /// Create a new DiskUsage for the given component.
133 pub(crate) fn new(ditto: Arc<BoxedDitto>, component: FsComponent) -> Self {
134 Self { ditto, component }
135 }
136
137 /// Returns the current disk usage as a tree of [`DiskUsageItem`]s.
138 ///
139 /// This provides an immediate snapshot of the disk usage. For continuous monitoring,
140 /// use [`observe`][Self::observe] instead.
141 pub fn item(&self) -> DiskUsageItem {
142 let cval = ffi_sdk::ditto_disk_usage(&self.ditto, self.component);
143 ::serde_cbor::from_slice(cval.as_slice()).unwrap()
144 }
145
146 /// Register a callback to monitor the disk usage of the component.
147 ///
148 /// The callback will be invoked whenever the disk usage changes.
149 /// Keep the returned observer handle in scope to continue receiving updates.
150 /// Drop the handle to stop observation.
151 pub fn observe<F: DiskUsageCallback>(&self, callback: F) -> Arc<DiskUsageObserverHandle> {
152 let observer: DiskUsageObserverHandle<F> = Mutex::new(DiskUsageObserver::<F> {
153 on_update: callback,
154 handle: None,
155 });
156 let observer: Arc<DiskUsageObserverHandle<F>> = Arc::new(observer);
157
158 // We will be using `Weak`s to get the "legacy" observer life-cycle pattern:
159 // the caller must hold onto this to keep it alive, otherwise the strong references die,
160 // so `DiskUsageObserver` dies, and drops the `Box<ffi_sdk::DiskUsageCbObserver>`, which
161 // unregisters the callback.
162 //
163 // It would have been way less self-referential to have only the `on_event` part be behind
164 // `Arc` indirection, and return a pair `(Arc<OnEvent>,
165 // repr_c::Box<ffi_sdk::DiskUsageCbObserver>)` (properly prettified behind a layer
166 // of `struct` abstraction), since it would have avoided the need for `Weak`s and the _a
167 // posteriori_ `set_handle()`, but our hands are tied until the next breaking version.
168 let weak_observer = Arc::downgrade(&observer);
169
170 let handle = unsafe {
171 ffi_sdk::ditto_register_disk_usage_callback(
172 &self.ditto,
173 self.component,
174 weak_observer.as_ptr() as *mut _,
175 Some(DiskUsageObserver::<F>::retain),
176 Some(DiskUsageObserver::<F>::release),
177 Some(<unsafe extern "C" fn(_, c_slice::Ref<'_, _>)>::into(
178 DiskUsageObserver::<F>::on_event,
179 )),
180 )
181 };
182 // Would happen implicitly, but writing this down avoids mistakes (_e.g._, incorrectly
183 // using `into_raw()` rather than `as_ptr()`).
184 drop(weak_observer);
185
186 observer.lock().unwrap().set_handle(handle.unwrap());
187 observer
188 }
189}