GUI Show post decoding info
This commit is contained in:
+234
-11
@@ -9,7 +9,8 @@ use std::{
|
||||
};
|
||||
|
||||
use can_dbc::Dbc;
|
||||
use slint::ComponentHandle;
|
||||
use chrono::Local;
|
||||
use slint::{ComponentHandle, ModelRc, StandardListViewItem, VecModel};
|
||||
|
||||
slint::include_modules!();
|
||||
|
||||
@@ -20,10 +21,213 @@ struct State {
|
||||
output_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
struct FileResult {
|
||||
file_name: String,
|
||||
date: String,
|
||||
unknown_count: i32,
|
||||
error_count: i32,
|
||||
unknown_ids: String,
|
||||
error_rows: Vec<(f64, u8, u8)>,
|
||||
runtime_stats: String,
|
||||
}
|
||||
|
||||
struct Run {
|
||||
label: String,
|
||||
files: Vec<FileResult>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Results {
|
||||
runs: Vec<Run>,
|
||||
window: Option<ResultsWindow>,
|
||||
run_counter: usize,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static RESULTS: RefCell<Results> = RefCell::new(Results::default());
|
||||
}
|
||||
|
||||
fn build_file_result(
|
||||
path: &PathBuf,
|
||||
info: libsvld::DecodeProcessInfo,
|
||||
) -> FileResult {
|
||||
let file_name = path
|
||||
.file_name()
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| path.display().to_string());
|
||||
|
||||
let date = info
|
||||
.date
|
||||
.and_utc()
|
||||
.to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
|
||||
|
||||
let unknown_count = info.unknown_ids.len() as i32;
|
||||
let error_count = info.error_frames.len() as i32;
|
||||
|
||||
let mut unknown_sorted: Vec<u32> = info.unknown_ids.iter().copied().collect();
|
||||
unknown_sorted.sort();
|
||||
let unknown_ids = if unknown_sorted.is_empty() {
|
||||
"(none)".to_string()
|
||||
} else {
|
||||
unknown_sorted
|
||||
.iter()
|
||||
.map(|id| format!("0x{:X}", id))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
};
|
||||
|
||||
let error_rows: Vec<(f64, u8, u8)> = info
|
||||
.error_frames
|
||||
.iter()
|
||||
.map(|f| (f.timestamp, f.channel, f.data))
|
||||
.collect();
|
||||
|
||||
let runtime_stats = format!(
|
||||
"threads: {} · page size: {} · pages/chunk: {} · chunk size: {}",
|
||||
info.thread_count, info.page_size, info.pages_per_chunk, info.chunk_size
|
||||
);
|
||||
|
||||
FileResult {
|
||||
file_name,
|
||||
date,
|
||||
unknown_count,
|
||||
error_count,
|
||||
unknown_ids,
|
||||
error_rows,
|
||||
runtime_stats,
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild_entries(results: &Results) -> Vec<ResultEntry> {
|
||||
let last_idx = results.runs.len().saturating_sub(1);
|
||||
let mut entries = Vec::new();
|
||||
for (run_idx, run) in results.runs.iter().enumerate() {
|
||||
let is_new = run_idx == last_idx;
|
||||
entries.push(ResultEntry {
|
||||
is_header: true,
|
||||
is_new,
|
||||
label: run.label.clone().into(),
|
||||
});
|
||||
for f in &run.files {
|
||||
entries.push(ResultEntry {
|
||||
is_header: false,
|
||||
is_new: false,
|
||||
label: f.file_name.clone().into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
entries
|
||||
}
|
||||
|
||||
fn find_file(results: &Results, target: usize) -> Option<&FileResult> {
|
||||
let mut i = 0;
|
||||
for run in &results.runs {
|
||||
if i == target {
|
||||
return None;
|
||||
}
|
||||
i += 1;
|
||||
for f in &run.files {
|
||||
if i == target {
|
||||
return Some(f);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn apply_selection(window: &ResultsWindow, results: &Results, idx: i32) {
|
||||
if idx < 0 {
|
||||
window.set_selected_index(-1);
|
||||
return;
|
||||
}
|
||||
let Some(f) = find_file(results, idx as usize) else {
|
||||
return;
|
||||
};
|
||||
window.set_selected_index(idx);
|
||||
window.set_sel_label(f.file_name.clone().into());
|
||||
window.set_sel_date(f.date.clone().into());
|
||||
window.set_sel_unknown_count(f.unknown_count);
|
||||
window.set_sel_error_count(f.error_count);
|
||||
window.set_sel_unknown_ids(f.unknown_ids.clone().into());
|
||||
window.set_sel_runtime_stats(f.runtime_stats.clone().into());
|
||||
|
||||
let rows: Vec<ModelRc<StandardListViewItem>> = f
|
||||
.error_rows
|
||||
.iter()
|
||||
.map(|(t, ch, d)| {
|
||||
let cells: Vec<StandardListViewItem> = vec![
|
||||
slint::format!("{}", t).into(),
|
||||
slint::format!("{}", ch).into(),
|
||||
slint::format!("0b{:08b}", d).into(),
|
||||
];
|
||||
ModelRc::new(VecModel::from(cells))
|
||||
})
|
||||
.collect();
|
||||
window.set_sel_error_rows(ModelRc::new(VecModel::from(rows)));
|
||||
}
|
||||
|
||||
fn show_results(results: &mut Results, new_run: Run) {
|
||||
results.runs.push(new_run);
|
||||
let entries = rebuild_entries(results);
|
||||
|
||||
let new_first_file_idx = entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev()
|
||||
.find(|(_, e)| !e.is_header)
|
||||
.map(|(i, _)| i as i32)
|
||||
.unwrap_or(-1);
|
||||
let new_first_file_idx = {
|
||||
// We want the first non-header entry of the last run, not the very last.
|
||||
let last_header_idx = entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev()
|
||||
.find(|(_, e)| e.is_header)
|
||||
.map(|(i, _)| i)
|
||||
.unwrap_or(0);
|
||||
entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(last_header_idx + 1)
|
||||
.find(|(_, e)| !e.is_header)
|
||||
.map(|(i, _)| i as i32)
|
||||
.unwrap_or(new_first_file_idx)
|
||||
};
|
||||
|
||||
if results.window.is_none() {
|
||||
let win = ResultsWindow::new().unwrap();
|
||||
let win_weak = win.as_weak();
|
||||
win.on_select(move |i| {
|
||||
if let Some(w) = win_weak.upgrade() {
|
||||
RESULTS.with(|cell| {
|
||||
apply_selection(&w, &cell.borrow(), i);
|
||||
});
|
||||
}
|
||||
});
|
||||
results.window = Some(win);
|
||||
}
|
||||
|
||||
let win = results.window.as_ref().unwrap();
|
||||
let model = ModelRc::new(VecModel::from(entries));
|
||||
win.set_entries(model);
|
||||
apply_selection(win, results, new_first_file_idx);
|
||||
win.show().unwrap();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let window = MainWindow::new().unwrap();
|
||||
let state = Rc::new(RefCell::new(State::default()));
|
||||
|
||||
window.on_open_results(|| {
|
||||
RESULTS.with(|cell| {
|
||||
if let Some(w) = cell.borrow().window.as_ref() {
|
||||
w.show().ok();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
window.on_browse_dbc({
|
||||
let window_weak = window.as_weak();
|
||||
let state = state.clone();
|
||||
@@ -105,29 +309,48 @@ fn main() {
|
||||
|
||||
let window_weak = window_weak.clone();
|
||||
thread::spawn(move || {
|
||||
let result = (|| -> Result<(), String> {
|
||||
let result = (|| -> Result<Vec<FileResult>, String> {
|
||||
let data = fs::read_to_string(&dbc_path)
|
||||
.map_err(|e| format!("Failed to read DBC: {}", e))?;
|
||||
let dbc = Dbc::try_from(data.as_str())
|
||||
.map_err(|e| format!("Failed to parse DBC: {:?}", e))?;
|
||||
let mut out = Vec::with_capacity(asc_paths.len());
|
||||
for asc in &asc_paths {
|
||||
libsvld::decode(&dbc, asc, &output_dir);
|
||||
let info = libsvld::decode(&dbc, asc, &output_dir);
|
||||
out.push(build_file_result(asc, info));
|
||||
}
|
||||
Ok(())
|
||||
Ok(out)
|
||||
})();
|
||||
|
||||
let message = match result {
|
||||
Ok(()) => "Done.".to_string(),
|
||||
Err(e) => format!("Error: {}", e),
|
||||
};
|
||||
|
||||
slint::invoke_from_event_loop(move || {
|
||||
if let Some(w) = window_weak.upgrade() {
|
||||
w.set_busy(false);
|
||||
w.set_status(message.into());
|
||||
}
|
||||
match result {
|
||||
Ok(files) => {
|
||||
if let Some(w) = window_weak.upgrade() {
|
||||
w.set_status("Done.".into());
|
||||
w.set_has_results(true);
|
||||
}
|
||||
RESULTS.with(|cell| {
|
||||
let mut r = cell.borrow_mut();
|
||||
r.run_counter += 1;
|
||||
let label = format!(
|
||||
"Run {} — {}",
|
||||
r.run_counter,
|
||||
Local::now().format("%H:%M:%S")
|
||||
);
|
||||
show_results(&mut r, Run { label, files });
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
if let Some(w) = window_weak.upgrade() {
|
||||
w.set_status(format!("Error: {}", e).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user