2024-12-11 18:18:40 +01:00
|
|
|
use std::any::TypeId;
|
|
|
|
use std::borrow::Cow;
|
|
|
|
use std::sync::atomic::{AtomicU32, Ordering};
|
|
|
|
use std::sync::{Arc, RwLock};
|
2024-12-10 16:30:48 +01:00
|
|
|
|
|
|
|
use serde::Serialize;
|
2024-12-26 17:16:52 +01:00
|
|
|
use utoipa::ToSchema;
|
2024-12-10 16:30:48 +01:00
|
|
|
|
|
|
|
pub trait Step: 'static + Send + Sync {
|
|
|
|
fn name(&self) -> Cow<'static, str>;
|
|
|
|
fn current(&self) -> u32;
|
|
|
|
fn total(&self) -> u32;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Default)]
|
|
|
|
pub struct Progress {
|
|
|
|
steps: Arc<RwLock<Vec<(TypeId, Box<dyn Step>)>>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Progress {
|
|
|
|
pub fn update_progress<P: Step>(&self, sub_progress: P) {
|
|
|
|
let mut steps = self.steps.write().unwrap();
|
|
|
|
let step_type = TypeId::of::<P>();
|
|
|
|
if let Some(idx) = steps.iter().position(|(id, _)| *id == step_type) {
|
|
|
|
steps.truncate(idx);
|
|
|
|
}
|
|
|
|
steps.push((step_type, Box::new(sub_progress)));
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: This code should be in meilisearch_types but cannot because milli can't depend on meilisearch_types
|
|
|
|
pub fn as_progress_view(&self) -> ProgressView {
|
|
|
|
let steps = self.steps.read().unwrap();
|
|
|
|
|
|
|
|
let mut percentage = 0.0;
|
|
|
|
let mut prev_factors = 1.0;
|
|
|
|
|
2024-12-11 18:17:33 +01:00
|
|
|
let mut step_view = Vec::with_capacity(steps.len());
|
2024-12-10 16:30:48 +01:00
|
|
|
for (_, step) in steps.iter() {
|
|
|
|
prev_factors *= step.total() as f32;
|
|
|
|
percentage += step.current() as f32 / prev_factors;
|
|
|
|
|
|
|
|
step_view.push(ProgressStepView {
|
2024-12-11 18:41:03 +01:00
|
|
|
current_step: step.name(),
|
2024-12-10 16:30:48 +01:00
|
|
|
finished: step.current(),
|
|
|
|
total: step.total(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
ProgressView { steps: step_view, percentage: percentage * 100.0 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This trait lets you use the AtomicSubStep defined right below.
|
|
|
|
/// The name must be a const that never changed but that can't be enforced by the type system because it make the trait non object-safe.
|
|
|
|
/// By forcing the Default trait + the &'static str we make it harder to miss-use the trait.
|
|
|
|
pub trait NamedStep: 'static + Send + Sync + Default {
|
|
|
|
fn name(&self) -> &'static str;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Structure to quickly define steps that need very quick, lockless updating of their current step.
|
|
|
|
/// You can use this struct if:
|
|
|
|
/// - The name of the step doesn't change
|
|
|
|
/// - The total number of steps doesn't change
|
|
|
|
pub struct AtomicSubStep<Name: NamedStep> {
|
2024-12-11 18:03:06 +01:00
|
|
|
unit_name: Name,
|
2024-12-10 16:30:48 +01:00
|
|
|
current: Arc<AtomicU32>,
|
|
|
|
total: u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<Name: NamedStep> AtomicSubStep<Name> {
|
|
|
|
pub fn new(total: u32) -> (Arc<AtomicU32>, Self) {
|
|
|
|
let current = Arc::new(AtomicU32::new(0));
|
2024-12-11 18:03:06 +01:00
|
|
|
(current.clone(), Self { current, total, unit_name: Name::default() })
|
2024-12-10 16:30:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<Name: NamedStep> Step for AtomicSubStep<Name> {
|
|
|
|
fn name(&self) -> Cow<'static, str> {
|
2024-12-11 18:03:06 +01:00
|
|
|
self.unit_name.name().into()
|
2024-12-10 16:30:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fn current(&self) -> u32 {
|
|
|
|
self.current.load(Ordering::Relaxed)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn total(&self) -> u32 {
|
|
|
|
self.total
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-11 18:00:46 +01:00
|
|
|
#[macro_export]
|
|
|
|
macro_rules! make_enum_progress {
|
|
|
|
($visibility:vis enum $name:ident { $($variant:ident,)+ }) => {
|
|
|
|
#[repr(u8)]
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Sequence)]
|
|
|
|
#[allow(clippy::enum_variant_names)]
|
|
|
|
$visibility enum $name {
|
|
|
|
$($variant),+
|
|
|
|
}
|
2024-12-10 16:30:48 +01:00
|
|
|
|
2024-12-11 18:00:46 +01:00
|
|
|
impl Step for $name {
|
|
|
|
fn name(&self) -> Cow<'static, str> {
|
|
|
|
use convert_case::Casing;
|
|
|
|
|
|
|
|
match self {
|
|
|
|
$(
|
|
|
|
$name::$variant => stringify!($variant).from_case(convert_case::Case::Camel).to_case(convert_case::Case::Lower).into()
|
|
|
|
),+
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn current(&self) -> u32 {
|
|
|
|
*self as u32
|
|
|
|
}
|
|
|
|
|
|
|
|
fn total(&self) -> u32 {
|
|
|
|
Self::CARDINALITY as u32
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#[macro_export]
|
|
|
|
macro_rules! make_atomic_progress {
|
|
|
|
($struct_name:ident alias $atomic_struct_name:ident => $step_name:literal) => {
|
|
|
|
#[derive(Default, Debug, Clone, Copy)]
|
|
|
|
pub struct $struct_name {}
|
|
|
|
impl NamedStep for $struct_name {
|
|
|
|
fn name(&self) -> &'static str {
|
|
|
|
$step_name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pub type $atomic_struct_name = AtomicSubStep<$struct_name>;
|
|
|
|
};
|
2024-12-10 16:30:48 +01:00
|
|
|
}
|
|
|
|
|
2024-12-11 18:00:46 +01:00
|
|
|
make_atomic_progress!(Document alias AtomicDocumentStep => "document" );
|
2024-12-11 18:07:45 +01:00
|
|
|
make_atomic_progress!(Payload alias AtomicPayloadStep => "payload" );
|
2024-12-10 16:30:48 +01:00
|
|
|
|
2024-12-26 17:16:52 +01:00
|
|
|
#[derive(Debug, Serialize, Clone, ToSchema)]
|
2024-12-11 18:15:33 +01:00
|
|
|
#[serde(rename_all = "camelCase")]
|
2024-12-26 17:16:52 +01:00
|
|
|
#[schema(rename_all = "camelCase")]
|
2024-12-10 16:30:48 +01:00
|
|
|
pub struct ProgressView {
|
2024-12-11 18:15:33 +01:00
|
|
|
pub steps: Vec<ProgressStepView>,
|
|
|
|
pub percentage: f32,
|
2024-12-10 16:30:48 +01:00
|
|
|
}
|
|
|
|
|
2024-12-26 17:16:52 +01:00
|
|
|
#[derive(Debug, Serialize, Clone, ToSchema)]
|
2024-12-11 18:15:33 +01:00
|
|
|
#[serde(rename_all = "camelCase")]
|
2024-12-26 17:16:52 +01:00
|
|
|
#[schema(rename_all = "camelCase")]
|
2024-12-10 16:30:48 +01:00
|
|
|
pub struct ProgressStepView {
|
2024-12-11 18:41:03 +01:00
|
|
|
pub current_step: Cow<'static, str>,
|
2024-12-11 18:15:33 +01:00
|
|
|
pub finished: u32,
|
|
|
|
pub total: u32,
|
2024-12-10 16:30:48 +01:00
|
|
|
}
|