use tokio::sync::mpsc;
use crate::app::runtime::handlers::common::{HandlerConfig, handle_result};
use crate::state::AppState;
struct FileHandlerConfig;
impl HandlerConfig for FileHandlerConfig {
type Result = crate::state::modal::PackageFileInfo;
fn get_resolving(&self, app: &AppState) -> bool {
app.files_resolving
}
fn set_resolving(&self, app: &mut AppState, value: bool) {
app.files_resolving = value;
}
fn get_preflight_resolving(&self, app: &AppState) -> bool {
app.preflight_files_resolving
}
fn set_preflight_resolving(&self, app: &mut AppState, value: bool) {
app.preflight_files_resolving = value;
}
fn stage_name(&self) -> &'static str {
"files"
}
fn update_cache(&self, app: &mut AppState, results: &[Self::Result]) {
tracing::debug!(
"[Runtime] handle_file_result: Updating install_list_files with {} entries (current cache has {})",
results.len(),
app.install_list_files.len()
);
for file_info in results {
tracing::info!(
"[Runtime] handle_file_result: Package '{}' - total={}, new={}, changed={}, removed={}, config={}, pacnew={}, pacsave={}",
file_info.name,
file_info.total_count,
file_info.new_count,
file_info.changed_count,
file_info.removed_count,
file_info.config_count,
file_info.pacnew_candidates,
file_info.pacsave_candidates
);
}
app.install_list_files = results.to_vec();
tracing::debug!(
"[Runtime] handle_file_result: install_list_files now has {} entries: {:?}",
app.install_list_files.len(),
app.install_list_files
.iter()
.map(|f| &f.name)
.collect::<Vec<_>>()
);
}
fn set_cache_dirty(&self, app: &mut AppState) {
app.files_cache_dirty = true;
tracing::debug!(
"[Runtime] handle_file_result: Marked files_cache_dirty=true, install_list_files has {} entries: {:?}",
app.install_list_files.len(),
app.install_list_files
.iter()
.map(|f| &f.name)
.collect::<Vec<_>>()
);
}
fn clear_preflight_items(&self, app: &mut AppState) {
app.preflight_files_items = None;
}
fn sync_to_modal(&self, app: &mut AppState, results: &[Self::Result], was_preflight: bool) {
if let crate::state::Modal::Preflight {
items, file_info, ..
} = &mut app.modal
{
let item_names: std::collections::HashSet<String> =
items.iter().map(|i| i.name.clone()).collect();
let filtered_files: Vec<_> = results
.iter()
.filter(|file_info| item_names.contains(&file_info.name))
.cloned()
.collect();
let old_files_len = file_info.len();
tracing::info!(
"[Runtime] handle_file_result: Modal open - items={}, filtered_files={}, modal_current={} (was {})",
items.len(),
filtered_files.len(),
file_info.len(),
old_files_len
);
if filtered_files.is_empty() {
tracing::debug!(
"[Runtime] handle_file_result: No matching files to sync. Modal items: {:?}, File packages: {:?}",
item_names,
results.iter().map(|f| &f.name).collect::<Vec<_>>()
);
} else {
tracing::info!(
"[Runtime] handle_file_result: Syncing {} file infos to preflight modal (was_preflight={}, modal had {} before)",
filtered_files.len(),
was_preflight,
old_files_len
);
let filtered_files_map: std::collections::HashMap<String, _> = filtered_files
.iter()
.map(|f| (f.name.clone(), f.clone()))
.collect();
for file_info_entry in file_info.iter_mut() {
if let Some(new_data) = filtered_files_map.get(&file_info_entry.name) {
let old_total = file_info_entry.total_count;
*file_info_entry = new_data.clone();
if old_total != new_data.total_count {
tracing::info!(
"[Runtime] handle_file_result: Updated modal entry for '{}' from total={} to total={}",
file_info_entry.name,
old_total,
new_data.total_count
);
}
}
}
for new_file in &filtered_files {
if !file_info.iter().any(|f| f.name == new_file.name) {
file_info.push(new_file.clone());
}
}
tracing::info!(
"[Runtime] handle_file_result: Successfully synced file info to modal, modal now has {} entries (was {})",
file_info.len(),
old_files_len
);
}
} else {
tracing::debug!(
"[Runtime] handle_file_result: Preflight modal not open, skipping sync"
);
}
}
fn log_flag_clear(&self, app: &AppState, was_preflight: bool, cancelled: bool) {
tracing::debug!(
"[Runtime] handle_file_result: Clearing flags - was_preflight={}, files_resolving={}, preflight_files_resolving={}, cancelled={}",
was_preflight,
self.get_resolving(app),
app.preflight_files_resolving,
cancelled
);
}
fn is_resolution_complete(&self, app: &AppState, results: &[Self::Result]) -> bool {
if let crate::state::Modal::Preflight { items, .. } = &app.modal {
let item_names: std::collections::HashSet<String> =
items.iter().map(|i| i.name.clone()).collect();
let result_names: std::collections::HashSet<String> =
results.iter().map(|f| f.name.clone()).collect();
let cache_names: std::collections::HashSet<String> = app
.install_list_files
.iter()
.map(|f| f.name.clone())
.collect();
let all_have_data = item_names
.iter()
.all(|name| result_names.contains(name) || cache_names.contains(name));
if !all_have_data {
let missing: Vec<String> = item_names
.iter()
.filter(|name| !result_names.contains(*name) && !cache_names.contains(*name))
.cloned()
.collect();
tracing::debug!(
"[Runtime] handle_file_result: Resolution incomplete - missing files for: {:?}",
missing
);
}
return all_have_data;
}
if let Some(ref install_items) = app.preflight_files_items {
let item_names: std::collections::HashSet<String> =
install_items.iter().map(|i| i.name.clone()).collect();
let result_names: std::collections::HashSet<String> =
results.iter().map(|f| f.name.clone()).collect();
let cache_names: std::collections::HashSet<String> = app
.install_list_files
.iter()
.map(|f| f.name.clone())
.collect();
let all_have_data = item_names
.iter()
.all(|name| result_names.contains(name) || cache_names.contains(name));
if !all_have_data {
let missing: Vec<String> = item_names
.iter()
.filter(|name| !result_names.contains(*name) && !cache_names.contains(*name))
.cloned()
.collect();
tracing::debug!(
"[Runtime] handle_file_result: Resolution incomplete - missing files for: {:?}",
missing
);
}
return all_have_data;
}
true
}
}
pub fn handle_file_result(
app: &mut AppState,
files: &[crate::state::modal::PackageFileInfo],
tick_tx: &mpsc::UnboundedSender<()>,
) {
handle_result(app, files, tick_tx, &FileHandlerConfig);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::new_app;
#[test]
fn handle_file_result_updates_cache() {
let mut app = new_app();
app.files_resolving = true;
let (tick_tx, _tick_rx) = mpsc::unbounded_channel();
let files = vec![crate::state::modal::PackageFileInfo {
name: "test-package".to_string(),
files: vec![],
total_count: 0,
new_count: 0,
changed_count: 0,
removed_count: 0,
config_count: 0,
pacnew_candidates: 0,
pacsave_candidates: 0,
}];
handle_file_result(&mut app, &files, &tick_tx);
assert_eq!(app.install_list_files.len(), 1);
assert!(!app.files_resolving);
assert!(!app.preflight_files_resolving);
assert!(app.files_cache_dirty);
}
}