1use std::{collections::HashMap, fs, path::Path, time::Instant};
2
3use crate::index as pkgindex;
4use crate::state::{AppState, PackageDetails, PackageItem};
5
6use super::super::deps_cache;
7use super::super::files_cache;
8use super::super::sandbox_cache;
9use super::super::services_cache;
10
11pub fn initialize_locale_system(
28 app: &mut AppState,
29 locale_pref: &str,
30 _prefs: &crate::theme::Settings,
31) {
32 let locales_dir = crate::i18n::find_locales_dir().unwrap_or_else(|| {
34 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
35 .join("config")
36 .join("locales")
37 });
38 let Some(i18n_config_path) = crate::i18n::find_config_file("i18n.yml") else {
39 tracing::error!(
40 "i18n config file not found in development or installed locations. Using default locale 'en-US'."
41 );
42 app.locale = "en-US".to_string();
43 app.translations = std::collections::HashMap::new();
44 app.translations_fallback = std::collections::HashMap::new();
45 return;
46 };
47
48 let resolver = crate::i18n::LocaleResolver::new(&i18n_config_path);
50 let resolved_locale = resolver.resolve(locale_pref);
51
52 tracing::info!(
53 "Resolved locale: '{}' (from settings: '{}')",
54 &resolved_locale,
55 if locale_pref.trim().is_empty() {
56 "<auto-detect>"
57 } else {
58 locale_pref
59 }
60 );
61 app.locale.clone_from(&resolved_locale);
62
63 let mut loader = crate::i18n::LocaleLoader::new(locales_dir);
65
66 match loader.load("en-US") {
68 Ok(fallback) => {
69 let key_count = fallback.len();
70 app.translations_fallback = fallback;
71 tracing::debug!("Loaded English fallback translations ({} keys)", key_count);
72 }
73 Err(e) => {
74 tracing::error!(
75 "Failed to load English fallback translations: {}. Application may show untranslated keys.",
76 e
77 );
78 app.translations_fallback = std::collections::HashMap::new();
79 }
80 }
81
82 if resolved_locale == "en-US" {
84 app.translations = app.translations_fallback.clone();
86 tracing::debug!("Using English as primary locale");
87 } else {
88 match loader.load(&resolved_locale) {
89 Ok(translations) => {
90 let key_count = translations.len();
91 app.translations = translations;
92 tracing::info!(
93 "Loaded translations for locale '{}' ({} keys)",
94 resolved_locale,
95 key_count
96 );
97 let test_keys = [
99 "app.details.footer.search_hint",
100 "app.details.footer.confirm_installation",
101 ];
102 for key in &test_keys {
103 if app.translations.contains_key(*key) {
104 tracing::debug!(" ✓ Key '{}' found in translations", key);
105 } else {
106 tracing::debug!(" ✗ Key '{}' NOT found in translations", key);
107 }
108 }
109 }
110 Err(e) => {
111 tracing::warn!(
112 "Failed to load translations for locale '{}': {}. Using English fallback.",
113 resolved_locale,
114 e
115 );
116 app.translations = std::collections::HashMap::new();
118 }
119 }
120 }
121}
122
123pub fn run_startup_config_preflight() -> crate::theme::Settings {
137 crate::theme::maybe_migrate_legacy_confs();
138 crate::theme::ensure_theme_keys_present();
139 let prefs = crate::theme::settings();
140 crate::theme::ensure_settings_keys_present(&prefs);
141 prefs
142}
143
144#[allow(clippy::struct_excessive_bools)]
160pub struct InitFlags {
161 pub needs_deps_resolution: bool,
163 pub needs_files_resolution: bool,
165 pub needs_services_resolution: bool,
167 pub needs_sandbox_resolution: bool,
169}
170
171fn load_cache_with_signature<T>(
187 install_list: &[crate::state::PackageItem],
188 cache_path: &std::path::PathBuf,
189 compute_signature: impl Fn(&[crate::state::PackageItem]) -> Vec<String>,
190 load_cache: impl Fn(&std::path::PathBuf, &[String]) -> Option<T>,
191 cache_name: &str,
192) -> (Option<T>, bool) {
193 if install_list.is_empty() {
194 return (None, false);
195 }
196
197 let signature = compute_signature(install_list);
198 load_cache(cache_path, &signature).map_or_else(
199 || {
200 tracing::info!(
201 "{} cache missing or invalid, will trigger background resolution",
202 cache_name
203 );
204 (None, true)
205 },
206 |cached| (Some(cached), false),
207 )
208}
209
210fn ensure_cache_parent_dir(path: &Path) {
221 if let Some(parent) = path.parent()
222 && let Err(error) = fs::create_dir_all(parent)
223 {
224 tracing::warn!(
225 path = %parent.display(),
226 %error,
227 "[Init] Failed to create cache directory"
228 );
229 }
230}
231
232fn initialize_cache_files(app: &AppState) {
244 let empty_signature: Vec<String> = Vec::new();
245
246 if !app.deps_cache_path.exists() {
247 ensure_cache_parent_dir(&app.deps_cache_path);
248 deps_cache::save_cache(&app.deps_cache_path, &empty_signature, &[]);
249 tracing::debug!(
250 path = %app.deps_cache_path.display(),
251 "[Init] Created empty dependency cache"
252 );
253 }
254
255 if !app.files_cache_path.exists() {
256 ensure_cache_parent_dir(&app.files_cache_path);
257 files_cache::save_cache(&app.files_cache_path, &empty_signature, &[]);
258 tracing::debug!(
259 path = %app.files_cache_path.display(),
260 "[Init] Created empty file cache"
261 );
262 }
263
264 if !app.services_cache_path.exists() {
265 ensure_cache_parent_dir(&app.services_cache_path);
266 services_cache::save_cache(&app.services_cache_path, &empty_signature, &[]);
267 tracing::debug!(
268 path = %app.services_cache_path.display(),
269 "[Init] Created empty service cache"
270 );
271 }
272
273 if !app.sandbox_cache_path.exists() {
274 ensure_cache_parent_dir(&app.sandbox_cache_path);
275 sandbox_cache::save_cache(&app.sandbox_cache_path, &empty_signature, &[]);
276 tracing::debug!(
277 path = %app.sandbox_cache_path.display(),
278 "[Init] Created empty sandbox cache"
279 );
280 }
281}
282
283pub fn apply_settings_to_app_state(app: &mut AppState, prefs: &crate::theme::Settings) {
294 app.layout_left_pct = prefs.layout_left_pct;
295 app.layout_center_pct = prefs.layout_center_pct;
296 app.layout_right_pct = prefs.layout_right_pct;
297 app.main_pane_order = prefs.main_pane_order;
298 app.vertical_layout_limits = crate::state::VerticalLayoutLimits::from_u16s(
299 prefs.vertical_min_results,
300 prefs.vertical_max_results,
301 prefs.vertical_min_middle,
302 prefs.vertical_max_middle,
303 prefs.vertical_min_package_info,
304 );
305 app.keymap = prefs.keymap.clone();
306 app.sort_mode = prefs.sort_mode;
307 app.package_marker = prefs.package_marker;
308 app.show_recent_pane = prefs.show_recent_pane;
309 app.show_install_pane = prefs.show_install_pane;
310 app.show_keybinds_footer = prefs.show_keybinds_footer;
311 app.search_normal_mode = prefs.search_startup_mode;
312 app.fuzzy_search_enabled = prefs.fuzzy_search;
313 app.installed_packages_mode = prefs.installed_packages_mode;
314 app.app_mode = if prefs.start_in_news {
315 crate::state::types::AppMode::News
316 } else {
317 crate::state::types::AppMode::Package
318 };
319 app.news_filter_show_arch_news = prefs.news_filter_show_arch_news;
320 app.news_filter_show_advisories = prefs.news_filter_show_advisories;
321 app.news_filter_show_pkg_updates = prefs.news_filter_show_pkg_updates;
322 app.news_filter_show_aur_updates = prefs.news_filter_show_aur_updates;
323 app.news_filter_show_aur_comments = prefs.news_filter_show_aur_comments;
324 app.news_filter_installed_only = prefs.news_filter_installed_only;
325 app.news_max_age_days = prefs.news_max_age_days;
326 app.refresh_news_results();
328 crate::logic::repos::refresh_dynamic_filters_in_app(app, prefs);
329}
330
331fn check_gnome_terminal(app: &mut AppState, headless: bool) {
343 if headless {
344 return;
345 }
346
347 let is_gnome = std::env::var("XDG_CURRENT_DESKTOP")
348 .ok()
349 .is_some_and(|v| v.to_uppercase().contains("GNOME"));
350
351 if !is_gnome {
352 return;
353 }
354
355 let has_gterm = crate::install::command_on_path("gnome-terminal");
356 let has_gconsole =
357 crate::install::command_on_path("gnome-console") || crate::install::command_on_path("kgx");
358
359 if !(has_gterm || has_gconsole) {
360 app.modal = crate::state::Modal::GnomeTerminalPrompt;
361 }
362}
363
364fn load_details_cache(app: &mut AppState) {
374 if let Ok(s) = std::fs::read_to_string(&app.cache_path)
375 && let Ok(map) = serde_json::from_str::<HashMap<String, PackageDetails>>(&s)
376 {
377 app.details_cache = map;
378 tracing::info!(path = %app.cache_path.display(), "loaded details cache");
379 }
380}
381
382fn load_recent_searches(app: &mut AppState) {
393 if let Ok(s) = std::fs::read_to_string(&app.recent_path)
394 && let Ok(list) = serde_json::from_str::<Vec<String>>(&s)
395 {
396 let count = list.len();
397 app.load_recent_items(&list);
398 if count > 0 {
399 app.history_state.select(Some(0));
400 }
401 tracing::info!(
402 path = %app.recent_path.display(),
403 count = count,
404 "loaded recent searches"
405 );
406 }
407}
408
409fn load_install_list(app: &mut AppState) {
420 if let Ok(s) = std::fs::read_to_string(&app.install_path)
421 && let Ok(list) = serde_json::from_str::<Vec<PackageItem>>(&s)
422 {
423 app.install_list = list;
424 if !app.install_list.is_empty() {
425 app.install_state.select(Some(0));
426 }
427 tracing::info!(
428 path = %app.install_path.display(),
429 count = app.install_list.len(),
430 "loaded install list"
431 );
432 }
433}
434
435fn load_news_read_urls(app: &mut AppState) {
445 if let Ok(s) = std::fs::read_to_string(&app.news_read_path)
446 && let Ok(set) = serde_json::from_str::<std::collections::HashSet<String>>(&s)
447 {
448 app.news_read_urls = set;
449 tracing::info!(
450 path = %app.news_read_path.display(),
451 count = app.news_read_urls.len(),
452 "loaded read news urls"
453 );
454 }
455}
456
457fn load_news_read_ids(app: &mut AppState) {
468 if let Ok(s) = std::fs::read_to_string(&app.news_read_ids_path)
469 && let Ok(set) = serde_json::from_str::<std::collections::HashSet<String>>(&s)
470 {
471 app.news_read_ids = set;
472 tracing::info!(
473 path = %app.news_read_ids_path.display(),
474 count = app.news_read_ids.len(),
475 "loaded read news ids"
476 );
477 return;
478 }
479
480 if app.news_read_ids.is_empty() && !app.news_read_urls.is_empty() {
481 app.news_read_ids.extend(app.news_read_urls.iter().cloned());
482 tracing::info!(
483 copied = app.news_read_ids.len(),
484 "seeded news read ids from legacy URL set"
485 );
486 app.news_read_ids_dirty = true;
487 }
488}
489
490fn load_announcement_state(app: &mut AppState) {
501 #[derive(serde::Deserialize)]
510 struct OldAnnouncementReadState {
511 hash: Option<String>,
513 }
514 if let Ok(s) = std::fs::read_to_string(&app.announcement_read_path) {
515 if let Ok(ids) = serde_json::from_str::<std::collections::HashSet<String>>(&s) {
517 app.announcements_read_ids = ids;
518 tracing::info!(
519 path = %app.announcement_read_path.display(),
520 count = app.announcements_read_ids.len(),
521 "loaded announcement read IDs"
522 );
523 return;
524 }
525 if let Ok(old_state) = serde_json::from_str::<OldAnnouncementReadState>(&s)
526 && let Some(hash) = old_state.hash
527 {
528 app.announcements_read_ids.insert(format!("hash:{hash}"));
529 app.announcement_dirty = true; tracing::info!(
531 path = %app.announcement_read_path.display(),
532 "migrated old announcement read state"
533 );
534 }
535 }
536}
537
538fn check_version_announcement(app: &mut AppState) {
549 const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION");
550
551 let current_base_version = crate::announcements::extract_base_version(CURRENT_VERSION);
553
554 if let Some(announcement) = crate::announcements::VERSION_ANNOUNCEMENTS
556 .iter()
557 .find(|a| {
558 let announcement_base_version = crate::announcements::extract_base_version(a.version);
559 announcement_base_version == current_base_version
560 })
561 {
562 let version_id = format!("v{CURRENT_VERSION}");
565
566 if app.announcements_read_ids.contains(&version_id) {
568 tracing::info!(
569 current_version = CURRENT_VERSION,
570 base_version = %current_base_version,
571 "version announcement already marked as read"
572 );
573 return;
574 }
575
576 if matches!(app.modal, crate::state::Modal::None) {
577 app.modal = crate::state::Modal::Announcement {
579 title: announcement.title.to_string(),
580 content: announcement.content.to_string(),
581 id: version_id,
582 scroll: 0,
583 };
584 tracing::info!(
585 current_version = CURRENT_VERSION,
586 base_version = %current_base_version,
587 announcement_version = announcement.version,
588 "showing version announcement modal"
589 );
590 } else {
591 app.pending_announcements
593 .push(crate::announcements::RemoteAnnouncement {
594 id: version_id,
595 title: announcement.title.to_string(),
596 content: announcement.content.to_string(),
597 min_version: None,
598 max_version: None,
599 expires: None,
600 });
601 tracing::info!(
602 current_version = CURRENT_VERSION,
603 base_version = %current_base_version,
604 announcement_version = announcement.version,
605 queue_size = app.pending_announcements.len(),
606 "queued version announcement modal because another modal is open"
607 );
608 }
609 }
610 }
613
614pub fn initialize_app_state(
633 app: &mut AppState,
634 dry_run_flag: bool,
635 headless: bool,
636 prefs: &crate::theme::Settings,
637) -> InitFlags {
638 app.dry_run = if dry_run_flag {
639 true
640 } else {
641 prefs.app_dry_run_default
642 };
643 app.last_input_change = Instant::now();
644
645 tracing::info!(
647 recent = %app.recent_path.display(),
648 install = %app.install_path.display(),
649 details_cache = %app.cache_path.display(),
650 index = %app.official_index_path.display(),
651 news_read = %app.news_read_path.display(),
652 news_read_ids = %app.news_read_ids_path.display(),
653 announcement_read = %app.announcement_read_path.display(),
654 "resolved state file paths"
655 );
656
657 crate::logic::repos::load_repos_config_into_app(app, crate::theme::resolve_repos_config_path());
658 apply_settings_to_app_state(app, prefs);
659
660 initialize_locale_system(app, &prefs.locale, prefs);
662
663 check_gnome_terminal(app, headless);
664
665 if !headless && !prefs.startup_news_configured {
667 if matches!(app.modal, crate::state::Modal::None) {
669 let ssh_command = crate::theme::settings().aur_vote_ssh_command;
670 app.pending_aur_ssh_help_check_result = Some(
671 crate::logic::ssh_setup::spawn_aur_ssh_help_check(ssh_command),
672 );
673 app.aur_ssh_help_ready = None;
674 app.modal = crate::state::Modal::StartupSetupSelector {
675 cursor: 0,
676 selected: std::collections::HashSet::new(),
677 active_privilege_tool: crate::logic::privilege::active_tool().ok(),
678 };
679 }
680 } else if !headless && prefs.startup_news_configured {
681 app.news_loading = true;
684 app.toast_message = Some(crate::i18n::t(app, "app.news_button.loading"));
685 app.toast_expires_at = None; }
687
688 if !headless {
690 let username = std::env::var("USER").unwrap_or_else(|_| "user".to_string());
691 let (is_locked, lockout_until, remaining_minutes) =
692 crate::logic::faillock::get_lockout_info(&username);
693 app.faillock_locked = is_locked;
694 app.faillock_lockout_until = lockout_until;
695 app.faillock_remaining_minutes = remaining_minutes;
696 }
697
698 load_details_cache(app);
699 load_recent_searches(app);
700 load_install_list(app);
701 initialize_cache_files(app);
702
703 let (deps_cache, needs_deps_resolution) = load_cache_with_signature(
705 &app.install_list,
706 &app.deps_cache_path,
707 deps_cache::compute_signature,
708 deps_cache::load_cache,
709 "dependency",
710 );
711 if let Some(cached_deps) = deps_cache {
712 app.install_list_deps = cached_deps;
713 tracing::info!(
714 path = %app.deps_cache_path.display(),
715 count = app.install_list_deps.len(),
716 "loaded dependency cache"
717 );
718 }
719
720 let (files_cache, needs_files_resolution) = load_cache_with_signature(
722 &app.install_list,
723 &app.files_cache_path,
724 files_cache::compute_signature,
725 files_cache::load_cache,
726 "file",
727 );
728 if let Some(cached_files) = files_cache {
729 app.install_list_files = cached_files;
730 tracing::info!(
731 path = %app.files_cache_path.display(),
732 count = app.install_list_files.len(),
733 "loaded file cache"
734 );
735 }
736
737 let (services_cache, needs_services_resolution) = load_cache_with_signature(
739 &app.install_list,
740 &app.services_cache_path,
741 services_cache::compute_signature,
742 services_cache::load_cache,
743 "service",
744 );
745 if let Some(cached_services) = services_cache {
746 app.install_list_services = cached_services;
747 tracing::info!(
748 path = %app.services_cache_path.display(),
749 count = app.install_list_services.len(),
750 "loaded service cache"
751 );
752 }
753
754 let (sandbox_cache, needs_sandbox_resolution) = load_cache_with_signature(
756 &app.install_list,
757 &app.sandbox_cache_path,
758 sandbox_cache::compute_signature,
759 sandbox_cache::load_cache,
760 "sandbox",
761 );
762 if let Some(cached_sandbox) = sandbox_cache {
763 app.install_list_sandbox = cached_sandbox;
764 tracing::info!(
765 path = %app.sandbox_cache_path.display(),
766 count = app.install_list_sandbox.len(),
767 "loaded sandbox cache"
768 );
769 }
770
771 load_news_read_urls(app);
772 load_news_read_ids(app);
773 load_announcement_state(app);
774
775 pkgindex::load_from_disk(&app.official_index_path);
776
777 check_version_announcement(app);
779 tracing::info!(
780 path = %app.official_index_path.display(),
781 "attempted to load official index from disk"
782 );
783
784 InitFlags {
785 needs_deps_resolution,
786 needs_files_resolution,
787 needs_services_resolution,
788 needs_sandbox_resolution,
789 }
790}
791
792pub fn trigger_initial_resolutions(
808 app: &mut AppState,
809 flags: &InitFlags,
810 deps_req_tx: &tokio::sync::mpsc::UnboundedSender<(
811 Vec<PackageItem>,
812 crate::state::modal::PreflightAction,
813 )>,
814 files_req_tx: &tokio::sync::mpsc::UnboundedSender<(
815 Vec<PackageItem>,
816 crate::state::modal::PreflightAction,
817 )>,
818 services_req_tx: &tokio::sync::mpsc::UnboundedSender<(
819 Vec<PackageItem>,
820 crate::state::modal::PreflightAction,
821 )>,
822 sandbox_req_tx: &tokio::sync::mpsc::UnboundedSender<Vec<PackageItem>>,
823) {
824 if flags.needs_deps_resolution && !app.install_list.is_empty() {
825 app.deps_resolving = true;
826 let _ = deps_req_tx.send((
828 app.install_list.clone(),
829 crate::state::modal::PreflightAction::Install,
830 ));
831 }
832
833 if flags.needs_files_resolution && !app.install_list.is_empty() {
834 app.files_resolving = true;
835 let _ = files_req_tx.send((
837 app.install_list.clone(),
838 crate::state::modal::PreflightAction::Install,
839 ));
840 }
841
842 if flags.needs_services_resolution && !app.install_list.is_empty() {
843 app.services_resolving = true;
844 let _ = services_req_tx.send((
845 app.install_list.clone(),
846 crate::state::modal::PreflightAction::Install,
847 ));
848 }
849
850 if flags.needs_sandbox_resolution && !app.install_list.is_empty() {
851 app.sandbox_resolving = true;
852 let _ = sandbox_req_tx.send(app.install_list.clone());
853 }
854}
855
856#[cfg(test)]
857mod tests {
858 use super::*;
859 use crate::app::runtime::background::Channels;
860
861 fn new_app() -> AppState {
866 AppState::default()
867 }
868
869 #[test]
870 fn initialize_locale_system_fallback_when_config_missing() {
883 let mut app = new_app();
884 let prefs = crate::theme::Settings::default();
885
886 initialize_locale_system(&mut app, "", &prefs);
888
889 assert!(!app.locale.is_empty());
891 assert!(app.translations.is_empty() || !app.translations.is_empty());
893 assert!(app.translations_fallback.is_empty() || !app.translations_fallback.is_empty());
894 }
895
896 #[test]
897 fn initialize_app_state_sets_dry_run_flag() {
911 let mut app = new_app();
912 let prefs = crate::theme::settings();
913 let flags = initialize_app_state(&mut app, true, false, &prefs);
914
915 assert!(app.dry_run);
916 let _ = flags;
919 }
920
921 #[test]
922 fn initialize_app_state_loads_settings() {
937 let mut app = new_app();
938 let prefs = crate::theme::settings();
939 let _flags = initialize_app_state(&mut app, false, false, &prefs);
940
941 assert!(app.layout_left_pct > 0);
943 assert!(app.layout_center_pct > 0);
944 assert!(app.layout_right_pct > 0);
945 }
949
950 #[test]
951 fn initialize_app_state_shows_startup_selector_when_news_unconfigured() {
953 let mut app = new_app();
954 let mut prefs = crate::theme::settings();
955 prefs.startup_news_configured = false;
956 let _flags = initialize_app_state(&mut app, false, false, &prefs);
957 assert!(matches!(
958 app.modal,
959 crate::state::Modal::StartupSetupSelector { .. }
960 ));
961 }
962
963 #[test]
964 fn check_version_announcement_queues_when_modal_already_open() {
976 let mut app = new_app();
977 app.modal = crate::state::Modal::StartupSetupSelector {
978 cursor: 0,
979 selected: std::collections::HashSet::new(),
980 active_privilege_tool: None,
981 };
982 let pending_before = app.pending_announcements.len();
983
984 check_version_announcement(&mut app);
985
986 assert!(matches!(
987 app.modal,
988 crate::state::Modal::StartupSetupSelector { .. }
989 ));
990 assert_eq!(
991 app.pending_announcements.len(),
992 pending_before.saturating_add(1)
993 );
994 }
995
996 #[test]
997 fn initialize_cache_files_creates_empty_placeholders() {
1008 let mut app = new_app();
1009 let mut deps_path = std::env::temp_dir();
1010 deps_path.push(format!(
1011 "pacsea_init_deps_cache_{}_{}.json",
1012 std::process::id(),
1013 std::time::SystemTime::now()
1014 .duration_since(std::time::UNIX_EPOCH)
1015 .expect("System time is before UNIX epoch")
1016 .as_nanos()
1017 ));
1018 let mut files_path = deps_path.clone();
1019 files_path.set_file_name("pacsea_init_files_cache.json");
1020 let mut services_path = deps_path.clone();
1021 services_path.set_file_name("pacsea_init_services_cache.json");
1022 let mut sandbox_path = deps_path.clone();
1023 sandbox_path.set_file_name("pacsea_init_sandbox_cache.json");
1024
1025 app.deps_cache_path = deps_path.clone();
1026 app.files_cache_path = files_path.clone();
1027 app.services_cache_path = services_path.clone();
1028 app.sandbox_cache_path = sandbox_path.clone();
1029
1030 let _ = std::fs::remove_file(&app.deps_cache_path);
1032 let _ = std::fs::remove_file(&app.files_cache_path);
1033 let _ = std::fs::remove_file(&app.services_cache_path);
1034 let _ = std::fs::remove_file(&app.sandbox_cache_path);
1035
1036 initialize_cache_files(&app);
1037
1038 let deps_body = std::fs::read_to_string(&app.deps_cache_path)
1039 .expect("Dependency cache file should exist");
1040 let deps_cache: crate::app::deps_cache::DependencyCache =
1041 serde_json::from_str(&deps_body).expect("Dependency cache should parse");
1042 assert!(deps_cache.install_list_signature.is_empty());
1043 assert!(deps_cache.dependencies.is_empty());
1044
1045 let files_body =
1046 std::fs::read_to_string(&app.files_cache_path).expect("File cache file should exist");
1047 let files_cache: crate::app::files_cache::FileCache =
1048 serde_json::from_str(&files_body).expect("File cache should parse");
1049 assert!(files_cache.install_list_signature.is_empty());
1050 assert!(files_cache.files.is_empty());
1051
1052 let services_body = std::fs::read_to_string(&app.services_cache_path)
1053 .expect("Service cache file should exist");
1054 let services_cache: crate::app::services_cache::ServiceCache =
1055 serde_json::from_str(&services_body).expect("Service cache should parse");
1056 assert!(services_cache.install_list_signature.is_empty());
1057 assert!(services_cache.services.is_empty());
1058
1059 let sandbox_body = std::fs::read_to_string(&app.sandbox_cache_path)
1060 .expect("Sandbox cache file should exist");
1061 let sandbox_cache: crate::app::sandbox_cache::SandboxCache =
1062 serde_json::from_str(&sandbox_body).expect("Sandbox cache should parse");
1063 assert!(sandbox_cache.install_list_signature.is_empty());
1064 assert!(sandbox_cache.sandbox_info.is_empty());
1065
1066 let _ = std::fs::remove_file(&app.deps_cache_path);
1067 let _ = std::fs::remove_file(&app.files_cache_path);
1068 let _ = std::fs::remove_file(&app.services_cache_path);
1069 let _ = std::fs::remove_file(&app.sandbox_cache_path);
1070 }
1071
1072 #[tokio::test]
1073 async fn trigger_initial_resolutions_skips_when_install_list_empty() {
1086 let mut app = new_app();
1087 app.install_list.clear();
1088
1089 let flags = InitFlags {
1090 needs_deps_resolution: true,
1091 needs_files_resolution: true,
1092 needs_services_resolution: true,
1093 needs_sandbox_resolution: true,
1094 };
1095
1096 let channels = Channels::new(std::path::PathBuf::from("/tmp"));
1098
1099 trigger_initial_resolutions(
1101 &mut app,
1102 &flags,
1103 &channels.deps_req_tx,
1104 &channels.files_req_tx,
1105 &channels.services_req_tx,
1106 &channels.sandbox_req_tx,
1107 );
1108
1109 assert!(!app.deps_resolving);
1111 assert!(!app.files_resolving);
1112 assert!(!app.services_resolving);
1113 assert!(!app.sandbox_resolving);
1114 }
1115
1116 #[tokio::test]
1117 async fn trigger_initial_resolutions_triggers_when_needed() {
1131 let mut app = new_app();
1132 app.install_list.push(crate::state::PackageItem {
1133 name: "test-package".to_string(),
1134 version: "1.0.0".to_string(),
1135 description: "Test".to_string(),
1136 source: crate::state::Source::Aur,
1137 popularity: None,
1138 out_of_date: None,
1139 orphaned: false,
1140 });
1141
1142 let flags = InitFlags {
1143 needs_deps_resolution: true,
1144 needs_files_resolution: false,
1145 needs_services_resolution: false,
1146 needs_sandbox_resolution: false,
1147 };
1148
1149 let channels = Channels::new(std::path::PathBuf::from("/tmp"));
1150
1151 trigger_initial_resolutions(
1152 &mut app,
1153 &flags,
1154 &channels.deps_req_tx,
1155 &channels.files_req_tx,
1156 &channels.services_req_tx,
1157 &channels.sandbox_req_tx,
1158 );
1159
1160 assert!(app.deps_resolving);
1162 assert!(!app.files_resolving);
1164 assert!(!app.services_resolving);
1165 assert!(!app.sandbox_resolving);
1166 }
1167}