From ca87b0d1777e51ef45284a73fc63b8b70aa7f48d Mon Sep 17 00:00:00 2001 From: Hellx2 Date: Sat, 24 Aug 2024 15:06:00 +1000 Subject: [PATCH] Fix safety, remove static mutables. --- src/packages/browse.rs | 242 +++++++++++++++++++----------------- src/packages/installed.rs | 252 +++++++++++++++++++------------------ src/packages/mod.rs | 30 ++++- src/packages/updates.rs | 254 ++++++++++++++++++++------------------ 4 files changed, 425 insertions(+), 353 deletions(-) diff --git a/src/packages/browse.rs b/src/packages/browse.rs index ac96a42..3adea9b 100644 --- a/src/packages/browse.rs +++ b/src/packages/browse.rs @@ -5,16 +5,10 @@ use gtk::{ Button, PositionType, }; use itertools::Itertools; -use std::process::Command; +use std::{process::Command, rc::Rc, sync::Mutex}; use gtk::{Align, ListBox, ScrolledWindow, SearchBar, SearchEntry}; -pub static mut FULL_PKG_LIST: Vec = vec![]; -pub static mut PACKAGES_LIST: Option = None; -pub static mut PKG_LIST: Vec = vec![]; -pub static mut PKG_LIST_INDEX: usize = 0; -pub static mut PKG_LISTBOX: Option = None; - pub fn browse() -> gtk::Box { // The main box that encompasses everything else let packages_box = gtk::Box::builder() @@ -48,21 +42,26 @@ pub fn browse() -> gtk::Box { */ let skip_list = ["available", "installed", "last"]; + let pkg_list_index_mut = Rc::new(Mutex::new(0)); + let mut pkg_list_index = pkg_list_index_mut.lock().expect("Failed to lock mutex."); + /* Initialize the packages list, needs to be a static mutable variable to allow usage with GTK functions. */ - unsafe { - PACKAGES_LIST = Some( - ListBox::builder() - .css_classes(["packages-list"]) - .valign(Align::Center) - .halign(Align::Fill) - .hexpand(true) - .build(), - ); + let packages_list_mut = Rc::new(Mutex::new( + ListBox::builder() + .css_classes(["packages-list"]) + .valign(Align::Center) + .halign(Align::Fill) + .hexpand(true) + .build(), + )); - FULL_PKG_LIST = String::from_utf8( + let packages_list = packages_list_mut.lock().expect("Failed to lock mutex."); + + let full_pkg_list: Rc> = Rc::new( + String::from_utf8( Command::new("dnf") .args(["list", "--available"]) .output() @@ -88,103 +87,116 @@ pub fn browse() -> gtk::Box { && !skip_list.contains(&x.to_lowercase().trim()) }) .dedup() - .collect(); + .collect(), + ); - PKG_LIST = FULL_PKG_LIST.clone(); + let pkg_list_mut = Rc::new(Mutex::new((*full_pkg_list).clone())); + let pkg_list = pkg_list_mut.lock().expect("Failed to lock mutex."); - /* - Gets the list of packages, which also needs to be static - mutable for the same reason as PACKAGES_LIST, and remove - the description and architecture details, and also filter - out the lines that aren't actually packages. - */ + /* + Gets the list of packages, which also needs to be static + mutable for the same reason as PACKAGES_LIST, and remove + the description and architecture details, and also filter + out the lines that aren't actually packages. + */ + /* + Add buttons for the first 50 packages in the list, to + keep the RAM usage to a minimum and not have every single + package and a button for it in the memory at the same time, + which has caused as much as 1.4GB of RAM usage during tests. + */ + for pkg in full_pkg_list[0..(50.min(full_pkg_list.len()))].iter() { + *pkg_list_index += 1; + let pkg_btn = Button::builder() + .label(pkg) + .css_classes(["package-name"]) + .build(); - /* - Add buttons for the first 50 packages in the list, to - keep the RAM usage to a minimum and not have every single - package and a button for it in the memory at the same time, - which has caused as much as 1.4GB of RAM usage during tests. - */ - for pkg in FULL_PKG_LIST[0..(50.min(FULL_PKG_LIST.len()))].iter() { - PKG_LIST_INDEX += 1; - let pkg_btn = Button::builder() - .label(pkg) - .css_classes(["package-name"]) - .build(); + pkg_btn.connect_clicked(|x| { + super::show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned())); + }); + packages_list.append(&pkg_btn) + } - pkg_btn.connect_clicked(|x| { - super::show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned())); - }); - PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn) - } - - /* - Create a scrollable area for the package list so that it - doesn't just go off the screen and hide all of the list. - */ - PKG_LISTBOX = Some( - ScrolledWindow::builder() - .css_classes(["package-list-scrollable"]) - .valign(Align::Center) - .halign(Align::Center) - .width_request(800) - .height_request(600) - .build(), - ); + /* + Create a scrollable area for the package list so that it + doesn't just go off the screen and hide all of the list. + */ + let pkg_listbox = Rc::new( + ScrolledWindow::builder() + .css_classes(["package-list-scrollable"]) + .valign(Align::Center) + .halign(Align::Center) + .width_request(800) + .height_request(600) + .build(), + ); + { + let pkg_list = pkg_list.clone(); + let packages_list = packages_list.clone(); + let pkg_list_index_mut = pkg_list_index_mut.clone(); /* Make it so that on reaching the end of the list it adds buttons for 50 more packages. */ - PKG_LISTBOX - .as_ref() - .unwrap() - .connect_edge_reached(|_, edge| { - if edge == PositionType::Bottom { - for i in - PKG_LIST[PKG_LIST_INDEX..((PKG_LIST_INDEX + 50).min(PKG_LIST.len()))].iter() - { - let pkg_btn = Button::builder() - .label(i.as_str()) - .css_classes(["package-name"]) - .build(); + pkg_listbox.connect_edge_reached(move |_, edge| { + let mut pkg_list_index = pkg_list_index_mut.lock().unwrap(); + if edge == PositionType::Bottom { + for i in + pkg_list[*pkg_list_index..((*pkg_list_index + 50).min(pkg_list.len()))].iter() + { + let pkg_btn = Button::builder() + .label(i.as_str()) + .css_classes(["package-name"]) + .build(); - pkg_btn.connect_clicked(|x| { - super::show_package( - x.label().map(|x| x.to_string()).unwrap_or("".to_owned()), - ); - }); - PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn); + pkg_btn.connect_clicked(|x| { + super::show_package( + x.label().map(|x| x.to_string()).unwrap_or("".to_owned()), + ); + }); + packages_list.append(&pkg_btn); - PKG_LIST_INDEX += 1; - } + *pkg_list_index += 1; } - }) - }; + } + }); + } - /* - Run the `search` function when the search is changed. - TODO: Make this asynchronous. - */ - pkgs_entry.connect_search_changed(move |x| { - let f = x.clone(); + { + let pkg_list_mut = pkg_list_mut.clone(); + let full_pkg_list = full_pkg_list.clone(); + let packages_list_mut = packages_list_mut.clone(); + let pkg_list_index_mut = pkg_list_index_mut.clone(); + let pkg_listbox = pkg_listbox.clone(); - unsafe { search(&f) } - }); + /* + Run the `search` function when the search is changed. + TODO: Make this asynchronous. + */ + pkgs_entry.connect_search_changed(move |x| { + let f = x.clone(); + + search( + &f, + pkg_list_mut.clone(), + full_pkg_list.clone(), + packages_list_mut.clone(), + pkg_list_index_mut.clone(), + pkg_listbox.clone(), + ); + }); + } pkgs_search.connect_entry(&pkgs_entry); // Add all of the parts to the box and return it. packages_box.append(&pkgs_search); packages_box.append(&pkgs_entry); - unsafe { - packages_box.append(PKG_LISTBOX.as_ref().unwrap()); + packages_box.append(&(*pkg_listbox)); - PKG_LISTBOX - .as_ref() - .unwrap() - .set_child(Some(PACKAGES_LIST.as_ref().unwrap())); - }; + pkg_listbox.set_child(Some(&(*packages_list))); packages_box } @@ -193,14 +205,22 @@ Function that takes a search entry and updates the package lists accordingly, skipping the values stated in `skip_list` */ -unsafe fn search(f: &SearchEntry) { +fn search( + f: &SearchEntry, + pkg_list_mut: Rc>>, + full_pkg_list: Rc>, + packages_list_mut: Rc>, + pkg_list_index_mut: Rc>, + pkg_listbox: Rc, +) { /* Reinitialize PKG_LIST with an extra predicate in the filter closure to ensure that the list of packages contains only ones that match the query */ - PKG_LIST = FULL_PKG_LIST + let mut pkg_list = pkg_list_mut.lock().expect("Failed to lock mutex."); + *pkg_list = full_pkg_list .iter() .filter(|x| { x.to_lowercase() @@ -215,30 +235,31 @@ unsafe fn search(f: &SearchEntry) { TODO: Check if this is necessary */ - std::mem::drop(PACKAGES_LIST.take()); + // std::mem::drop(PACKAGES_LIST.take()); + let mut packages_list = packages_list_mut.lock().expect("Failed to lock Mutex."); /* Reinitialize PACKAGES_LIST as a new ListBox. */ - PACKAGES_LIST = Some( - ListBox::builder() - .css_classes(["packages-list"]) - .valign(Align::Center) - .halign(Align::Fill) - .hexpand(true) - .build(), - ); + *packages_list = ListBox::builder() + .css_classes(["packages-list"]) + .valign(Align::Center) + .halign(Align::Fill) + .hexpand(true) + .build(); + + let mut pkg_list_index = pkg_list_index_mut.lock().expect("Failed to lock mutex."); // Reset the list index so that it starts at 0 in the package list - PKG_LIST_INDEX = 0; + *pkg_list_index = 0; /* Loop through the package list from 0 until either 50 or the length of the list, whichever is less, and make buttons in the list for each */ - for pkg in PKG_LIST[0..(50.min(PKG_LIST.len()))].iter() { - PKG_LIST_INDEX += 1; + for pkg in pkg_list[0..(50.min(pkg_list.len()))].iter() { + *pkg_list_index += 1; let pkg_btn = Button::builder() .label(pkg) .css_classes(["package-name"]) @@ -247,15 +268,12 @@ unsafe fn search(f: &SearchEntry) { pkg_btn.connect_clicked(|x| { super::show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned())); }); - PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn) + packages_list.append(&pkg_btn) } /* Reinitialise PKG_LISTBOX since the value it points to has now been changed. */ - PKG_LISTBOX - .as_ref() - .unwrap() - .set_child(Some(PACKAGES_LIST.as_ref().unwrap())); + pkg_listbox.set_child(Some(&(*packages_list))); } diff --git a/src/packages/installed.rs b/src/packages/installed.rs index 6c07ba4..c9734e8 100644 --- a/src/packages/installed.rs +++ b/src/packages/installed.rs @@ -1,25 +1,14 @@ +#![allow(clippy::assigning_clones)] + use gtk::{ prelude::{BoxExt, ButtonExt, EditableExt}, Button, PositionType, }; -use std::process::Command; - use itertools::Itertools; +use std::{process::Command, rc::Rc, sync::Mutex}; use gtk::{Align, ListBox, ScrolledWindow, SearchBar, SearchEntry}; -use super::show_package; - -pub static mut FULL_PKG_LIST: Vec = vec![]; -pub static mut PACKAGES_LIST: Option = None; -pub static mut PKG_LIST: Vec = vec![]; -pub static mut PKG_LIST_INDEX: usize = 0; -pub static mut PKG_LISTBOX: Option = None; - -/** -Function to initialise a box that contains a -list of installed packages. -*/ pub fn installed() -> gtk::Box { // The main box that encompasses everything else let packages_box = gtk::Box::builder() @@ -53,27 +42,26 @@ pub fn installed() -> gtk::Box { */ let skip_list = ["available", "installed", "last"]; + let pkg_list_index_mut = Rc::new(Mutex::new(0)); + let mut pkg_list_index = pkg_list_index_mut.lock().expect("Failed to lock mutex."); + /* Initialize the packages list, needs to be a static mutable variable to allow usage with GTK functions. */ - unsafe { - PACKAGES_LIST = Some( - ListBox::builder() - .css_classes(["packages-list"]) - .valign(Align::Center) - .halign(Align::Fill) - .hexpand(true) - .build(), - ); + let packages_list_mut = Rc::new(Mutex::new( + ListBox::builder() + .css_classes(["packages-list"]) + .valign(Align::Center) + .halign(Align::Fill) + .hexpand(true) + .build(), + )); - /* - Gets the list of packages, which also needs to be static - mutable for the same reason as PACKAGES_LIST, and remove - the description and architecture details, and also filter - out the lines that aren't actually packages. - */ - FULL_PKG_LIST = String::from_utf8( + let packages_list = packages_list_mut.lock().expect("Failed to lock mutex."); + + let full_pkg_list: Rc> = Rc::new( + String::from_utf8( Command::new("dnf") .args(["list", "--installed"]) .output() @@ -99,94 +87,116 @@ pub fn installed() -> gtk::Box { && !skip_list.contains(&x.to_lowercase().trim()) }) .dedup() - .collect::>(); + .collect(), + ); - /* - Add buttons for the first 50 packages in the list, to - keep the RAM usage to a minimum and not have every single - package and a button for it in the memory at the same time, - which has caused as much as 1.4GB of RAM usage during tests. - */ - for pkg in FULL_PKG_LIST[0..(50.min(FULL_PKG_LIST.len()))].iter() { - PKG_LIST_INDEX += 1; - let pkg_btn = Button::builder() - .label(pkg) - .css_classes(["package-name"]) - .build(); + let pkg_list_mut = Rc::new(Mutex::new((*full_pkg_list).clone())); + let pkg_list = pkg_list_mut.lock().expect("Failed to lock mutex."); - pkg_btn.connect_clicked(|x| { - show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned())); - }); - PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn) - } + /* + Gets the list of packages, which also needs to be static + mutable for the same reason as PACKAGES_LIST, and remove + the description and architecture details, and also filter + out the lines that aren't actually packages. + */ + /* + Add buttons for the first 50 packages in the list, to + keep the RAM usage to a minimum and not have every single + package and a button for it in the memory at the same time, + which has caused as much as 1.4GB of RAM usage during tests. + */ + for pkg in full_pkg_list[0..(50.min(full_pkg_list.len()))].iter() { + *pkg_list_index += 1; + let pkg_btn = Button::builder() + .label(pkg) + .css_classes(["package-name"]) + .build(); - /* - Create a scrollable area for the package list so that it - doesn't just go off the screen and hide all of the list. - */ - PKG_LISTBOX = Some( - ScrolledWindow::builder() - .css_classes(["package-list-scrollable"]) - .valign(Align::Center) - .halign(Align::Center) - .width_request(800) - .height_request(600) - .build(), - ); + pkg_btn.connect_clicked(|x| { + super::show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned())); + }); + packages_list.append(&pkg_btn) + } + /* + Create a scrollable area for the package list so that it + doesn't just go off the screen and hide all of the list. + */ + let pkg_listbox = Rc::new( + ScrolledWindow::builder() + .css_classes(["package-list-scrollable"]) + .valign(Align::Center) + .halign(Align::Center) + .width_request(800) + .height_request(600) + .build(), + ); + + { + let pkg_list = pkg_list.clone(); + let packages_list = packages_list.clone(); + let pkg_list_index_mut = pkg_list_index_mut.clone(); /* Make it so that on reaching the end of the list it adds buttons for 50 more packages. */ - PKG_LISTBOX - .as_ref() - .unwrap() - .connect_edge_reached(|_, edge| { - if edge == PositionType::Bottom { - (PKG_LIST_INDEX..((PKG_LIST_INDEX + 50).min(FULL_PKG_LIST.len()))).for_each( - |i| { - let pkg_btn = Button::builder() - .label(FULL_PKG_LIST[i].as_str()) - .css_classes(["package-name"]) - .build(); + pkg_listbox.connect_edge_reached(move |_, edge| { + let mut pkg_list_index = pkg_list_index_mut.lock().unwrap(); + if edge == PositionType::Bottom { + for i in + pkg_list[*pkg_list_index..((*pkg_list_index + 50).min(pkg_list.len()))].iter() + { + let pkg_btn = Button::builder() + .label(i.as_str()) + .css_classes(["package-name"]) + .build(); - pkg_btn.connect_clicked(|x| { - show_package( - x.label().map(|x| x.to_string()).unwrap_or("".to_owned()), - ); - }); - PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn); + pkg_btn.connect_clicked(|x| { + super::show_package( + x.label().map(|x| x.to_string()).unwrap_or("".to_owned()), + ); + }); + packages_list.append(&pkg_btn); - PKG_LIST_INDEX += 1; - }, - ); + *pkg_list_index += 1; } - }) - }; + } + }); + } - /* - Run the `search` function when the search is changed. - TODO: Make this asynchronous. - */ - pkgs_entry.connect_search_changed(move |x| { - let f = x.clone(); + { + let pkg_list_mut = pkg_list_mut.clone(); + let full_pkg_list = full_pkg_list.clone(); + let packages_list_mut = packages_list_mut.clone(); + let pkg_list_index_mut = pkg_list_index_mut.clone(); + let pkg_listbox = pkg_listbox.clone(); - unsafe { search(&f) } - }); + /* + Run the `search` function when the search is changed. + TODO: Make this asynchronous. + */ + pkgs_entry.connect_search_changed(move |x| { + let f = x.clone(); + + search( + &f, + pkg_list_mut.clone(), + full_pkg_list.clone(), + packages_list_mut.clone(), + pkg_list_index_mut.clone(), + pkg_listbox.clone(), + ); + }); + } pkgs_search.connect_entry(&pkgs_entry); // Add all of the parts to the box and return it. packages_box.append(&pkgs_search); packages_box.append(&pkgs_entry); - unsafe { - packages_box.append(PKG_LISTBOX.as_ref().unwrap()); + packages_box.append(&(*pkg_listbox)); - PKG_LISTBOX - .as_ref() - .unwrap() - .set_child(Some(PACKAGES_LIST.as_ref().unwrap())); - }; + pkg_listbox.set_child(Some(&(*packages_list))); packages_box } @@ -195,14 +205,22 @@ Function that takes a search entry and updates the package lists accordingly, skipping the values stated in `skip_list` */ -unsafe fn search(f: &SearchEntry) { +fn search( + f: &SearchEntry, + pkg_list_mut: Rc>>, + full_pkg_list: Rc>, + packages_list_mut: Rc>, + pkg_list_index_mut: Rc>, + pkg_listbox: Rc, +) { /* Reinitialize PKG_LIST with an extra predicate in the filter closure to ensure that the list of packages contains only ones that match the query */ - PKG_LIST = FULL_PKG_LIST + let mut pkg_list = pkg_list_mut.lock().expect("Failed to lock mutex."); + *pkg_list = full_pkg_list .iter() .filter(|x| { x.to_lowercase() @@ -217,47 +235,45 @@ unsafe fn search(f: &SearchEntry) { TODO: Check if this is necessary */ - std::mem::drop(PACKAGES_LIST.take()); + // std::mem::drop(PACKAGES_LIST.take()); + let mut packages_list = packages_list_mut.lock().expect("Failed to lock Mutex."); /* Reinitialize PACKAGES_LIST as a new ListBox. */ - PACKAGES_LIST = Some( - ListBox::builder() - .css_classes(["packages-list"]) - .valign(Align::Center) - .halign(Align::Fill) - .hexpand(true) - .build(), - ); + *packages_list = ListBox::builder() + .css_classes(["packages-list"]) + .valign(Align::Center) + .halign(Align::Fill) + .hexpand(true) + .build(); + + let mut pkg_list_index = pkg_list_index_mut.lock().expect("Failed to lock mutex."); // Reset the list index so that it starts at 0 in the package list - PKG_LIST_INDEX = 0; + *pkg_list_index = 0; /* Loop through the package list from 0 until either 50 or the length of the list, whichever is less, and make buttons in the list for each */ - for pkg in PKG_LIST[0..(50.min(PKG_LIST.len()))].iter() { - PKG_LIST_INDEX += 1; + for pkg in pkg_list[0..(50.min(pkg_list.len()))].iter() { + *pkg_list_index += 1; let pkg_btn = Button::builder() .label(pkg) .css_classes(["package-name"]) .build(); pkg_btn.connect_clicked(|x| { - show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned())); + super::show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned())); }); - PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn) + packages_list.append(&pkg_btn) } /* Reinitialise PKG_LISTBOX since the value it points to has now been changed. */ - PKG_LISTBOX - .as_ref() - .unwrap() - .set_child(Some(PACKAGES_LIST.as_ref().unwrap())); + pkg_listbox.set_child(Some(&(*packages_list))); } diff --git a/src/packages/mod.rs b/src/packages/mod.rs index f790964..6d83e64 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -77,7 +77,9 @@ fn show_package(pkg: String) { ) .unwrap(); + let mut is_updatable = true; let mut is_installed = false; + /* Get the correct info for whether the package is installed or not and use the data from @@ -96,6 +98,7 @@ fn show_package(pkg: String) { .0 .trim() } else { + is_updatable = false; info.split_once("Available Packages").unwrap().1.trim() }; @@ -103,6 +106,7 @@ fn show_package(pkg: String) { let mut version = "1.0.0"; let mut size = "Unknown"; let mut license = "Unknown"; + let mut architecture = "Unknown"; /* Sort through each of the lines and add the @@ -112,17 +116,35 @@ fn show_package(pkg: String) { */ for line in info.lines() { if let Some(e) = line.trim().strip_prefix("Version") { - version = e.split_once(':').unwrap().1.trim(); + version = e.split_once(':').unwrap().1.trim() } else if let Some(e) = line.trim().strip_prefix("Size") { - size = e.split_once(':').unwrap().1.trim(); + size = e.split_once(':').unwrap().1.trim() } else if let Some(e) = line.trim().strip_prefix("License") { - license = e.split_once(':').unwrap().1.trim(); + license = e.split_once(':').unwrap().1.trim() + } else if let Some(e) = line.trim().strip_prefix("Architecture") { + architecture = e.split_once(':').unwrap().1.trim() } + if line.trim().starts_with("Description") { break; } } + is_updatable = is_updatable + && architecture + == info + .split_once("Available Packages") + .unwrap() + .1 + .trim() + .lines() + .find(|x| x.starts_with("Architecture")) + .expect("Failed to get available architecture") + .split_once(':') + .unwrap() + .1 + .trim(); + /* Get the description of the package, removing the colons and whitespace at the start of each @@ -244,7 +266,7 @@ fn show_package(pkg: String) { buttons_box.append(&install_btn); } - if unsafe { updates::FULL_PKG_LIST.contains(&pkg) } { + if is_updatable { let update_btn = Button::builder().label("Update").build(); /* diff --git a/src/packages/updates.rs b/src/packages/updates.rs index 5791c99..db37664 100644 --- a/src/packages/updates.rs +++ b/src/packages/updates.rs @@ -1,25 +1,14 @@ +#![allow(clippy::assigning_clones)] + use gtk::{ prelude::{BoxExt, ButtonExt, EditableExt}, Button, PositionType, }; -use std::process::Command; - use itertools::Itertools; +use std::{process::Command, rc::Rc, sync::Mutex}; use gtk::{Align, ListBox, ScrolledWindow, SearchBar, SearchEntry}; -use super::show_package; - -pub static mut FULL_PKG_LIST: Vec = vec![]; -pub static mut PACKAGES_LIST: Option = None; -pub static mut PKG_LIST: Vec = vec![]; -pub static mut PKG_LIST_INDEX: usize = 0; -pub static mut PKG_LISTBOX: Option = None; - -/** -Function to initialise a box that contains a -list of updatable packages. -*/ pub fn updates() -> gtk::Box { // The main box that encompasses everything else let packages_box = gtk::Box::builder() @@ -53,29 +42,28 @@ pub fn updates() -> gtk::Box { */ let skip_list = ["available", "installed", "last"]; + let pkg_list_index_mut = Rc::new(Mutex::new(0)); + let mut pkg_list_index = pkg_list_index_mut.lock().expect("Failed to lock mutex."); + /* Initialize the packages list, needs to be a static mutable variable to allow usage with GTK functions. */ - unsafe { - PACKAGES_LIST = Some( - ListBox::builder() - .css_classes(["packages-list"]) - .valign(Align::Center) - .halign(Align::Fill) - .hexpand(true) - .build(), - ); + let packages_list_mut = Rc::new(Mutex::new( + ListBox::builder() + .css_classes(["packages-list"]) + .valign(Align::Center) + .halign(Align::Fill) + .hexpand(true) + .build(), + )); - /* - Gets the list of packages, which also needs to be static - mutable for the same reason as PACKAGES_LIST, and remove - the description and architecture details, and also filter - out the lines that aren't actually packages. - */ - FULL_PKG_LIST = String::from_utf8( + let packages_list = packages_list_mut.lock().expect("Failed to lock mutex."); + + let full_pkg_list: Rc> = Rc::new( + String::from_utf8( Command::new("dnf") - .args(["check-update"]) + .arg("check-update") .output() .unwrap() .stdout, @@ -99,94 +87,116 @@ pub fn updates() -> gtk::Box { && !skip_list.contains(&x.to_lowercase().trim()) }) .dedup() - .collect::>(); + .collect(), + ); - /* - Add buttons for the first 50 packages in the list, to - keep the RAM usage to a minimum and not have every single - package and a button for it in the memory at the same time, - which has caused as much as 1.4GB of RAM usage during tests. - */ - for pkg in FULL_PKG_LIST[0..(50.min(FULL_PKG_LIST.len()))].iter() { - PKG_LIST_INDEX += 1; - let pkg_btn = Button::builder() - .label(pkg) - .css_classes(["package-name"]) - .build(); + let pkg_list_mut = Rc::new(Mutex::new((*full_pkg_list).clone())); + let pkg_list = pkg_list_mut.lock().expect("Failed to lock mutex."); - pkg_btn.connect_clicked(|x| { - show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned())); - }); - PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn) - } + /* + Gets the list of packages, which also needs to be static + mutable for the same reason as PACKAGES_LIST, and remove + the description and architecture details, and also filter + out the lines that aren't actually packages. + */ + /* + Add buttons for the first 50 packages in the list, to + keep the RAM usage to a minimum and not have every single + package and a button for it in the memory at the same time, + which has caused as much as 1.4GB of RAM usage during tests. + */ + for pkg in full_pkg_list[0..(50.min(full_pkg_list.len()))].iter() { + *pkg_list_index += 1; + let pkg_btn = Button::builder() + .label(pkg) + .css_classes(["package-name"]) + .build(); - /* - Create a scrollable area for the package list so that it - doesn't just go off the screen and hide all of the list. - */ - PKG_LISTBOX = Some( - ScrolledWindow::builder() - .css_classes(["package-list-scrollable"]) - .valign(Align::Center) - .halign(Align::Center) - .width_request(800) - .height_request(600) - .build(), - ); + pkg_btn.connect_clicked(|x| { + super::show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned())); + }); + packages_list.append(&pkg_btn) + } + /* + Create a scrollable area for the package list so that it + doesn't just go off the screen and hide all of the list. + */ + let pkg_listbox = Rc::new( + ScrolledWindow::builder() + .css_classes(["package-list-scrollable"]) + .valign(Align::Center) + .halign(Align::Center) + .width_request(800) + .height_request(600) + .build(), + ); + + { + let pkg_list = pkg_list.clone(); + let packages_list = packages_list.clone(); + let pkg_list_index_mut = pkg_list_index_mut.clone(); /* Make it so that on reaching the end of the list it adds buttons for 50 more packages. */ - PKG_LISTBOX - .as_ref() - .unwrap() - .connect_edge_reached(|_, edge| { - if edge == PositionType::Bottom { - (PKG_LIST_INDEX..((PKG_LIST_INDEX + 50).min(FULL_PKG_LIST.len()))).for_each( - |i| { - let pkg_btn = Button::builder() - .label(FULL_PKG_LIST[i].as_str()) - .css_classes(["package-name"]) - .build(); + pkg_listbox.connect_edge_reached(move |_, edge| { + let mut pkg_list_index = pkg_list_index_mut.lock().unwrap(); + if edge == PositionType::Bottom { + for i in + pkg_list[*pkg_list_index..((*pkg_list_index + 50).min(pkg_list.len()))].iter() + { + let pkg_btn = Button::builder() + .label(i.as_str()) + .css_classes(["package-name"]) + .build(); - pkg_btn.connect_clicked(|x| { - show_package( - x.label().map(|x| x.to_string()).unwrap_or("".to_owned()), - ); - }); - PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn); + pkg_btn.connect_clicked(|x| { + super::show_package( + x.label().map(|x| x.to_string()).unwrap_or("".to_owned()), + ); + }); + packages_list.append(&pkg_btn); - PKG_LIST_INDEX += 1; - }, - ); + *pkg_list_index += 1; } - }) - }; + } + }); + } - /* - Run the `search` function when the search is changed. - TODO: Make this asynchronous. - */ - pkgs_entry.connect_search_changed(move |x| { - let f = x.clone(); + { + let pkg_list_mut = pkg_list_mut.clone(); + let full_pkg_list = full_pkg_list.clone(); + let packages_list_mut = packages_list_mut.clone(); + let pkg_list_index_mut = pkg_list_index_mut.clone(); + let pkg_listbox = pkg_listbox.clone(); - unsafe { search(&f) } - }); + /* + Run the `search` function when the search is changed. + TODO: Make this asynchronous. + */ + pkgs_entry.connect_search_changed(move |x| { + let f = x.clone(); + + search( + &f, + pkg_list_mut.clone(), + full_pkg_list.clone(), + packages_list_mut.clone(), + pkg_list_index_mut.clone(), + pkg_listbox.clone(), + ); + }); + } pkgs_search.connect_entry(&pkgs_entry); // Add all of the parts to the box and return it. packages_box.append(&pkgs_search); packages_box.append(&pkgs_entry); - unsafe { - packages_box.append(PKG_LISTBOX.as_ref().unwrap()); + packages_box.append(&(*pkg_listbox)); - PKG_LISTBOX - .as_ref() - .unwrap() - .set_child(Some(PACKAGES_LIST.as_ref().unwrap())); - }; + pkg_listbox.set_child(Some(&(*packages_list))); packages_box } @@ -195,14 +205,22 @@ Function that takes a search entry and updates the package lists accordingly, skipping the values stated in `skip_list` */ -unsafe fn search(f: &SearchEntry) { +fn search( + f: &SearchEntry, + pkg_list_mut: Rc>>, + full_pkg_list: Rc>, + packages_list_mut: Rc>, + pkg_list_index_mut: Rc>, + pkg_listbox: Rc, +) { /* Reinitialize PKG_LIST with an extra predicate in the filter closure to ensure that the list of packages contains only ones that match the query */ - PKG_LIST = FULL_PKG_LIST + let mut pkg_list = pkg_list_mut.lock().expect("Failed to lock mutex."); + *pkg_list = full_pkg_list .iter() .filter(|x| { x.to_lowercase() @@ -217,47 +235,45 @@ unsafe fn search(f: &SearchEntry) { TODO: Check if this is necessary */ - std::mem::drop(PACKAGES_LIST.take()); + // std::mem::drop(PACKAGES_LIST.take()); + let mut packages_list = packages_list_mut.lock().expect("Failed to lock Mutex."); /* Reinitialize PACKAGES_LIST as a new ListBox. */ - PACKAGES_LIST = Some( - ListBox::builder() - .css_classes(["packages-list"]) - .valign(Align::Center) - .halign(Align::Fill) - .hexpand(true) - .build(), - ); + *packages_list = ListBox::builder() + .css_classes(["packages-list"]) + .valign(Align::Center) + .halign(Align::Fill) + .hexpand(true) + .build(); + + let mut pkg_list_index = pkg_list_index_mut.lock().expect("Failed to lock mutex."); // Reset the list index so that it starts at 0 in the package list - PKG_LIST_INDEX = 0; + *pkg_list_index = 0; /* Loop through the package list from 0 until either 50 or the length of the list, whichever is less, and make buttons in the list for each */ - for pkg in PKG_LIST[0..(50.min(PKG_LIST.len()))].iter() { - PKG_LIST_INDEX += 1; + for pkg in pkg_list[0..(50.min(pkg_list.len()))].iter() { + *pkg_list_index += 1; let pkg_btn = Button::builder() .label(pkg) .css_classes(["package-name"]) .build(); pkg_btn.connect_clicked(|x| { - show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned())); + super::show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned())); }); - PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn) + packages_list.append(&pkg_btn) } /* Reinitialise PKG_LISTBOX since the value it points to has now been changed. */ - PKG_LISTBOX - .as_ref() - .unwrap() - .set_child(Some(PACKAGES_LIST.as_ref().unwrap())); + pkg_listbox.set_child(Some(&(*packages_list))); }