use crate::datasheets::schema::*;
use std::path::Path;
const EMBEDDED_STM32F411: &str = include_str!("../../datasheets/stm32f411.json");
const EMBEDDED_ESP32: &str = include_str!("../../datasheets/esp32-wroom-32.json");
const EMBEDDED_ATMEGA328P: &str = include_str!("../../datasheets/atmega328p.json");
const EMBEDDED_RP2040: &str = include_str!("../../datasheets/rp2040.json");
const EMBEDDED_LM1117: &str = include_str!("../../datasheets/lm1117-3.3.json");
const EMBEDDED_AMS1117: &str = include_str!("../../datasheets/ams1117-3.3.json");
const EMBEDDED_CH340G: &str = include_str!("../../datasheets/ch340g.json");
const EMBEDDED_CP2102: &str = include_str!("../../datasheets/cp2102.json");
const EMBEDDED_NE555: &str = include_str!("../../datasheets/ne555.json");
const EMBEDDED_LM7805: &str = include_str!("../../datasheets/lm7805.json");
pub fn get_all_datasheets() -> Vec<DatasheetRequirements> {
let embedded_jsons = [
EMBEDDED_STM32F411,
EMBEDDED_ESP32,
EMBEDDED_ATMEGA328P,
EMBEDDED_RP2040,
EMBEDDED_LM1117,
EMBEDDED_AMS1117,
EMBEDDED_CH340G,
EMBEDDED_CP2102,
EMBEDDED_NE555,
EMBEDDED_LM7805,
];
let mut datasheets = Vec::new();
for json_str in embedded_jsons {
match serde_json::from_str::<DatasheetRequirements>(json_str) {
Ok(ds) => datasheets.push(ds),
Err(e) => {
tracing::warn!("Failed to parse embedded datasheet: {}", e);
}
}
}
datasheets
}
pub fn load_datasheets_from_directory(dir: &Path) -> (Vec<DatasheetRequirements>, Vec<String>) {
let mut datasheets = Vec::new();
let mut errors = Vec::new();
if !dir.exists() || !dir.is_dir() {
return (datasheets, errors);
}
let entries = match std::fs::read_dir(dir) {
Ok(entries) => entries,
Err(e) => {
errors.push(format!("Failed to read directory {:?}: {}", dir, e));
return (datasheets, errors);
}
};
for entry in entries.flatten() {
let path = entry.path();
if path.extension().map(|e| e != "json").unwrap_or(true) {
continue;
}
match load_datasheet_from_file(&path) {
Ok(ds) => {
tracing::info!(
"Loaded datasheet for {} from {:?}",
ds.part_numbers.first().unwrap_or(&"Unknown".to_string()),
path.file_name()
);
datasheets.push(ds);
}
Err(e) => {
let error_msg = format!("Failed to load {:?}: {}", path.file_name(), e);
tracing::warn!("{}", error_msg);
errors.push(error_msg);
}
}
}
(datasheets, errors)
}
pub fn load_datasheet_from_file(path: &Path) -> Result<DatasheetRequirements, String> {
let content = std::fs::read_to_string(path)
.map_err(|e| format!("Failed to read file: {}", e))?;
serde_json::from_str(&content)
.map_err(|e| format!("Failed to parse JSON: {}", e))
}
pub fn get_datasheets_directory() -> Option<std::path::PathBuf> {
if let Ok(exe_path) = std::env::current_exe() {
if let Some(parent) = exe_path.parent() {
let resources_path = parent.parent()
.map(|p| p.join("Resources").join("datasheets"));
if let Some(ref path) = resources_path {
if path.exists() {
return Some(path.clone());
}
}
let dev_path = parent.parent()
.and_then(|p| p.parent())
.map(|p| p.join("datasheets"));
if let Some(ref path) = dev_path {
if path.exists() {
return Some(path.clone());
}
}
let cwd_path = std::path::PathBuf::from("datasheets");
if cwd_path.exists() {
return Some(cwd_path);
}
let src_tauri_path = std::path::PathBuf::from("src-tauri/datasheets");
if src_tauri_path.exists() {
return Some(src_tauri_path);
}
}
}
None
}
fn get_user_datasheets_directory() -> Option<std::path::PathBuf> {
#[cfg(target_os = "macos")]
{
if let Some(home) = std::env::var_os("HOME") {
let app_data = std::path::PathBuf::from(home)
.join("Library/Application Support/com.kicadai.assistant/datasheets");
if app_data.exists() {
return Some(app_data);
}
}
}
#[cfg(target_os = "windows")]
{
if let Some(app_data) = std::env::var_os("APPDATA") {
let app_data = std::path::PathBuf::from(app_data)
.join("com.kicadai.assistant/datasheets");
if app_data.exists() {
return Some(app_data);
}
}
}
#[cfg(target_os = "linux")]
{
if let Some(home) = std::env::var_os("HOME") {
let app_data = std::path::PathBuf::from(home)
.join(".local/share/com.kicadai.assistant/datasheets");
if app_data.exists() {
return Some(app_data);
}
}
}
None
}
pub fn load_all_datasheets() -> Vec<DatasheetRequirements> {
let mut all_datasheets = Vec::new();
let mut loaded_part_numbers: std::collections::HashSet<String> = std::collections::HashSet::new();
if let Some(user_dir) = get_user_datasheets_directory() {
let (user_datasheets, errors) = load_datasheets_from_directory(&user_dir);
for error in errors {
tracing::warn!("User datasheet loading error: {}", error);
}
for ds in user_datasheets {
for pn in &ds.part_numbers {
loaded_part_numbers.insert(pn.to_uppercase());
}
all_datasheets.push(ds);
}
tracing::info!("Loaded {} datasheets from user directory: {:?}", all_datasheets.len(), user_dir);
}
if let Some(dir) = get_datasheets_directory() {
let (external, errors) = load_datasheets_from_directory(&dir);
for error in errors {
tracing::warn!("Datasheet loading error: {}", error);
}
for ds in external {
let already_loaded = ds.part_numbers.iter()
.any(|pn| loaded_part_numbers.contains(&pn.to_uppercase()));
if !already_loaded {
for pn in &ds.part_numbers {
loaded_part_numbers.insert(pn.to_uppercase());
}
all_datasheets.push(ds);
}
}
tracing::info!("Loaded {} total datasheets (including external)", all_datasheets.len());
}
let embedded = get_all_datasheets();
for ds in embedded {
let already_loaded = ds.part_numbers.iter()
.any(|pn| loaded_part_numbers.contains(&pn.to_uppercase()));
if !already_loaded {
for pn in &ds.part_numbers {
loaded_part_numbers.insert(pn.to_uppercase());
}
all_datasheets.push(ds);
}
}
tracing::info!("Total datasheets available: {}", all_datasheets.len());
all_datasheets
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_embedded_datasheets_parse() {
let datasheets = get_all_datasheets();
assert_eq!(datasheets.len(), 10);
for ds in &datasheets {
assert!(!ds.part_numbers.is_empty());
assert!(!ds.manufacturer.is_empty());
}
}
#[test]
fn test_stm32_datasheet() {
let ds: DatasheetRequirements = serde_json::from_str(EMBEDDED_STM32F411).unwrap();
assert!(ds.part_numbers.contains(&"STM32F411CEU6".to_string()));
assert_eq!(ds.manufacturer, "STMicroelectronics");
assert!(!ds.decoupling_requirements.is_empty());
}
#[test]
fn test_esp32_datasheet() {
let ds: DatasheetRequirements = serde_json::from_str(EMBEDDED_ESP32).unwrap();
assert!(ds.part_numbers.contains(&"ESP32-WROOM-32".to_string()));
assert_eq!(ds.manufacturer, "Espressif");
}
#[test]
fn test_ne555_datasheet() {
let ds: DatasheetRequirements = serde_json::from_str(EMBEDDED_NE555).unwrap();
assert!(ds.part_numbers.contains(&"NE555".to_string()));
assert!(ds.part_numbers.contains(&"555".to_string()));
}
}