diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 7721140..e0669bc 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -9,7 +9,7 @@ - + @@ -165,7 +165,8 @@ - + + diff --git a/locales/en_US.json b/locales/en_US.json index efa03b2..b24e5f0 100644 --- a/locales/en_US.json +++ b/locales/en_US.json @@ -62,5 +62,11 @@ "flatpak_update_dialog_heading": "Flatpak Appstream Sync", "flatpak_update_dialog_retry_label": "Retry", "flatpak_update_dialog_status_failed": "Flatpak Appstream Sync: Failed", - "flatpak_packages_no_viewport_page_title": "All Flatpak Packages are Up to date!" + "flatpak_packages_no_viewport_page_title": "All Flatpak Packages are Up to date!", + "flatpak_type_user": "User", + "flatpak_type_system": "System", + "flatpak_ref": "Flatpak Ref", + "flatpak_status": "Status", + "flatpak_transaction_bytes_transferred": "Downloaded", + "flatpak_transaction_installed_size": "Saved to Disk" } \ No newline at end of file diff --git a/src/bin/gui/flatpak_update_page/mod.rs b/src/bin/gui/flatpak_update_page/mod.rs index 68e94a9..e9b597e 100644 --- a/src/bin/gui/flatpak_update_page/mod.rs +++ b/src/bin/gui/flatpak_update_page/mod.rs @@ -45,6 +45,12 @@ pub fn flatpak_update_page( let user_refs_for_upgrade_vec: Rc>> = Rc::new(RefCell::new(Vec::new())); + let system_refs_for_upgrade_vec_all: Rc>> = + Rc::new(RefCell::new(Vec::new())); + + let user_refs_for_upgrade_vec_all: Rc>> = + Rc::new(RefCell::new(Vec::new())); + let cancellable_no = libflatpak::gio::Cancellable::NONE; thread::spawn(move || { @@ -64,7 +70,8 @@ pub fn flatpak_update_page( .expect("appstream_sync_percent_receiver closed"); appstream_sync_status_sender .send_blocking(format!( - "System - {}: {}", + "{} - {}: {}", + t!("flatpak_type_system"), remote.name().unwrap_or("Unknown remote".into()), status )) @@ -104,7 +111,8 @@ pub fn flatpak_update_page( .expect("appstream_sync_percent_receiver closed"); appstream_sync_status_sender .send_blocking(format!( - "User - {}: {}", + "{} - {}: {}", + t!("flatpak_type_user"), remote.name().unwrap_or("Unknown remote".into()), status )) @@ -269,19 +277,27 @@ pub fn flatpak_update_page( .build(); update_button.add_css_class("destructive-action"); + let system_refs_for_upgrade_vec_all_clone0 = &system_refs_for_upgrade_vec_all.clone(); + let user_refs_for_upgrade_vec_all_clone0 = user_refs_for_upgrade_vec_all.clone(); + + let system_refs_for_upgrade_vec_clone0 = system_refs_for_upgrade_vec.clone(); + let user_refs_for_upgrade_vec_clone0 = user_refs_for_upgrade_vec.clone(); + update_button.connect_clicked(clone!( #[weak] window, #[weak] retry_signal_action, #[strong] - system_refs_for_upgrade_vec, + system_refs_for_upgrade_vec_all_clone0, #[strong] - user_refs_for_upgrade_vec, + user_refs_for_upgrade_vec_all_clone0, move |_| { process::flatpak_process_update( - &system_refs_for_upgrade_vec.borrow(), - &user_refs_for_upgrade_vec.borrow(), + Some(&system_refs_for_upgrade_vec_clone0.borrow()), + Some(&user_refs_for_upgrade_vec_clone0.borrow()), + &system_refs_for_upgrade_vec_all_clone0.borrow(), + &user_refs_for_upgrade_vec_all_clone0.borrow(), window, &retry_signal_action, ) @@ -313,6 +329,14 @@ pub fn flatpak_update_page( #[strong] packages_boxedlist, #[strong] + system_refs_for_upgrade_vec_all, + #[strong] + user_refs_for_upgrade_vec_all, + #[strong] + system_refs_for_upgrade_vec, + #[strong] + user_refs_for_upgrade_vec, + #[strong] viewport_bin, #[strong] packages_viewport, @@ -430,6 +454,10 @@ pub fn flatpak_update_page( .borrow_mut() .push(flatpak_row.clone()); + system_refs_for_upgrade_vec_all + .borrow_mut() + .push(flatpak_row.clone()); + flatpak_row.connect_closure( "checkbutton-toggled", false, @@ -578,6 +606,10 @@ pub fn flatpak_update_page( .borrow_mut() .push(flatpak_row.clone()); + user_refs_for_upgrade_vec_all + .borrow_mut() + .push(flatpak_row.clone()); + flatpak_row.connect_closure( "checkbutton-toggled", false, diff --git a/src/bin/gui/flatpak_update_page/process.rs b/src/bin/gui/flatpak_update_page/process.rs index 13be727..202ed5a 100644 --- a/src/bin/gui/flatpak_update_page/process.rs +++ b/src/bin/gui/flatpak_update_page/process.rs @@ -4,14 +4,18 @@ use adw::prelude::*; use gtk::glib::*; use gtk::*; use libflatpak::prelude::*; +use libflatpak::Transaction; use pretty_bytes::converter::convert; use serde::Serialize; use serde_json::Value; use std::cell::RefCell; +use std::fs::OpenOptions; +use std::io::Write; use std::path::Path; use std::process::Command; use std::rc::Rc; -use std::thread; +use std::sync::{Arc, Mutex}; +use std::{fs, thread}; use tokio::runtime::Runtime; struct FlatpakChangesInfo { @@ -47,8 +51,10 @@ impl FlatpakChangesInfo { } pub fn flatpak_process_update( - system_refs_for_upgrade_vec: &Vec, - user_refs_for_upgrade_vec: &Vec, + system_refs_for_upgrade_vec_opt: Option<&Vec>, + user_refs_for_upgrade_vec_opt: Option<&Vec>, + system_refs_for_upgrade_vec_all: &Vec, + user_refs_for_upgrade_vec_all: &Vec, window: adw::ApplicationWindow, retry_signal_action: &SimpleAction, ) { @@ -61,48 +67,83 @@ pub fn flatpak_process_update( total_installed_size: 0, }; - let flatpak_system_installation = libflatpak::Installation::new_system(cancellable).unwrap(); - let flatpak_system_transaction = - libflatpak::Transaction::for_installation(&flatpak_system_installation, cancellable) - .unwrap(); - for flatpak_row in system_refs_for_upgrade_vec { - flatpak_changes_struct.add_system(); - // - let installed_size_installed = flatpak_row.flatref_installed_size_installed(); - let installed_size_remote = flatpak_row.flatref_installed_size_installed(); - let installed_download_size = flatpak_row.flatref_download_size(); - let ref_format = flatpak_row.flatref_ref_format(); - // - flatpak_changes_struct.decrease_total_installed_size_by(installed_size_installed); - flatpak_changes_struct.increase_total_installed_size_by(installed_size_remote); - // - flatpak_changes_struct.increase_total_download_size_by(installed_download_size); - // - flatpak_system_transaction - .add_update(&ref_format, &[], None) - .unwrap(); - } + let mut system_refs_for_upgrade_vec = Vec::new(); - let flatpak_user_installation = libflatpak::Installation::new_user(cancellable).unwrap(); - let flatpak_user_transaction = - libflatpak::Transaction::for_installation(&flatpak_user_installation, cancellable).unwrap(); - for flatpak_row in user_refs_for_upgrade_vec { - flatpak_changes_struct.add_user(); - // - let installed_size_installed = flatpak_row.flatref_installed_size_installed(); - let installed_size_remote = flatpak_row.flatref_installed_size_installed(); - let installed_download_size = flatpak_row.flatref_download_size(); - let ref_format = flatpak_row.flatref_ref_format(); - // - flatpak_changes_struct.decrease_total_installed_size_by(installed_size_installed); - flatpak_changes_struct.increase_total_installed_size_by(installed_size_remote); - // - flatpak_changes_struct.increase_total_download_size_by(installed_download_size); - // - flatpak_user_transaction - .add_update(&ref_format, &[], None) - .unwrap(); - } + match system_refs_for_upgrade_vec_opt { + Some(t) => { + for flatpak_row in t { + flatpak_changes_struct.add_system(); + // + let installed_size_installed = flatpak_row.flatref_installed_size_installed(); + let installed_size_remote = flatpak_row.flatref_installed_size_installed(); + let installed_download_size = flatpak_row.flatref_download_size(); + let ref_format = flatpak_row.flatref_ref_format(); + // + flatpak_changes_struct.decrease_total_installed_size_by(installed_size_installed); + flatpak_changes_struct.increase_total_installed_size_by(installed_size_remote); + // + flatpak_changes_struct.increase_total_download_size_by(installed_download_size); + // + system_refs_for_upgrade_vec.push(ref_format); + } + } + None => { + for flatpak_row in system_refs_for_upgrade_vec_all { + flatpak_changes_struct.add_system(); + // + let installed_size_installed = flatpak_row.flatref_installed_size_installed(); + let installed_size_remote = flatpak_row.flatref_installed_size_installed(); + let installed_download_size = flatpak_row.flatref_download_size(); + let ref_format = flatpak_row.flatref_ref_format(); + // + flatpak_changes_struct.decrease_total_installed_size_by(installed_size_installed); + flatpak_changes_struct.increase_total_installed_size_by(installed_size_remote); + // + flatpak_changes_struct.increase_total_download_size_by(installed_download_size); + // + system_refs_for_upgrade_vec.push(ref_format); + } + } + }; + + let mut user_refs_for_upgrade_vec = Vec::new(); + + match user_refs_for_upgrade_vec_opt { + Some(t) => { + for flatpak_row in t { + flatpak_changes_struct.add_user(); + // + let installed_size_installed = flatpak_row.flatref_installed_size_installed(); + let installed_size_remote = flatpak_row.flatref_installed_size_installed(); + let installed_download_size = flatpak_row.flatref_download_size(); + let ref_format = flatpak_row.flatref_ref_format(); + // + flatpak_changes_struct.decrease_total_installed_size_by(installed_size_installed); + flatpak_changes_struct.increase_total_installed_size_by(installed_size_remote); + // + flatpak_changes_struct.increase_total_download_size_by(installed_download_size); + // + user_refs_for_upgrade_vec.push(ref_format); + } + } + None => { + for flatpak_row in user_refs_for_upgrade_vec_all { + flatpak_changes_struct.add_user(); + // + let installed_size_installed = flatpak_row.flatref_installed_size_installed(); + let installed_size_remote = flatpak_row.flatref_installed_size_installed(); + let installed_download_size = flatpak_row.flatref_download_size(); + let ref_format = flatpak_row.flatref_ref_format(); + // + flatpak_changes_struct.decrease_total_installed_size_by(installed_size_installed); + flatpak_changes_struct.increase_total_installed_size_by(installed_size_remote); + // + flatpak_changes_struct.increase_total_download_size_by(installed_download_size); + // + user_refs_for_upgrade_vec.push(ref_format); + } + } + }; let flatpak_confirm_dialog_child_box = Box::builder().orientation(Orientation::Vertical).build(); @@ -177,34 +218,133 @@ pub fn flatpak_process_update( .clone() .choose(None::<&gio::Cancellable>, move |choice| { if choice == "flatpak_confirm_dialog_confirm" { - //flatpak_full_upgrade_from_socket(window, &retry_signal_action0); + flatpak_run_transactions( + system_refs_for_upgrade_vec, + user_refs_for_upgrade_vec, + window, + &retry_signal_action0, + ); } }); } -fn flatpak_full_upgrade_from_socket( +fn flatpak_run_transactions( + system_refs_for_upgrade_vec: Vec, + user_refs_for_upgrade_vec: Vec, window: adw::ApplicationWindow, retry_signal_action: &SimpleAction, ) { - let (upgrade_percent_sender, upgrade_percent_receiver) = async_channel::unbounded::(); - let upgrade_percent_sender = upgrade_percent_sender.clone(); - let (upgrade_status_sender, upgrade_status_receiver) = async_channel::unbounded::(); - let upgrade_status_sender = upgrade_status_sender.clone(); - let upgrade_status_sender_clone0 = upgrade_status_sender.clone(); + let (transaction_percent_sender, transaction_percent_receiver) = + async_channel::unbounded::(); + let transaction_percent_sender = transaction_percent_sender.clone(); + let (transaction_status_sender, transaction_status_receiver) = + async_channel::unbounded::(); + let transaction_status_sender = transaction_status_sender.clone(); + + thread::spawn(move || { + let cancellable_no = libflatpak::gio::Cancellable::NONE; + + let transaction_status_sender0 = transaction_status_sender.clone(); + let transaction_percent_sender0 = transaction_percent_sender.clone(); + + let transaction_run_closure = + move |transaction: &libflatpak::Transaction, + transaction_operation: &libflatpak::TransactionOperation, + transaction_progress: &libflatpak::TransactionProgress| { + let transaction_status_sender = transaction_status_sender0.clone(); + let transaction_percent_sender = transaction_percent_sender0.clone(); + transaction_progress.connect_changed(clone!(@strong transaction_progress, @strong transaction_operation => move |_| { + let status_message = format!("{}: {}\n{}: {}\n{}: {}/{}\n{}: {}", t!("flatpak_ref"), transaction_operation.get_ref().unwrap_or(libflatpak::glib::GString::from_string_unchecked("Unknown".to_owned())), t!("flatpak_status") ,transaction_progress.status().unwrap_or(libflatpak::glib::GString::from_string_unchecked("Unknown".to_owned())), t!("flatpak_transaction_bytes_transferred"), convert(transaction_progress.bytes_transferred() as f64), convert(transaction_operation.download_size() as f64), t!("flatpak_transaction_installed_size"), convert(transaction_operation.installed_size() as f64)); + transaction_status_sender.send_blocking(status_message).expect("transaction_status_receiver closed!"); + transaction_percent_sender.send_blocking(transaction_progress.progress().try_into().unwrap_or(0)).expect("transaction_percent_receiver closed!"); + })); + }; + + // + + let flatpak_system_installation = + libflatpak::Installation::new_system(cancellable_no).unwrap(); + let flatpak_system_transaction = + libflatpak::Transaction::for_installation(&flatpak_system_installation, cancellable_no) + .unwrap(); + + for ref_format in system_refs_for_upgrade_vec { + flatpak_system_transaction + .add_update(&ref_format, &[], None) + .unwrap(); + } + + flatpak_system_transaction.connect_new_operation(transaction_run_closure.clone()); + + match flatpak_system_transaction.run(cancellable_no) { + Ok(_) => {} + Err(e) => { + transaction_status_sender + .send_blocking(e.to_string()) + .expect("transaction_sync_status_receiver closed"); + transaction_status_sender + .send_blocking("FN_OVERRIDE_FAILED".to_owned()) + .expect("transaction_sync_status_receiver closed"); + panic!("{}", e); + } + } + + // + + let flatpak_user_installation = libflatpak::Installation::new_user(cancellable_no).unwrap(); + let flatpak_user_transaction = + libflatpak::Transaction::for_installation(&flatpak_user_installation, cancellable_no) + .unwrap(); + + flatpak_user_transaction.connect_new_operation(transaction_run_closure); + + for ref_format in user_refs_for_upgrade_vec { + flatpak_user_transaction + .add_update(&ref_format, &[], None) + .unwrap(); + } + + match flatpak_user_transaction.run(cancellable_no) { + Ok(_) => { + transaction_status_sender + .send_blocking("FN_OVERRIDE_SUCCESSFUL".to_owned()) + .expect("transaction_sync_status_receiver closed"); + } + Err(e) => { + transaction_status_sender + .send_blocking(e.to_string()) + .expect("transaction_sync_status_receiver closed"); + transaction_status_sender + .send_blocking("FN_OVERRIDE_FAILED".to_owned()) + .expect("transaction_sync_status_receiver closed"); + panic!("{}", e); + } + } + }); let log_file_path = format!( - "/tmp/pika-flatpak-upgrade_{}.log", + "/tmp/pika-flatpak-transaction_{}.log", chrono::offset::Local::now().format("%Y-%m-%d_%H:%M") ); + let log_file_path_clone0 = log_file_path.clone(); - let flatpak_upgrade_dialog_child_box = + if !Path::new(&log_file_path).exists() { + match fs::File::create(&log_file_path) { + Ok(_) => {} + Err(_) => { + eprintln!("Warning: {} file couldn't be created", log_file_path); + } + }; + } + + let flatpak_transaction_dialog_child_box = Box::builder().orientation(Orientation::Vertical).build(); - let flatpak_upgrade_dialog_progress_bar = + let flatpak_transaction_dialog_progress_bar = ProgressBar::builder().show_text(true).hexpand(true).build(); - let flatpak_upgrade_dialog_spinner = Spinner::builder() + let flatpak_transaction_dialog_spinner = Spinner::builder() .hexpand(true) .valign(Align::Start) .halign(Align::Center) @@ -213,31 +353,31 @@ fn flatpak_full_upgrade_from_socket( .width_request(128) .build(); - flatpak_upgrade_dialog_child_box.append(&flatpak_upgrade_dialog_spinner); - flatpak_upgrade_dialog_child_box.append(&flatpak_upgrade_dialog_progress_bar); + flatpak_transaction_dialog_child_box.append(&flatpak_transaction_dialog_spinner); + flatpak_transaction_dialog_child_box.append(&flatpak_transaction_dialog_progress_bar); - let flatpak_upgrade_dialog = adw::MessageDialog::builder() + let flatpak_transaction_dialog = adw::MessageDialog::builder() .transient_for(&window) - .extra_child(&flatpak_upgrade_dialog_child_box) - .heading(t!("flatpak_upgrade_dialog_heading")) + .extra_child(&flatpak_transaction_dialog_child_box) + .heading(t!("flatpak_transaction_dialog_heading")) .width_request(500) .build(); - flatpak_upgrade_dialog.add_response( - "flatpak_upgrade_dialog_ok", - &t!("flatpak_upgrade_dialog_ok_label").to_string(), + flatpak_transaction_dialog.add_response( + "flatpak_transaction_dialog_ok", + &t!("flatpak_transaction_dialog_ok_label").to_string(), ); - let flatpak_upgrade_dialog_child_box_done = + let flatpak_transaction_dialog_child_box_done = Box::builder().orientation(Orientation::Vertical).build(); - let flatpak_upgrade_log_image = Image::builder() + let flatpak_transaction_log_image = Image::builder() .pixel_size(128) .halign(Align::Center) .build(); - let flatpak_upgrade_log_button = Button::builder() - .label(t!("flatpak_upgrade_dialog_open_log_file_label")) + let flatpak_transaction_log_button = Button::builder() + .label(t!("flatpak_transaction_dialog_open_log_file_label")) .halign(Align::Center) .margin_start(15) .margin_end(15) @@ -245,69 +385,79 @@ fn flatpak_full_upgrade_from_socket( .margin_bottom(15) .build(); - flatpak_upgrade_dialog_child_box_done.append(&flatpak_upgrade_log_image); - flatpak_upgrade_dialog_child_box_done.append(&flatpak_upgrade_log_button); + flatpak_transaction_dialog_child_box_done.append(&flatpak_transaction_log_image); + flatpak_transaction_dialog_child_box_done.append(&flatpak_transaction_log_button); - flatpak_upgrade_dialog.set_response_enabled("flatpak_upgrade_dialog_ok", false); - flatpak_upgrade_dialog.set_close_response("flatpak_upgrade_dialog_ok"); + flatpak_transaction_dialog.set_response_enabled("flatpak_transaction_dialog_ok", false); + flatpak_transaction_dialog.set_close_response("flatpak_transaction_dialog_ok"); - let upgrade_percent_server_context = MainContext::default(); + let transaction_percent_server_context = MainContext::default(); // The main loop executes the asynchronous block - upgrade_percent_server_context.spawn_local(clone!( + transaction_percent_server_context.spawn_local(clone!( #[weak] - flatpak_upgrade_dialog_progress_bar, + flatpak_transaction_dialog_progress_bar, async move { - while let Ok(state) = upgrade_percent_receiver.recv().await { - match state.as_ref() { - "FN_OVERRIDE_SUCCESSFUL" => {} - _ => match state.parse::() { - Ok(p) => flatpak_upgrade_dialog_progress_bar.set_fraction(p / 100.0), - Err(_) => {} - }, - } + while let Ok(state) = transaction_percent_receiver.recv().await { + flatpak_transaction_dialog_progress_bar.set_fraction((state as f32 / 100.0).into()); } } )); - let upgrade_status_server_context = MainContext::default(); + let transaction_status_server_context = MainContext::default(); // The main loop executes the asynchronous block - upgrade_status_server_context.spawn_local(clone!( + transaction_status_server_context.spawn_local(clone!( #[weak] - flatpak_upgrade_dialog, + flatpak_transaction_dialog, #[weak] - flatpak_upgrade_dialog_child_box, + flatpak_transaction_dialog_child_box, #[strong] - flatpak_upgrade_dialog_child_box_done, + flatpak_transaction_dialog_child_box_done, #[strong] - flatpak_upgrade_log_image, + flatpak_transaction_log_image, async move { - while let Ok(state) = upgrade_status_receiver.recv().await { + while let Ok(state) = transaction_status_receiver.recv().await { match state.as_ref() { "FN_OVERRIDE_SUCCESSFUL" => { - flatpak_upgrade_dialog_child_box.set_visible(false); - flatpak_upgrade_log_image.set_icon_name(Some("face-cool-symbolic")); - flatpak_upgrade_dialog - .set_extra_child(Some(&flatpak_upgrade_dialog_child_box_done)); - flatpak_upgrade_dialog.set_title(Some( - &t!("flatpak_upgrade_dialog_status_successful").to_string(), + flatpak_transaction_dialog_child_box.set_visible(false); + flatpak_transaction_log_image.set_icon_name(Some("face-cool-symbolic")); + flatpak_transaction_dialog + .set_extra_child(Some(&flatpak_transaction_dialog_child_box_done)); + flatpak_transaction_dialog.set_title(Some( + &t!("flatpak_transaction_dialog_status_successful").to_string(), )); - flatpak_upgrade_dialog - .set_response_enabled("flatpak_upgrade_dialog_ok", true); + flatpak_transaction_dialog + .set_response_enabled("flatpak_transaction_dialog_ok", true); } "FN_OVERRIDE_FAILED" => { - flatpak_upgrade_dialog_child_box.set_visible(false); - flatpak_upgrade_log_image.set_icon_name(Some("dialog-error-symbolic")); - flatpak_upgrade_dialog - .set_extra_child(Some(&flatpak_upgrade_dialog_child_box_done)); - flatpak_upgrade_dialog.set_title(Some( - &t!("flatpak_upgrade_dialog_status_failed").to_string(), + flatpak_transaction_dialog_child_box.set_visible(false); + flatpak_transaction_log_image.set_icon_name(Some("dialog-error-symbolic")); + flatpak_transaction_dialog + .set_extra_child(Some(&flatpak_transaction_dialog_child_box_done)); + flatpak_transaction_dialog.set_title(Some( + &t!("flatpak_transaction_dialog_status_failed").to_string(), )); - flatpak_upgrade_dialog - .set_response_enabled("flatpak_upgrade_dialog_ok", true); - flatpak_upgrade_dialog - .set_response_enabled("flatpak_upgrade_dialog_open_log_file", true); + flatpak_transaction_dialog + .set_response_enabled("flatpak_transaction_dialog_ok", true); + flatpak_transaction_dialog + .set_response_enabled("flatpak_transaction_dialog_open_log_file", true); + } + _ => { + flatpak_transaction_dialog.set_body(&state); + let mut log_file = OpenOptions::new() + .write(true) + .append(true) + .open(&log_file_path) + .unwrap(); + + if let Err(e) = writeln!( + log_file, + "[{}] {}", + chrono::offset::Local::now().format("%Y/%m/%d_%H:%M"), + state + ) { + eprintln!("Couldn't write to file: {}", e); + } } - _ => flatpak_upgrade_dialog.set_body(&state), } } } @@ -315,15 +465,15 @@ fn flatpak_full_upgrade_from_socket( let retry_signal_action0 = retry_signal_action.clone(); - flatpak_upgrade_log_button.connect_clicked(move |_| { + flatpak_transaction_log_button.connect_clicked(move |_| { let _ = Command::new("xdg-open") .arg(log_file_path_clone0.to_owned()) .spawn(); }); - flatpak_upgrade_dialog.choose(None::<&gio::Cancellable>, move |choice| { + flatpak_transaction_dialog.choose(None::<&gio::Cancellable>, move |choice| { match choice.as_str() { - "flatpak_upgrade_dialog_ok" => { + "flatpak_transaction_dialog_ok" => { retry_signal_action0.activate(None); } _ => {}