Fix safety, remove static mutables.

This commit is contained in:
Hellx2 2024-08-24 15:06:00 +10:00
parent 941f66662e
commit ca87b0d177
4 changed files with 425 additions and 353 deletions

View file

@ -5,16 +5,10 @@ use gtk::{
Button, PositionType, Button, PositionType,
}; };
use itertools::Itertools; use itertools::Itertools;
use std::process::Command; use std::{process::Command, rc::Rc, sync::Mutex};
use gtk::{Align, ListBox, ScrolledWindow, SearchBar, SearchEntry}; use gtk::{Align, ListBox, ScrolledWindow, SearchBar, SearchEntry};
pub static mut FULL_PKG_LIST: Vec<String> = vec![];
pub static mut PACKAGES_LIST: Option<ListBox> = None;
pub static mut PKG_LIST: Vec<String> = vec![];
pub static mut PKG_LIST_INDEX: usize = 0;
pub static mut PKG_LISTBOX: Option<ScrolledWindow> = None;
pub fn browse() -> gtk::Box { pub fn browse() -> gtk::Box {
// The main box that encompasses everything else // The main box that encompasses everything else
let packages_box = gtk::Box::builder() let packages_box = gtk::Box::builder()
@ -48,21 +42,26 @@ pub fn browse() -> gtk::Box {
*/ */
let skip_list = ["available", "installed", "last"]; 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 Initialize the packages list, needs to be a static
mutable variable to allow usage with GTK functions. mutable variable to allow usage with GTK functions.
*/ */
unsafe { let packages_list_mut = Rc::new(Mutex::new(
PACKAGES_LIST = Some( ListBox::builder()
ListBox::builder() .css_classes(["packages-list"])
.css_classes(["packages-list"]) .valign(Align::Center)
.valign(Align::Center) .halign(Align::Fill)
.halign(Align::Fill) .hexpand(true)
.hexpand(true) .build(),
.build(), ));
);
FULL_PKG_LIST = String::from_utf8( let packages_list = packages_list_mut.lock().expect("Failed to lock mutex.");
let full_pkg_list: Rc<Vec<String>> = Rc::new(
String::from_utf8(
Command::new("dnf") Command::new("dnf")
.args(["list", "--available"]) .args(["list", "--available"])
.output() .output()
@ -88,103 +87,116 @@ pub fn browse() -> gtk::Box {
&& !skip_list.contains(&x.to_lowercase().trim()) && !skip_list.contains(&x.to_lowercase().trim())
}) })
.dedup() .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 Gets the list of packages, which also needs to be static
mutable for the same reason as PACKAGES_LIST, and remove mutable for the same reason as PACKAGES_LIST, and remove
the description and architecture details, and also filter the description and architecture details, and also filter
out the lines that aren't actually packages. 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();
/* pkg_btn.connect_clicked(|x| {
Add buttons for the first 50 packages in the list, to super::show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned()));
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, packages_list.append(&pkg_btn)
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())); Create a scrollable area for the package list so that it
}); doesn't just go off the screen and hide all of the list.
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn) */
} let pkg_listbox = Rc::new(
ScrolledWindow::builder()
/* .css_classes(["package-list-scrollable"])
Create a scrollable area for the package list so that it .valign(Align::Center)
doesn't just go off the screen and hide all of the list. .halign(Align::Center)
*/ .width_request(800)
PKG_LISTBOX = Some( .height_request(600)
ScrolledWindow::builder() .build(),
.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 Make it so that on reaching the end of the list it adds
buttons for 50 more packages. buttons for 50 more packages.
*/ */
PKG_LISTBOX pkg_listbox.connect_edge_reached(move |_, edge| {
.as_ref() let mut pkg_list_index = pkg_list_index_mut.lock().unwrap();
.unwrap() if edge == PositionType::Bottom {
.connect_edge_reached(|_, edge| { for i in
if edge == PositionType::Bottom { pkg_list[*pkg_list_index..((*pkg_list_index + 50).min(pkg_list.len()))].iter()
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())
let pkg_btn = Button::builder() .css_classes(["package-name"])
.label(i.as_str()) .build();
.css_classes(["package-name"])
.build();
pkg_btn.connect_clicked(|x| { pkg_btn.connect_clicked(|x| {
super::show_package( super::show_package(
x.label().map(|x| x.to_string()).unwrap_or("".to_owned()), x.label().map(|x| x.to_string()).unwrap_or("".to_owned()),
); );
}); });
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn); packages_list.append(&pkg_btn);
PKG_LIST_INDEX += 1; *pkg_list_index += 1;
}
} }
}) }
}; });
}
/* {
Run the `search` function when the search is changed. let pkg_list_mut = pkg_list_mut.clone();
TODO: Make this asynchronous. let full_pkg_list = full_pkg_list.clone();
*/ let packages_list_mut = packages_list_mut.clone();
pkgs_entry.connect_search_changed(move |x| { let pkg_list_index_mut = pkg_list_index_mut.clone();
let f = x.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); pkgs_search.connect_entry(&pkgs_entry);
// Add all of the parts to the box and return it. // Add all of the parts to the box and return it.
packages_box.append(&pkgs_search); packages_box.append(&pkgs_search);
packages_box.append(&pkgs_entry); packages_box.append(&pkgs_entry);
unsafe { packages_box.append(&(*pkg_listbox));
packages_box.append(PKG_LISTBOX.as_ref().unwrap());
PKG_LISTBOX pkg_listbox.set_child(Some(&(*packages_list)));
.as_ref()
.unwrap()
.set_child(Some(PACKAGES_LIST.as_ref().unwrap()));
};
packages_box packages_box
} }
@ -193,14 +205,22 @@ Function that takes a search entry and updates
the package lists accordingly, skipping the values the package lists accordingly, skipping the values
stated in `skip_list` stated in `skip_list`
*/ */
unsafe fn search(f: &SearchEntry) { fn search(
f: &SearchEntry,
pkg_list_mut: Rc<Mutex<Vec<String>>>,
full_pkg_list: Rc<Vec<String>>,
packages_list_mut: Rc<Mutex<ListBox>>,
pkg_list_index_mut: Rc<Mutex<usize>>,
pkg_listbox: Rc<ScrolledWindow>,
) {
/* /*
Reinitialize PKG_LIST with an extra predicate Reinitialize PKG_LIST with an extra predicate
in the filter closure to ensure that the list in the filter closure to ensure that the list
of packages contains only ones that match the of packages contains only ones that match the
query 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() .iter()
.filter(|x| { .filter(|x| {
x.to_lowercase() x.to_lowercase()
@ -215,30 +235,31 @@ unsafe fn search(f: &SearchEntry) {
TODO: Check if this is necessary 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. Reinitialize PACKAGES_LIST as a new ListBox.
*/ */
PACKAGES_LIST = Some( *packages_list = ListBox::builder()
ListBox::builder() .css_classes(["packages-list"])
.css_classes(["packages-list"]) .valign(Align::Center)
.valign(Align::Center) .halign(Align::Fill)
.halign(Align::Fill) .hexpand(true)
.hexpand(true) .build();
.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 // 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 Loop through the package list from 0 until
either 50 or the length of the list, whichever either 50 or the length of the list, whichever
is less, and make buttons in the list for each is less, and make buttons in the list for each
*/ */
for pkg in PKG_LIST[0..(50.min(PKG_LIST.len()))].iter() { for pkg in pkg_list[0..(50.min(pkg_list.len()))].iter() {
PKG_LIST_INDEX += 1; *pkg_list_index += 1;
let pkg_btn = Button::builder() let pkg_btn = Button::builder()
.label(pkg) .label(pkg)
.css_classes(["package-name"]) .css_classes(["package-name"])
@ -247,15 +268,12 @@ unsafe fn search(f: &SearchEntry) {
pkg_btn.connect_clicked(|x| { pkg_btn.connect_clicked(|x| {
super::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 Reinitialise PKG_LISTBOX since the value
it points to has now been changed. it points to has now been changed.
*/ */
PKG_LISTBOX pkg_listbox.set_child(Some(&(*packages_list)));
.as_ref()
.unwrap()
.set_child(Some(PACKAGES_LIST.as_ref().unwrap()));
} }

View file

@ -1,25 +1,14 @@
#![allow(clippy::assigning_clones)]
use gtk::{ use gtk::{
prelude::{BoxExt, ButtonExt, EditableExt}, prelude::{BoxExt, ButtonExt, EditableExt},
Button, PositionType, Button, PositionType,
}; };
use std::process::Command;
use itertools::Itertools; use itertools::Itertools;
use std::{process::Command, rc::Rc, sync::Mutex};
use gtk::{Align, ListBox, ScrolledWindow, SearchBar, SearchEntry}; use gtk::{Align, ListBox, ScrolledWindow, SearchBar, SearchEntry};
use super::show_package;
pub static mut FULL_PKG_LIST: Vec<String> = vec![];
pub static mut PACKAGES_LIST: Option<ListBox> = None;
pub static mut PKG_LIST: Vec<String> = vec![];
pub static mut PKG_LIST_INDEX: usize = 0;
pub static mut PKG_LISTBOX: Option<ScrolledWindow> = None;
/**
Function to initialise a box that contains a
list of installed packages.
*/
pub fn installed() -> gtk::Box { pub fn installed() -> gtk::Box {
// The main box that encompasses everything else // The main box that encompasses everything else
let packages_box = gtk::Box::builder() let packages_box = gtk::Box::builder()
@ -53,27 +42,26 @@ pub fn installed() -> gtk::Box {
*/ */
let skip_list = ["available", "installed", "last"]; 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 Initialize the packages list, needs to be a static
mutable variable to allow usage with GTK functions. mutable variable to allow usage with GTK functions.
*/ */
unsafe { let packages_list_mut = Rc::new(Mutex::new(
PACKAGES_LIST = Some( ListBox::builder()
ListBox::builder() .css_classes(["packages-list"])
.css_classes(["packages-list"]) .valign(Align::Center)
.valign(Align::Center) .halign(Align::Fill)
.halign(Align::Fill) .hexpand(true)
.hexpand(true) .build(),
.build(), ));
);
/* let packages_list = packages_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 let full_pkg_list: Rc<Vec<String>> = Rc::new(
the description and architecture details, and also filter String::from_utf8(
out the lines that aren't actually packages.
*/
FULL_PKG_LIST = String::from_utf8(
Command::new("dnf") Command::new("dnf")
.args(["list", "--installed"]) .args(["list", "--installed"])
.output() .output()
@ -99,94 +87,116 @@ pub fn installed() -> gtk::Box {
&& !skip_list.contains(&x.to_lowercase().trim()) && !skip_list.contains(&x.to_lowercase().trim())
}) })
.dedup() .dedup()
.collect::<Vec<String>>(); .collect(),
);
/* let pkg_list_mut = Rc::new(Mutex::new((*full_pkg_list).clone()));
Add buttons for the first 50 packages in the list, to let pkg_list = pkg_list_mut.lock().expect("Failed to lock mutex.");
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| { /*
show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned())); Gets the list of packages, which also needs to be static
}); mutable for the same reason as PACKAGES_LIST, and remove
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn) 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();
/* pkg_btn.connect_clicked(|x| {
Create a scrollable area for the package list so that it super::show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned()));
doesn't just go off the screen and hide all of the list. });
*/ packages_list.append(&pkg_btn)
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 Make it so that on reaching the end of the list it adds
buttons for 50 more packages. buttons for 50 more packages.
*/ */
PKG_LISTBOX pkg_listbox.connect_edge_reached(move |_, edge| {
.as_ref() let mut pkg_list_index = pkg_list_index_mut.lock().unwrap();
.unwrap() if edge == PositionType::Bottom {
.connect_edge_reached(|_, edge| { for i in
if edge == PositionType::Bottom { pkg_list[*pkg_list_index..((*pkg_list_index + 50).min(pkg_list.len()))].iter()
(PKG_LIST_INDEX..((PKG_LIST_INDEX + 50).min(FULL_PKG_LIST.len()))).for_each( {
|i| { let pkg_btn = Button::builder()
let pkg_btn = Button::builder() .label(i.as_str())
.label(FULL_PKG_LIST[i].as_str()) .css_classes(["package-name"])
.css_classes(["package-name"]) .build();
.build();
pkg_btn.connect_clicked(|x| { pkg_btn.connect_clicked(|x| {
show_package( super::show_package(
x.label().map(|x| x.to_string()).unwrap_or("".to_owned()), x.label().map(|x| x.to_string()).unwrap_or("".to_owned()),
); );
}); });
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn); packages_list.append(&pkg_btn);
PKG_LIST_INDEX += 1; *pkg_list_index += 1;
},
);
} }
}) }
}; });
}
/* {
Run the `search` function when the search is changed. let pkg_list_mut = pkg_list_mut.clone();
TODO: Make this asynchronous. let full_pkg_list = full_pkg_list.clone();
*/ let packages_list_mut = packages_list_mut.clone();
pkgs_entry.connect_search_changed(move |x| { let pkg_list_index_mut = pkg_list_index_mut.clone();
let f = x.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); pkgs_search.connect_entry(&pkgs_entry);
// Add all of the parts to the box and return it. // Add all of the parts to the box and return it.
packages_box.append(&pkgs_search); packages_box.append(&pkgs_search);
packages_box.append(&pkgs_entry); packages_box.append(&pkgs_entry);
unsafe { packages_box.append(&(*pkg_listbox));
packages_box.append(PKG_LISTBOX.as_ref().unwrap());
PKG_LISTBOX pkg_listbox.set_child(Some(&(*packages_list)));
.as_ref()
.unwrap()
.set_child(Some(PACKAGES_LIST.as_ref().unwrap()));
};
packages_box packages_box
} }
@ -195,14 +205,22 @@ Function that takes a search entry and updates
the package lists accordingly, skipping the values the package lists accordingly, skipping the values
stated in `skip_list` stated in `skip_list`
*/ */
unsafe fn search(f: &SearchEntry) { fn search(
f: &SearchEntry,
pkg_list_mut: Rc<Mutex<Vec<String>>>,
full_pkg_list: Rc<Vec<String>>,
packages_list_mut: Rc<Mutex<ListBox>>,
pkg_list_index_mut: Rc<Mutex<usize>>,
pkg_listbox: Rc<ScrolledWindow>,
) {
/* /*
Reinitialize PKG_LIST with an extra predicate Reinitialize PKG_LIST with an extra predicate
in the filter closure to ensure that the list in the filter closure to ensure that the list
of packages contains only ones that match the of packages contains only ones that match the
query 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() .iter()
.filter(|x| { .filter(|x| {
x.to_lowercase() x.to_lowercase()
@ -217,47 +235,45 @@ unsafe fn search(f: &SearchEntry) {
TODO: Check if this is necessary 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. Reinitialize PACKAGES_LIST as a new ListBox.
*/ */
PACKAGES_LIST = Some( *packages_list = ListBox::builder()
ListBox::builder() .css_classes(["packages-list"])
.css_classes(["packages-list"]) .valign(Align::Center)
.valign(Align::Center) .halign(Align::Fill)
.halign(Align::Fill) .hexpand(true)
.hexpand(true) .build();
.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 // 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 Loop through the package list from 0 until
either 50 or the length of the list, whichever either 50 or the length of the list, whichever
is less, and make buttons in the list for each is less, and make buttons in the list for each
*/ */
for pkg in PKG_LIST[0..(50.min(PKG_LIST.len()))].iter() { for pkg in pkg_list[0..(50.min(pkg_list.len()))].iter() {
PKG_LIST_INDEX += 1; *pkg_list_index += 1;
let pkg_btn = Button::builder() let pkg_btn = Button::builder()
.label(pkg) .label(pkg)
.css_classes(["package-name"]) .css_classes(["package-name"])
.build(); .build();
pkg_btn.connect_clicked(|x| { 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 Reinitialise PKG_LISTBOX since the value
it points to has now been changed. it points to has now been changed.
*/ */
PKG_LISTBOX pkg_listbox.set_child(Some(&(*packages_list)));
.as_ref()
.unwrap()
.set_child(Some(PACKAGES_LIST.as_ref().unwrap()));
} }

View file

@ -77,7 +77,9 @@ fn show_package(pkg: String) {
) )
.unwrap(); .unwrap();
let mut is_updatable = true;
let mut is_installed = false; let mut is_installed = false;
/* /*
Get the correct info for whether the package Get the correct info for whether the package
is installed or not and use the data from is installed or not and use the data from
@ -96,6 +98,7 @@ fn show_package(pkg: String) {
.0 .0
.trim() .trim()
} else { } else {
is_updatable = false;
info.split_once("Available Packages").unwrap().1.trim() 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 version = "1.0.0";
let mut size = "Unknown"; let mut size = "Unknown";
let mut license = "Unknown"; let mut license = "Unknown";
let mut architecture = "Unknown";
/* /*
Sort through each of the lines and add the Sort through each of the lines and add the
@ -112,17 +116,35 @@ fn show_package(pkg: String) {
*/ */
for line in info.lines() { for line in info.lines() {
if let Some(e) = line.trim().strip_prefix("Version") { 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") { } 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") { } 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") { if line.trim().starts_with("Description") {
break; 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 Get the description of the package, removing
the colons and whitespace at the start of each the colons and whitespace at the start of each
@ -244,7 +266,7 @@ fn show_package(pkg: String) {
buttons_box.append(&install_btn); buttons_box.append(&install_btn);
} }
if unsafe { updates::FULL_PKG_LIST.contains(&pkg) } { if is_updatable {
let update_btn = Button::builder().label("Update").build(); let update_btn = Button::builder().label("Update").build();
/* /*

View file

@ -1,25 +1,14 @@
#![allow(clippy::assigning_clones)]
use gtk::{ use gtk::{
prelude::{BoxExt, ButtonExt, EditableExt}, prelude::{BoxExt, ButtonExt, EditableExt},
Button, PositionType, Button, PositionType,
}; };
use std::process::Command;
use itertools::Itertools; use itertools::Itertools;
use std::{process::Command, rc::Rc, sync::Mutex};
use gtk::{Align, ListBox, ScrolledWindow, SearchBar, SearchEntry}; use gtk::{Align, ListBox, ScrolledWindow, SearchBar, SearchEntry};
use super::show_package;
pub static mut FULL_PKG_LIST: Vec<String> = vec![];
pub static mut PACKAGES_LIST: Option<ListBox> = None;
pub static mut PKG_LIST: Vec<String> = vec![];
pub static mut PKG_LIST_INDEX: usize = 0;
pub static mut PKG_LISTBOX: Option<ScrolledWindow> = None;
/**
Function to initialise a box that contains a
list of updatable packages.
*/
pub fn updates() -> gtk::Box { pub fn updates() -> gtk::Box {
// The main box that encompasses everything else // The main box that encompasses everything else
let packages_box = gtk::Box::builder() let packages_box = gtk::Box::builder()
@ -53,29 +42,28 @@ pub fn updates() -> gtk::Box {
*/ */
let skip_list = ["available", "installed", "last"]; 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 Initialize the packages list, needs to be a static
mutable variable to allow usage with GTK functions. mutable variable to allow usage with GTK functions.
*/ */
unsafe { let packages_list_mut = Rc::new(Mutex::new(
PACKAGES_LIST = Some( ListBox::builder()
ListBox::builder() .css_classes(["packages-list"])
.css_classes(["packages-list"]) .valign(Align::Center)
.valign(Align::Center) .halign(Align::Fill)
.halign(Align::Fill) .hexpand(true)
.hexpand(true) .build(),
.build(), ));
);
/* let packages_list = packages_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 let full_pkg_list: Rc<Vec<String>> = Rc::new(
the description and architecture details, and also filter String::from_utf8(
out the lines that aren't actually packages.
*/
FULL_PKG_LIST = String::from_utf8(
Command::new("dnf") Command::new("dnf")
.args(["check-update"]) .arg("check-update")
.output() .output()
.unwrap() .unwrap()
.stdout, .stdout,
@ -99,94 +87,116 @@ pub fn updates() -> gtk::Box {
&& !skip_list.contains(&x.to_lowercase().trim()) && !skip_list.contains(&x.to_lowercase().trim())
}) })
.dedup() .dedup()
.collect::<Vec<String>>(); .collect(),
);
/* let pkg_list_mut = Rc::new(Mutex::new((*full_pkg_list).clone()));
Add buttons for the first 50 packages in the list, to let pkg_list = pkg_list_mut.lock().expect("Failed to lock mutex.");
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| { /*
show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned())); Gets the list of packages, which also needs to be static
}); mutable for the same reason as PACKAGES_LIST, and remove
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn) 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();
/* pkg_btn.connect_clicked(|x| {
Create a scrollable area for the package list so that it super::show_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned()));
doesn't just go off the screen and hide all of the list. });
*/ packages_list.append(&pkg_btn)
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 Make it so that on reaching the end of the list it adds
buttons for 50 more packages. buttons for 50 more packages.
*/ */
PKG_LISTBOX pkg_listbox.connect_edge_reached(move |_, edge| {
.as_ref() let mut pkg_list_index = pkg_list_index_mut.lock().unwrap();
.unwrap() if edge == PositionType::Bottom {
.connect_edge_reached(|_, edge| { for i in
if edge == PositionType::Bottom { pkg_list[*pkg_list_index..((*pkg_list_index + 50).min(pkg_list.len()))].iter()
(PKG_LIST_INDEX..((PKG_LIST_INDEX + 50).min(FULL_PKG_LIST.len()))).for_each( {
|i| { let pkg_btn = Button::builder()
let pkg_btn = Button::builder() .label(i.as_str())
.label(FULL_PKG_LIST[i].as_str()) .css_classes(["package-name"])
.css_classes(["package-name"]) .build();
.build();
pkg_btn.connect_clicked(|x| { pkg_btn.connect_clicked(|x| {
show_package( super::show_package(
x.label().map(|x| x.to_string()).unwrap_or("".to_owned()), x.label().map(|x| x.to_string()).unwrap_or("".to_owned()),
); );
}); });
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn); packages_list.append(&pkg_btn);
PKG_LIST_INDEX += 1; *pkg_list_index += 1;
},
);
} }
}) }
}; });
}
/* {
Run the `search` function when the search is changed. let pkg_list_mut = pkg_list_mut.clone();
TODO: Make this asynchronous. let full_pkg_list = full_pkg_list.clone();
*/ let packages_list_mut = packages_list_mut.clone();
pkgs_entry.connect_search_changed(move |x| { let pkg_list_index_mut = pkg_list_index_mut.clone();
let f = x.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); pkgs_search.connect_entry(&pkgs_entry);
// Add all of the parts to the box and return it. // Add all of the parts to the box and return it.
packages_box.append(&pkgs_search); packages_box.append(&pkgs_search);
packages_box.append(&pkgs_entry); packages_box.append(&pkgs_entry);
unsafe { packages_box.append(&(*pkg_listbox));
packages_box.append(PKG_LISTBOX.as_ref().unwrap());
PKG_LISTBOX pkg_listbox.set_child(Some(&(*packages_list)));
.as_ref()
.unwrap()
.set_child(Some(PACKAGES_LIST.as_ref().unwrap()));
};
packages_box packages_box
} }
@ -195,14 +205,22 @@ Function that takes a search entry and updates
the package lists accordingly, skipping the values the package lists accordingly, skipping the values
stated in `skip_list` stated in `skip_list`
*/ */
unsafe fn search(f: &SearchEntry) { fn search(
f: &SearchEntry,
pkg_list_mut: Rc<Mutex<Vec<String>>>,
full_pkg_list: Rc<Vec<String>>,
packages_list_mut: Rc<Mutex<ListBox>>,
pkg_list_index_mut: Rc<Mutex<usize>>,
pkg_listbox: Rc<ScrolledWindow>,
) {
/* /*
Reinitialize PKG_LIST with an extra predicate Reinitialize PKG_LIST with an extra predicate
in the filter closure to ensure that the list in the filter closure to ensure that the list
of packages contains only ones that match the of packages contains only ones that match the
query 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() .iter()
.filter(|x| { .filter(|x| {
x.to_lowercase() x.to_lowercase()
@ -217,47 +235,45 @@ unsafe fn search(f: &SearchEntry) {
TODO: Check if this is necessary 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. Reinitialize PACKAGES_LIST as a new ListBox.
*/ */
PACKAGES_LIST = Some( *packages_list = ListBox::builder()
ListBox::builder() .css_classes(["packages-list"])
.css_classes(["packages-list"]) .valign(Align::Center)
.valign(Align::Center) .halign(Align::Fill)
.halign(Align::Fill) .hexpand(true)
.hexpand(true) .build();
.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 // 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 Loop through the package list from 0 until
either 50 or the length of the list, whichever either 50 or the length of the list, whichever
is less, and make buttons in the list for each is less, and make buttons in the list for each
*/ */
for pkg in PKG_LIST[0..(50.min(PKG_LIST.len()))].iter() { for pkg in pkg_list[0..(50.min(pkg_list.len()))].iter() {
PKG_LIST_INDEX += 1; *pkg_list_index += 1;
let pkg_btn = Button::builder() let pkg_btn = Button::builder()
.label(pkg) .label(pkg)
.css_classes(["package-name"]) .css_classes(["package-name"])
.build(); .build();
pkg_btn.connect_clicked(|x| { 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 Reinitialise PKG_LISTBOX since the value
it points to has now been changed. it points to has now been changed.
*/ */
PKG_LISTBOX pkg_listbox.set_child(Some(&(*packages_list)));
.as_ref()
.unwrap()
.set_child(Some(PACKAGES_LIST.as_ref().unwrap()));
} }