use super::{
format::{group_thousands, to_i32},
inputs::{FilterSnapshot, sorted_unique},
session::{CachedIds, REMOTE_MESSAGE, Session},
};
use crate::{
MainWindow, RoughPlanModel, RoughPlannerWindow, bridge::library::source::LibrarySource,
};
use indicatrix_vault::db::sqlite::Database;
use slint::{ComponentHandle, Weak};
use std::{
cell::RefCell,
collections::BTreeSet,
sync::{Arc, Mutex, PoisonError},
};
use tracing::warn;
pub fn is_remote(source: &Mutex<LibrarySource>) -> bool {
source
.lock()
.unwrap_or_else(PoisonError::into_inner)
.is_remote()
}
fn count_text(count: i32) -> String {
usize::try_from(count).map_or_else(|_| "counting...".to_string(), group_thousands)
}
fn show_counts(model: &RoughPlanModel<'_>, filter: i32, library: i32) {
model.set_filter_count(filter);
model.set_filter_count_text(count_text(filter).into());
model.set_library_count(library);
model.set_library_count_text(count_text(library).into());
}
pub(super) fn plannable_count(ids: &[i64], excluded: &BTreeSet<i64>) -> usize {
ids.iter().filter(|id| !excluded.contains(id)).count()
}
pub fn spawn_count_query(
ui_weak: Weak<RoughPlannerWindow>,
db: Arc<Mutex<Database>>,
snapshot: FilterSnapshot,
cache: Arc<Mutex<CachedIds>>,
generation: u64,
) -> std::io::Result<()> {
std::thread::Builder::new()
.name("rough-plan-count".to_string())
.spawn(move || {
let filter = snapshot
.query(&db)
.inspect_err(|e| warn!("Rough planner: could not count the filtered designs: {e}"))
.ok();
let (library, excluded) = {
let guard = db.lock().unwrap_or_else(PoisonError::into_inner);
(
guard
.all_entry_ids()
.inspect_err(|e| warn!("Rough planner: could not count the library: {e}"))
.ok()
.map(sorted_unique),
guard
.planner_excluded_ids()
.inspect_err(|e| {
warn!("Rough planner: could not read the excluded designs: {e}");
})
.unwrap_or_default(),
)
};
let counts = (
filter
.as_deref()
.map_or(0, |ids| plannable_count(ids, &excluded)),
library
.as_deref()
.map_or(0, |ids| plannable_count(ids, &excluded)),
);
{
let mut cached = cache.lock().unwrap_or_else(PoisonError::into_inner);
if cached.generation != generation {
return;
}
cached.filter = filter;
cached.library = library;
}
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
let current = cache
.lock()
.unwrap_or_else(PoisonError::into_inner)
.generation;
if current == generation {
show_counts(
&ui.global::<RoughPlanModel>(),
to_i32(counts.0),
to_i32(counts.1),
);
}
});
})
.map(|_handle| ())
}
pub fn sync_remote_message(model: &RoughPlanModel<'_>, remote: bool) {
if remote {
model.set_error_text(REMOTE_MESSAGE.into());
} else if model.get_error_text().as_str() == REMOTE_MESSAGE {
model.set_error_text("".into());
}
}
pub fn refresh_counts(
main: &MainWindow,
planner: &RoughPlannerWindow,
db: &Arc<Mutex<Database>>,
source: &Arc<Mutex<LibrarySource>>,
session: &RefCell<Session>,
) {
let model = planner.global::<RoughPlanModel>();
let cache = Arc::clone(&session.borrow().ids);
let generation = cache.lock().unwrap_or_else(PoisonError::into_inner).reset();
if is_remote(source) {
show_counts(&model, 0, 0);
sync_remote_message(&model, true);
session.borrow_mut().counted_filter = None;
return;
}
sync_remote_message(&model, false);
show_counts(&model, -1, -1);
let snapshot = FilterSnapshot::read(main);
session.borrow_mut().counted_filter = Some(snapshot.clone());
if let Err(e) = spawn_count_query(
planner.as_weak(),
Arc::clone(db),
snapshot,
cache,
generation,
) {
warn!("Rough planner: could not start the counting thread: {e}");
show_counts(&model, 0, 0);
}
}
pub fn refresh_counts_if_filter_changed(
main: &MainWindow,
planner: &RoughPlannerWindow,
db: &Arc<Mutex<Database>>,
source: &Arc<Mutex<LibrarySource>>,
session: &RefCell<Session>,
) {
if planner.global::<RoughPlanModel>().get_running() || is_remote(source) {
return;
}
let now = FilterSnapshot::read(main);
if session.borrow().counted_filter.as_ref() != Some(&now) {
refresh_counts(main, planner, db, source, session);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_count_reads_with_thousands_separators_and_a_marker_while_counting() {
assert_eq!(count_text(0), "0");
assert_eq!(count_text(1), "1");
assert_eq!(count_text(999), "999");
assert_eq!(count_text(1_234), "1,234");
assert_eq!(count_text(1_234_567), "1,234,567");
assert_eq!(count_text(-1), "counting...");
}
#[test]
fn the_plannable_count_leaves_out_the_excluded_designs() {
let ids = [2, 4, 6, 8];
assert_eq!(plannable_count(&ids, &BTreeSet::new()), 4);
assert_eq!(plannable_count(&ids, &BTreeSet::from([4, 8])), 2);
assert_eq!(plannable_count(&ids, &BTreeSet::from([1, 3, 99])), 4);
assert_eq!(plannable_count(&ids, &BTreeSet::from([2, 4, 6, 8])), 0);
assert_eq!(
plannable_count(&ids, &BTreeSet::from([1, 2, 4, 6, 8, 9])),
0
);
assert_eq!(plannable_count(&[], &BTreeSet::from([1])), 0);
}
}