use std::path::Path;
use std::path::PathBuf;
use crate::config::profile;
use crate::integrations::{
NefaxDriveType, NefaxOpts, ZahirOutputMode, ZahirRC, pre_opts_for_nefaxer,
};
use crate::utils::BumperBuffer;
use super::paths::{UblxPaths, normalize_rel_path_for_policy, path_is_under_or_equal};
use super::toast::OPERATION_NAME;
use super::validation;
pub struct UblxOptsForDirExtras<'a> {
pub valid_theme_names: &'a [&'a str],
pub bumper: Option<&'a BumperBuffer>,
}
#[derive(Clone, Debug)]
pub struct UblxSettings {
pub num_threads: usize,
pub drive_type: String,
pub parallel_walk: bool,
pub config_source: Option<String>,
}
#[must_use]
pub fn parse_drive_type(s: &str) -> NefaxDriveType {
match s {
"SSD" => NefaxDriveType::SSD,
"HDD" => NefaxDriveType::HDD,
"Network" => NefaxDriveType::Network,
_ => NefaxDriveType::Unknown,
}
}
fn drive_type_to_string(d: NefaxDriveType) -> &'static str {
match d {
NefaxDriveType::SSD => "SSD",
NefaxDriveType::HDD => "HDD",
NefaxDriveType::Network => "Network",
NefaxDriveType::Unknown => "Unknown",
}
}
pub const STREAMING_THRESHOLD: usize = 6;
pub const TOKIO_RUNTIME_WORKERS: usize = 2;
const HIDDEN_EXCLUDE_PATTERN: &str = ".*";
#[derive(Clone, Debug)]
pub struct UblxOpts {
pub nefax_opts: NefaxOpts,
pub zahir_rc: ZahirRC,
pub max_workers_available: usize,
pub nefax_workers_override: Option<usize>,
pub zahir_workers_override: Option<usize>,
pub tokio_runtime_workers: usize,
#[allow(dead_code)]
pub ublx_workers_override: Option<usize>,
pub streaming: bool,
pub config_source: Option<String>,
pub theme: Option<String>,
pub layout: profile::LayoutOverlay,
pub bg_opacity: Option<f32>,
pub opacity_format: profile::Osc11BackgroundFormat,
pub editor_path: Option<String>,
pub enable_enhance_all: bool,
pub ask_enhance_on_new_root: bool,
pub enable_enhance_all_cache_before_apply: Option<bool>,
pub with_hash_cache_before_apply: Option<bool>,
pub enhance_policy: Vec<profile::EnhancePolicyEntry>,
pub run_snapshot_on_startup: bool,
}
impl UblxOpts {
#[must_use]
pub fn load_overlay_from_cache(ublx_paths: &UblxPaths) -> Option<profile::UblxOverlay> {
profile::load_ublx_toml(ublx_paths.last_applied_config_path(), None)
}
#[must_use]
pub fn load_merged_overlay(
ublx_paths: &UblxPaths,
valid_theme_names: Option<&[&str]>,
) -> profile::UblxOverlay {
let global = profile::load_ublx_toml(ublx_paths.global_config(), valid_theme_names);
let local = profile::load_ublx_toml(ublx_paths.toml_path(), valid_theme_names);
profile::UblxOverlay::merge(global, local)
}
#[must_use]
pub fn load_overlay_from_path(path: Option<PathBuf>) -> profile::UblxOverlay {
profile::load_ublx_toml(path, None).unwrap_or_default()
}
fn apply_overlay(&mut self, overlay: &profile::UblxOverlay) {
if let Some(ref extra) = overlay.exclude {
self.nefax_opts.exclude.extend(extra.iter().cloned());
}
self.apply_hot_reload_overlay(overlay);
}
pub fn apply_hot_reload_overlay(&mut self, overlay: &profile::UblxOverlay) {
let show_hidden = overlay.show_hidden_files.unwrap_or(false);
if show_hidden {
self.nefax_opts
.exclude
.retain(|p| p != HIDDEN_EXCLUDE_PATTERN);
self.zahir_rc.flags.ignore_hidden_files = false;
} else {
if !self
.nefax_opts
.exclude
.iter()
.any(|p| p == HIDDEN_EXCLUDE_PATTERN)
{
self.nefax_opts
.exclude
.push(HIDDEN_EXCLUDE_PATTERN.to_string());
}
self.zahir_rc.flags.ignore_hidden_files = true;
}
if let Some(hash) = overlay.hash {
self.nefax_opts.with_hash = hash;
}
if overlay.theme.is_some() {
self.theme.clone_from(&overlay.theme);
}
if overlay.layout.is_some() {
self.layout = overlay.layout.clone().unwrap_or_default();
}
self.bg_opacity = overlay.bg_opacity;
self.opacity_format = overlay.opacity_format.unwrap_or_default();
if overlay.editor_path.is_some() {
self.editor_path.clone_from(&overlay.editor_path);
}
if let Some(v) = overlay.enable_enhance_all {
self.enable_enhance_all = v;
}
if let Some(v) = overlay.ask_enhance_on_new_root {
self.ask_enhance_on_new_root = v;
}
self.enhance_policy = overlay.enhance_policy.clone().unwrap_or_default();
self.run_snapshot_on_startup = overlay.run_snapshot_on_startup.unwrap_or(true);
}
pub fn reload_hot_config(
&mut self,
ublx_paths: &UblxPaths,
valid_theme_names: &[&str],
) -> validation::ReloadResult {
let from_disk = Self::load_merged_overlay(ublx_paths, Some(valid_theme_names));
let to_apply = if from_disk == profile::UblxOverlay::default() {
Self::load_overlay_from_cache(ublx_paths).unwrap_or_default()
} else {
from_disk
};
if to_apply == profile::UblxOverlay::default() {
return validation::ReloadResult::default();
}
match validation::validate_hot_reload_overlay(&to_apply, valid_theme_names) {
Ok(()) => {
self.apply_hot_reload_overlay(&to_apply);
profile::save_overlay_to_cache(ublx_paths, &to_apply);
validation::ReloadResult {
applied: true,
validation_errors: Vec::new(),
}
}
Err(validation_errors) => {
let cached = Self::load_overlay_from_cache(ublx_paths).unwrap_or_default();
self.apply_hot_reload_overlay(&cached);
validation::ReloadResult {
applied: false,
validation_errors,
}
}
}
}
fn startup_validation_fallback_message(errors: &[validation::HotReloadError]) -> String {
format!(
"Config invalid at startup, using cache: {}",
validation::first_validation_error_message(errors)
)
}
#[must_use]
pub fn for_dir(
dir_to_ublx: &Path,
ublx_paths: &UblxPaths,
nefax_workers_override: Option<usize>,
zahir_workers_override: Option<usize>,
ublx_workers_override: Option<usize>,
cached_settings: Option<&UblxSettings>,
config: &UblxOptsForDirExtras<'_>,
) -> Self {
let exclude = ublx_paths.exclude();
let nefax_opts = pre_opts_for_nefaxer(dir_to_ublx, &exclude, cached_settings);
let num_threads = nefax_opts.num_threads.unwrap_or(1);
let zahir_rc = ZahirRC::new();
let streaming = num_threads >= STREAMING_THRESHOLD;
let config_source = cached_settings.and_then(|s| s.config_source.clone());
let enable_enhance_all_cache_before_apply =
Self::load_overlay_from_cache(ublx_paths).and_then(|o| o.enable_enhance_all);
let with_hash_cache_before_apply =
Self::load_overlay_from_cache(ublx_paths).and_then(|o| o.hash);
let mut opts = Self {
nefax_opts,
zahir_rc,
max_workers_available: num_threads,
nefax_workers_override,
zahir_workers_override,
ublx_workers_override,
tokio_runtime_workers: TOKIO_RUNTIME_WORKERS,
streaming,
config_source,
theme: None,
layout: profile::LayoutOverlay::default(),
bg_opacity: None,
opacity_format: profile::Osc11BackgroundFormat::default(),
editor_path: None,
enable_enhance_all: false,
ask_enhance_on_new_root: true,
enable_enhance_all_cache_before_apply,
with_hash_cache_before_apply,
enhance_policy: Vec::new(),
run_snapshot_on_startup: true,
};
let global =
profile::load_ublx_toml(ublx_paths.global_config(), Some(config.valid_theme_names));
let local = profile::load_ublx_toml(ublx_paths.toml_path(), Some(config.valid_theme_names));
let merged = profile::UblxOverlay::merge(global, local);
match validation::validate_hot_reload_overlay(&merged, config.valid_theme_names) {
Ok(()) => {
opts.apply_overlay(&merged);
profile::save_overlay_to_cache(ublx_paths, &merged);
}
Err(validation_errors) => {
let cached = Self::load_overlay_from_cache(ublx_paths).unwrap_or_default();
opts.apply_overlay(&cached);
if let Some(b) = config.bumper {
b.push_with_operation(
log::Level::Warn,
Self::startup_validation_fallback_message(&validation_errors).as_str(),
Some(OPERATION_NAME.op("settings")),
);
}
}
}
opts
}
pub fn to_ublx_settings(&self) -> UblxSettings {
UblxSettings {
num_threads: self.max_workers_available,
drive_type: self
.nefax_opts
.drive_type
.map_or("Unknown", drive_type_to_string)
.to_string(),
parallel_walk: self.nefax_opts.use_parallel_walk.unwrap_or(false),
config_source: self.config_source.clone(),
}
}
#[must_use]
pub fn for_zahir_only(max_workers_available: usize, zahir_rc: ZahirRC) -> Self {
let nefax_opts = NefaxOpts::default();
let streaming = max_workers_available >= STREAMING_THRESHOLD;
Self {
nefax_opts,
zahir_rc,
max_workers_available,
nefax_workers_override: Some(0),
zahir_workers_override: Some(max_workers_available),
ublx_workers_override: Some(0),
tokio_runtime_workers: TOKIO_RUNTIME_WORKERS,
streaming,
config_source: None,
theme: None,
layout: profile::LayoutOverlay::default(),
bg_opacity: None,
opacity_format: profile::Osc11BackgroundFormat::default(),
editor_path: None,
enable_enhance_all: true,
ask_enhance_on_new_root: true,
enable_enhance_all_cache_before_apply: None,
with_hash_cache_before_apply: None,
enhance_policy: Vec::new(),
run_snapshot_on_startup: true,
}
}
#[must_use]
pub fn is_sequential_mode(&self) -> bool {
self.max_workers_available < STREAMING_THRESHOLD
}
fn default_share_1_1_1(&self) -> (usize, usize, usize) {
let n = self.max_workers_available;
let third = n / 3;
let nefax = third;
let zahir = third;
let ublx = n.saturating_sub(nefax).saturating_sub(zahir);
(nefax, zahir, ublx)
}
fn default_share_streaming(&self) -> (usize, usize, usize) {
let n = self.max_workers_available;
let nefax = 2.min(n);
let ublx = 2.min(n.saturating_sub(nefax));
let zahir = n.saturating_sub(nefax).saturating_sub(ublx);
(nefax, zahir, ublx)
}
fn default_share(&self) -> (usize, usize, usize) {
if self.streaming {
self.default_share_streaming()
} else {
self.default_share_1_1_1()
}
}
fn effective_workers_for(&self, override_val: Option<usize>, default_share: usize) -> usize {
if self.is_sequential_mode() {
return self.max_workers_available;
}
override_val
.unwrap_or(default_share)
.min(self.max_workers_available)
}
#[must_use]
pub fn effective_nefax_workers(&self) -> usize {
let (n, _, _) = self.default_share();
self.effective_workers_for(self.nefax_workers_override, n)
}
#[must_use]
pub fn effective_zahir_workers(&self) -> usize {
let (_, z, _) = self.default_share();
self.effective_workers_for(self.zahir_workers_override, z)
}
#[allow(dead_code)]
#[must_use]
pub fn effective_ublx_workers(&self) -> usize {
let (_, _, u) = self.default_share();
self.effective_workers_for(self.ublx_workers_override, u)
}
#[must_use]
pub fn nefax_opts(&self) -> &NefaxOpts {
&self.nefax_opts
}
#[must_use]
pub fn nefax_opts_with_workers(&self) -> NefaxOpts {
let mut opts = self.nefax_opts.clone();
opts.num_threads = Some(self.effective_nefax_workers());
opts
}
#[must_use]
pub fn zahir_runtime_config(&self) -> ZahirRC {
let mut config = self.zahir_rc.clone();
config.output_mode = ZahirOutputMode::Templates;
config.max_workers = self.effective_zahir_workers();
config
}
#[must_use]
pub fn matching_enhance_policy(&self, rel_path: &str) -> Option<profile::EnhancePolicy> {
let rel = normalize_rel_path_for_policy(rel_path);
let mut best: Option<(usize, profile::EnhancePolicy)> = None;
for e in &self.enhance_policy {
let p = normalize_rel_path_for_policy(&e.path);
if p.is_empty() {
continue;
}
if path_is_under_or_equal(&rel, &p) {
let len = p.len();
if best.as_ref().is_none_or(|(blen, _)| len > *blen) {
best = Some((len, e.policy));
}
}
}
best.map(|(_, pol)| pol)
}
#[must_use]
pub fn batch_zahir_for_path(&self, rel_path: &str) -> bool {
match self.matching_enhance_policy(rel_path) {
Some(profile::EnhancePolicy::Auto) => true,
Some(profile::EnhancePolicy::Manual) => false,
None => self.enable_enhance_all,
}
}
}