use orbok_core::OrbokResult;
use orbok_db::{CATALOG_FILE_NAME, Catalog};
use orbok_db::repo::SettingsRepository;
use orbok_models::SearchCapability;
use orbok_search::SearchService;
use orbok_ui::AppState;
use orbok_ui::i18n::Locale;
use orbok_ui::state::{WizardFileCheck, WizardState};
use orbok_workers::{VerifyOutcome, verify_embedding_model};
use std::path::PathBuf;
use crate::settings::{OrbokSettings, load_settings};
pub fn data_dir() -> PathBuf {
if let Ok(env) = std::env::var("ORBOK_DATA_DIR") {
return PathBuf::from(env);
}
dirs::data_local_dir()
.map(|d| d.join("orbok"))
.unwrap_or_else(|| PathBuf::from("orbok-data"))
}
pub fn data_dir_for_args(portable: bool) -> PathBuf {
if portable { PathBuf::from("orbok-data") } else { data_dir() }
}
pub fn open_catalog(data_dir: &std::path::Path) -> OrbokResult<Catalog> {
std::fs::create_dir_all(data_dir)?;
Catalog::open(data_dir.join(CATALOG_FILE_NAME))
}
pub fn load_initial_state() -> Result<AppState, Box<dyn std::error::Error>> {
let dir = data_dir();
let catalog = open_catalog(&dir)?;
let cache_path = dir.join(orbok_db::CACHE_FILE_NAME);
let recovery = orbok_workers::run_startup_recovery(&catalog, &cache_path)?;
if recovery.jobs_reset > 0 {
tracing::warn!(reset = recovery.jobs_reset, "reset interrupted jobs on startup");
}
let settings = load_settings();
let locale = Locale::parse(&settings.locale)
.or_else(|| {
SettingsRepository::new(&catalog)
.get::<String>("ui.locale")
.ok()
.flatten()
.and_then(|s| Locale::parse(&s))
})
.unwrap_or_default();
let outcome = verify_embedding_model(settings.embedding_model_dir.as_deref());
tracing::info!("{}", orbok_workers::verify_outcome_summary(&outcome));
let (capability, wizard) = build_capability_and_wizard(outcome, &settings);
let mut state = AppState::default();
state.locale = locale;
state.capability = capability;
state.wizard = wizard;
Ok(state)
}
fn build_capability_and_wizard(
outcome: VerifyOutcome,
_settings: &OrbokSettings,
) -> (SearchCapability, Option<WizardState>) {
match outcome {
VerifyOutcome::Ready => (SearchCapability::Hybrid, None),
VerifyOutcome::NotConfigured => {
(SearchCapability::KeywordOnly, Some(WizardState::NotConfigured))
}
VerifyOutcome::FilesInvalid { model_dir, issues } => {
let checks: Vec<WizardFileCheck> = orbok_workers::model_verifier::REQUIRED_MODEL_FILES
.iter()
.map(|rel| {
let found = !issues.iter().any(|i| i.relative_path == *rel);
WizardFileCheck {
relative_path: rel.to_string(),
found,
size_mb: None,
}
})
.collect();
let wizard = WizardState::FileMissing { previous_dir: model_dir };
(SearchCapability::KeywordOnly, Some(wizard))
}
}
}
pub(crate) fn run_search(
catalog: &Catalog,
query: &str,
limit: u32,
) -> Result<Vec<orbok_ui::state::SearchResultDisplay>, Box<dyn std::error::Error>> {
let service = SearchService::new(catalog);
let results = service.search(query, limit)?;
Ok(results
.into_iter()
.map(|r| orbok_ui::state::SearchResultDisplay {
display_path: r.display_path,
title: r.title,
heading_path: r.heading_path,
snippet: r.snippet,
keyword_rank: r.keyword_rank,
badges: r.badges.iter().map(|b| format!("{b:?}")).collect(),
})
.collect())
}
pub fn run_check() -> Result<(), Box<dyn std::error::Error>> {
let dir = data_dir();
tracing::info!(path = %dir.display(), "opening catalog");
let catalog = open_catalog(&dir)?;
let version = catalog.schema_version()?;
let expected = orbok_db::migrations::latest_version();
if version != expected {
return Err(format!("schema version {version} != expected {expected}").into());
}
let settings = load_settings();
let outcome = verify_embedding_model(settings.embedding_model_dir.as_deref());
println!(
"orbok --check OK data_dir={} schema_version={} model={}",
dir.display(),
version,
orbok_workers::verify_outcome_summary(&outcome)
);
Ok(())
}
pub fn persist_locale(catalog: &Catalog, locale: &Locale) -> OrbokResult<()> {
SettingsRepository::new(catalog).set("ui.locale", &locale.as_str().to_string())
}
pub fn persist_model_dir(model_dir: &str) -> Result<(), Box<dyn std::error::Error>> {
let mut settings = load_settings();
settings.embedding_model_dir = Some(model_dir.to_string());
crate::settings::save_settings(&settings)
.map_err(|e| format!("settings save failed: {e:?}"))?;
Ok(())
}