dittolive-ditto 5.0.0

Ditto is a peer to peer cross-platform database that allows mobile, web, IoT and server apps to sync with or without an internet connection.
Documentation
//! Use [`ditto.disk_usage()`] to access the [`DiskUsage`] API to inspect Ditto's disk usage.
//!
//! # Monitor disk usage on a device
//!
//! In order to persist data on a device, Ditto can create a local database in a folder specified
//! via [`DittoConfig::persistence_directory`].
//!
//! To monitor this folder size, [`DiskUsage`] offers two methods:
//!   - [`.item()`] to retrieve an immediate `DiskUsageItem` tree and
//!   - [`.observe(...)`] to call a callback every time changes occur in the Ditto folder.
//!
//! [`ditto.disk_usage()`]: crate::Ditto::disk_usage
//! [`DittoConfig::persistence_directory`]: crate::ditto::init::DittoConfig::persistence_directory
//! [`.item()`]: DiskUsage::item
//! [`.observe(...)`]: DiskUsage::observe

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)]
/// Represents the type of file system entry (directory, file, or symbolic link).
pub enum FileSystemType {
    /// Item is a directory
    Directory = 0,

    /// Item is a standard file
    File,

    /// Item is a symlink
    SymLink,
}

#[derive(Deserialize, Debug, Clone, Eq, PartialEq)]
/// Represents an entry in the file system tree returned by [`DiskUsage`].
///
/// This can be a file, a directory, or a symbolic link. For directories,
/// the [`children`][Self::children] field contains the nested entries.
pub struct DiskUsageItem {
    /// Type of the entry.
    pub fs_type: FileSystemType,
    /// Relative path in the [`DittoRoot`].
    pub path: String,
    /// Size of the entry in bytes. For directories, this includes the size of all children.
    pub size_in_bytes: usize,
    /// If the entry is a directory, it contains child items.
    pub children: Option<Vec<DiskUsageItem>>,
}

/// Handle returned by [`DiskUsage::observe`].
///
/// Keep this observer in scope as long as you want to receive updates on disk usage changes.
/// Dropping the observer will stop the observation.
pub struct DiskUsageObserver<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,
}

trait_alias! {
    /// Callback bounds for the [`DiskUsage`] [`.observe(...)`] handler.
    ///
    /// [`.observe(...)`]: DiskUsage::observe
    pub trait DiskUsageCallback = 'static + Send + FnMut(DiskUsageItem)
}

/// Alias for the observer type returned by [`.observe(...)`].
///
/// [`.observe(...)`]: DiskUsage::observe
#[allow(type_alias_bounds)]
pub type DiskUsageObserverHandle<F: ?Sized + DiskUsageCallback = dyn DiskUsageCallback> =
    Mutex<DiskUsageObserver<F>>;

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

impl<F: DiskUsageCallback> DiskUsageObserver<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< DiskUsageObserver<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.
///
/// Access this through [`Ditto::disk_usage()`][crate::Ditto::disk_usage].
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 }
    }

    /// Returns the current disk usage as a tree of [`DiskUsageItem`]s.
    ///
    /// This provides an immediate snapshot of the disk usage. For continuous monitoring,
    /// use [`observe`][Self::observe] instead.
    pub fn item(&self) -> DiskUsageItem {
        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.
    ///
    /// The callback will be invoked whenever the disk usage changes.
    /// Keep the returned observer handle in scope to continue receiving updates.
    /// Drop the handle to stop observation.
    pub fn observe<F: DiskUsageCallback>(&self, callback: F) -> Arc<DiskUsageObserverHandle> {
        let observer: DiskUsageObserverHandle<F> = Mutex::new(DiskUsageObserver::<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(DiskUsageObserver::<F>::retain),
                Some(DiskUsageObserver::<F>::release),
                Some(<unsafe extern "C" fn(_, c_slice::Ref<'_, _>)>::into(
                    DiskUsageObserver::<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
    }
}