convert to gitea
This commit is contained in:
192
apps/rust_gyber/src/file/json_reader.rs
Normal file
192
apps/rust_gyber/src/file/json_reader.rs
Normal file
@ -0,0 +1,192 @@
|
||||
use anyhow::{Context, Result};
|
||||
use log::{debug, error, info, warn};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
// 문자열 정리 및 기본값("N/A") 설정 함수
|
||||
fn clean_string(s: Option<&str>) -> String {
|
||||
match s {
|
||||
Some(val) => {
|
||||
let trimmed = val.trim();
|
||||
if trimmed.is_empty() {
|
||||
"N/A".to_string() // 비어 있으면 "N/A" 반환
|
||||
} else {
|
||||
trimmed.to_string() // 앞뒤 공백 제거
|
||||
}
|
||||
}
|
||||
None => "N/A".to_string(), // Option이 None이면 "N/A" 반환
|
||||
}
|
||||
}
|
||||
|
||||
// JSON 파일의 원본 데이터 구조체
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct RawHwInfo {
|
||||
pub hostname: Option<String>,
|
||||
pub category: Option<String>,
|
||||
pub manufacturer: Option<String>,
|
||||
pub model: Option<String>,
|
||||
pub serial: Option<String>, // 실제 시리얼 (합성 키 생성에 사용될 수 있음)
|
||||
pub spec_value: Option<String>,
|
||||
pub spec_unit: Option<String>,
|
||||
pub port_or_slot: Option<String>, // 합성 키 생성에 사용
|
||||
}
|
||||
|
||||
// 처리된 하드웨어 정보 구조체 (실제 사용될 데이터)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProcessedHwInfo {
|
||||
pub hostname: String,
|
||||
pub category: String,
|
||||
pub manufacturer: String,
|
||||
pub model: String,
|
||||
pub serial_key: String, // <-- 합성 키 (비교 및 DB 전달용)
|
||||
pub spec_value: String,
|
||||
pub spec_unit: String,
|
||||
}
|
||||
|
||||
// 지정된 디렉토리에서 패턴에 맞는 JSON 파일 목록 찾기
|
||||
pub fn find_json_files(dir_path: &str) -> Result<Vec<PathBuf>> {
|
||||
let mut json_files = Vec::new();
|
||||
let path = Path::new(dir_path);
|
||||
|
||||
// 경로 유효성 검사
|
||||
if !path.is_dir() {
|
||||
anyhow::bail!("제공된 JSON 경로가 디렉토리가 아닙니다: {}", dir_path);
|
||||
}
|
||||
|
||||
// 디렉토리 순회하며 파일 찾기
|
||||
for entry in fs::read_dir(path).with_context(|| format!("디렉토리 읽기 오류: {}", dir_path))? {
|
||||
let entry = entry.context("디렉토리 항목 읽기 실패")?;
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
// 파일 이름 패턴 확인 (HWInfo_*.json)
|
||||
if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
|
||||
if filename.starts_with("HWInfo_") && filename.ends_with(".json") {
|
||||
debug!("발견된 JSON 파일: {:?}", path);
|
||||
json_files.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(json_files)
|
||||
}
|
||||
|
||||
// 단일 JSON 파일 파싱 및 데이터 처리 함수
|
||||
fn parse_and_process_single_json(path: &Path) -> Result<Vec<ProcessedHwInfo>> {
|
||||
// 파일 읽기 및 BOM 처리
|
||||
let bytes = fs::read(path).with_context(|| format!("JSON 파일 읽기 실패: {:?}", path))?;
|
||||
let json_content_without_bom = if bytes.starts_with(&[0xEF, 0xBB, 0xBF]) {
|
||||
std::str::from_utf8(&bytes[3..]).with_context(|| format!("UTF-8 변환 실패(BOM제거): {:?}", path))?
|
||||
} else {
|
||||
std::str::from_utf8(&bytes).with_context(|| format!("UTF-8 변환 실패: {:?}", path))?
|
||||
};
|
||||
|
||||
// 빈 파일 처리
|
||||
if json_content_without_bom.trim().is_empty() {
|
||||
warn!("JSON 파일 내용이 비어 있습니다: {:?}", path);
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
// JSON 파싱
|
||||
let raw_data: Vec<RawHwInfo> = serde_json::from_str(json_content_without_bom)
|
||||
.with_context(|| format!("JSON 파싱 실패: {:?}", path))?;
|
||||
|
||||
let mut processed_list = Vec::new();
|
||||
// 처리할 카테고리 목록 (필요시 DB와 동기화 또는 설정 파일로 분리)
|
||||
let relevant_categories = ["CPU", "Mainboard", "Memory", "SSD", "HDD", "VGA"];
|
||||
|
||||
for raw_item in raw_data {
|
||||
// 필수 필드(카테고리, 호스트명) 확인
|
||||
if let (Some(cat_str), Some(host_str)) = (raw_item.category.as_deref(), raw_item.hostname.as_deref()) {
|
||||
let category = clean_string(Some(cat_str));
|
||||
let hostname = clean_string(Some(host_str));
|
||||
|
||||
// 관련 없는 카테고리 또는 유효하지 않은 호스트명 건너뛰기
|
||||
if hostname == "N/A" || !relevant_categories.contains(&category.as_str()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let original_serial = clean_string(raw_item.serial.as_deref());
|
||||
let port_or_slot = clean_string(raw_item.port_or_slot.as_deref());
|
||||
let model = clean_string(raw_item.model.as_deref());
|
||||
|
||||
// --- 합성 키 생성 로직 ---
|
||||
let serial_key: String;
|
||||
let mut key_parts: Vec<String> = Vec::new();
|
||||
// 실제 시리얼, 슬롯/포트, 호스트명을 조합하여 고유 키 생성 시도
|
||||
if original_serial != "N/A" { key_parts.push(original_serial.replace('.', "")); }
|
||||
if port_or_slot != "N/A" { key_parts.push(port_or_slot.replace('.', "")); }
|
||||
if hostname != "N/A" { key_parts.push(hostname.replace('.', "")); }
|
||||
let combined_key = key_parts.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join("_");
|
||||
|
||||
// 조합된 키가 비어있거나 호스트명만 있는 경우, 대체 키 생성 시도 (모델명+호스트명)
|
||||
if combined_key.is_empty() {
|
||||
let model_cleaned = model.replace('.', "");
|
||||
let hostname_cleaned_fallback = hostname.replace('.', "");
|
||||
if model != "N/A" && hostname != "N/A" {
|
||||
serial_key = format!("{}_{}", model_cleaned, hostname_cleaned_fallback);
|
||||
debug!("대체 Serial Key 생성 (모델+호스트): {}", serial_key);
|
||||
} else {
|
||||
// 대체 키 생성도 불가능하면 해당 항목 건너뛰기
|
||||
warn!("고유 Key 생성 불가 (시리얼/슬롯/모델 정보 부족), 항목 건너<0xEB><0x9B><0x81>: {:?}", raw_item);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
serial_key = combined_key;
|
||||
}
|
||||
// --- 합성 키 생성 로직 끝 ---
|
||||
|
||||
// 처리된 데이터 구조체 생성
|
||||
let processed = ProcessedHwInfo {
|
||||
hostname: hostname.clone(),
|
||||
category, // clean_string 이미 적용됨
|
||||
manufacturer: clean_string(raw_item.manufacturer.as_deref()),
|
||||
model, // clean_string 이미 적용됨
|
||||
serial_key, // 생성된 합성 키
|
||||
spec_value: clean_string(raw_item.spec_value.as_deref()),
|
||||
spec_unit: clean_string(raw_item.spec_unit.as_deref()),
|
||||
};
|
||||
processed_list.push(processed);
|
||||
} else {
|
||||
// 필수 필드 누락 시 경고 로그
|
||||
warn!("필수 필드(Category 또는 Hostname) 누락 항목 건너<0xEB><0x9B><0x81>: {:?}", raw_item);
|
||||
}
|
||||
}
|
||||
Ok(processed_list)
|
||||
}
|
||||
|
||||
// 모든 JSON 파일을 읽고 처리하여 호스트명 기준으로 그룹화하는 함수
|
||||
pub fn read_and_process_json_files(
|
||||
json_dir_path: &str,
|
||||
) -> Result<HashMap<String, Vec<ProcessedHwInfo>>> {
|
||||
info!("'{}' 디렉토리에서 JSON 파일 검색 및 처리 시작...", json_dir_path);
|
||||
let json_files = find_json_files(json_dir_path)?;
|
||||
info!("총 {}개의 JSON 파일 발견.", json_files.len());
|
||||
|
||||
let mut all_processed_data: HashMap<String, Vec<ProcessedHwInfo>> = HashMap::new();
|
||||
// 각 JSON 파일 처리
|
||||
for json_path in json_files {
|
||||
debug!("처리 중인 파일: {:?}", json_path);
|
||||
match parse_and_process_single_json(&json_path) {
|
||||
Ok(processed_items) => {
|
||||
if !processed_items.is_empty() {
|
||||
// 처리된 데이터를 호스트명 기준으로 그룹화
|
||||
for item in processed_items {
|
||||
all_processed_data.entry(item.hostname.clone()).or_default().push(item);
|
||||
}
|
||||
} else {
|
||||
// 처리할 데이터가 없는 경우 디버그 로그
|
||||
debug!("파일에서 처리할 관련 카테고리 데이터가 없습니다: {:?}", json_path);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// 파일 처리 중 오류 발생 시 에러 로그
|
||||
error!("파일 처리 중 오류 발생 {:?}: {}", json_path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
info!("JSON 파일 처리 완료. 총 {}개 호스트 데이터 생성.", all_processed_data.len());
|
||||
Ok(all_processed_data)
|
||||
}
|
||||
Reference in New Issue
Block a user