Add documentation to installed.rs
This commit is contained in:
parent
f0ae517e46
commit
3ddff9f710
2 changed files with 383 additions and 66 deletions
|
@ -1,6 +1,6 @@
|
|||
use gtk::{
|
||||
prelude::{BoxExt, ButtonExt, EditableExt},
|
||||
Button, PositionType, Window,
|
||||
prelude::{BoxExt, ButtonExt, EditableExt, GtkWindowExt},
|
||||
Button, Label, PositionType, Window,
|
||||
};
|
||||
use std::process::Command;
|
||||
|
||||
|
@ -156,7 +156,96 @@ fn show_installed_package(pkg: String) {
|
|||
return;
|
||||
}
|
||||
|
||||
let _pkg_window = Window::builder().build();
|
||||
let pkg_window = Window::builder().title(&pkg).build();
|
||||
let pkg_box = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.build();
|
||||
|
||||
let info = String::from_utf8(
|
||||
Command::new("dnf")
|
||||
.args(["info", pkg.as_str()])
|
||||
.output()
|
||||
.unwrap()
|
||||
.stdout,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let info = if info.contains("Installed Packages") {
|
||||
let a = info.split_once("Installed Packages").unwrap().1.trim();
|
||||
a.split_once("Available Packages")
|
||||
.unwrap_or((a, ""))
|
||||
.0
|
||||
.trim()
|
||||
} else {
|
||||
info.split_once("Available Packages").unwrap().0.trim()
|
||||
};
|
||||
|
||||
let mut version = "1.0.0";
|
||||
let mut size = "Unknown";
|
||||
let mut license = "Unknown";
|
||||
|
||||
for line in info.lines() {
|
||||
if let Some(e) = line.trim().strip_prefix("Version") {
|
||||
version = e.split_once(":").unwrap().1.trim();
|
||||
} else if let Some(e) = line.trim().strip_prefix("Size") {
|
||||
size = e.split_once(":").unwrap().1.trim();
|
||||
} else if let Some(e) = line.trim().strip_prefix("License") {
|
||||
license = e.split_once(":").unwrap().1.trim();
|
||||
}
|
||||
if line.trim().starts_with("Description") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let description = info
|
||||
.split_once("Description")
|
||||
.unwrap()
|
||||
.1
|
||||
.split_once(":")
|
||||
.unwrap()
|
||||
.1
|
||||
.trim()
|
||||
.lines()
|
||||
.map(|x| x.split_once(":").unwrap_or(("", x)).1.trim().to_owned() + "\n")
|
||||
.collect::<String>();
|
||||
|
||||
pkg_box.append(
|
||||
&Label::builder()
|
||||
.label("Description: ".to_owned() + description.as_str())
|
||||
.build(),
|
||||
);
|
||||
pkg_box.append(
|
||||
&Label::builder()
|
||||
.label("Version: ".to_owned() + version)
|
||||
.build(),
|
||||
);
|
||||
pkg_box.append(&Label::builder().label("Size: ".to_owned() + size).build());
|
||||
pkg_box.append(
|
||||
&Label::builder()
|
||||
.label("License: ".to_owned() + license)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let buttons_box = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Horizontal)
|
||||
.css_classes(["buttons-box"])
|
||||
.build();
|
||||
|
||||
let install_btn = Button::builder().label("Install").build();
|
||||
|
||||
install_btn.connect_clicked(move |_| {
|
||||
Command::new("pkexec")
|
||||
.args(["dnf", "install", pkg.as_str()])
|
||||
.spawn()
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
buttons_box.append(&install_btn);
|
||||
|
||||
pkg_box.append(&buttons_box);
|
||||
|
||||
pkg_window.set_child(Some(&pkg_box));
|
||||
pkg_window.present();
|
||||
}
|
||||
|
||||
unsafe fn search(f: &SearchEntry, skip_list: &Vec<&str>) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use gtk::{
|
||||
prelude::{BoxExt, ButtonExt, Cast, EditableExt, ListBoxRowExt},
|
||||
Button, PositionType, Window,
|
||||
prelude::{BoxExt, ButtonExt, EditableExt, GtkWindowExt},
|
||||
Button, Label, PositionType, Window,
|
||||
};
|
||||
use std::process::Command;
|
||||
|
||||
|
@ -9,8 +9,12 @@ use gtk::{Align, ListBox, ScrolledWindow, SearchBar, SearchEntry};
|
|||
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 {
|
||||
// The main box that encompasses everything else
|
||||
let packages_box = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.css_classes(["packages-box"])
|
||||
|
@ -19,6 +23,7 @@ pub fn installed() -> gtk::Box {
|
|||
.hexpand(true)
|
||||
.build();
|
||||
|
||||
// Search bar
|
||||
let pkgs_search = SearchBar::builder()
|
||||
.halign(Align::Fill)
|
||||
.valign(Align::Start)
|
||||
|
@ -35,8 +40,16 @@ pub fn installed() -> gtk::Box {
|
|||
.hexpand(true)
|
||||
.build();
|
||||
|
||||
/*
|
||||
List of names to skip when searching, this is to remove the
|
||||
labels "Available Packages", "Last refreshed at ..." and "Installed Packages"
|
||||
*/
|
||||
let skip_list = vec!["available", "installed", "last"];
|
||||
|
||||
/*
|
||||
Initialize the packages list, needs to be a static
|
||||
mutable variable to allow usage with GTK functions.
|
||||
*/
|
||||
unsafe {
|
||||
PACKAGES_LIST = Some(
|
||||
ListBox::builder()
|
||||
|
@ -48,16 +61,23 @@ pub fn installed() -> gtk::Box {
|
|||
)
|
||||
};
|
||||
|
||||
// TODO: Make this part asynchronous so that the app opens faster
|
||||
let pkg_list = String::from_utf8(
|
||||
Command::new("dnf")
|
||||
.args(["list", "--installed"])
|
||||
.output()
|
||||
.unwrap()
|
||||
.stdout,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
unsafe {
|
||||
// TODO: Make this part asynchronous so that the app opens faster
|
||||
let pkg_list = String::from_utf8(
|
||||
Command::new("dnf")
|
||||
.args(["list", "--installed"])
|
||||
.output()
|
||||
.unwrap()
|
||||
.stdout,
|
||||
)
|
||||
.unwrap();
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
PKG_LIST = pkg_list
|
||||
.lines()
|
||||
.map(|x| {
|
||||
|
@ -76,11 +96,15 @@ pub fn installed() -> gtk::Box {
|
|||
&& !x.contains("=")
|
||||
&& !skip_list.contains(&x.to_lowercase().trim())
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
.collect();
|
||||
|
||||
unsafe {
|
||||
for pkg in PKG_LIST[0..50].to_vec() {
|
||||
/*
|
||||
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 PKG_LIST[0..(50.min(PKG_LIST.len()))].to_vec() {
|
||||
PKG_LIST_INDEX += 1;
|
||||
let pkg_btn = Button::builder()
|
||||
.label(pkg)
|
||||
|
@ -92,72 +116,276 @@ pub fn installed() -> gtk::Box {
|
|||
});
|
||||
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn)
|
||||
}
|
||||
}
|
||||
|
||||
pkgs_entry.connect_search_changed(move |x| {
|
||||
let x = x.clone();
|
||||
/*
|
||||
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(),
|
||||
);
|
||||
|
||||
// TODO: Possibly refactor to use `dnf search` instead of a filter function.
|
||||
unsafe {
|
||||
PACKAGES_LIST.as_ref().unwrap().set_filter_func(move |y| {
|
||||
y.child()
|
||||
.unwrap()
|
||||
.downcast::<Button>()
|
||||
.unwrap()
|
||||
.label()
|
||||
.map(|x| x.to_string())
|
||||
.unwrap_or("".to_string())
|
||||
.contains(x.text().to_string().as_str())
|
||||
})
|
||||
};
|
||||
});
|
||||
/*
|
||||
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_INDEX..((PKG_LIST_INDEX + 50).min(PKG_LIST.len())) {
|
||||
let pkg_btn = Button::builder()
|
||||
.label(PKG_LIST[i].as_str())
|
||||
.css_classes(["package-name"])
|
||||
.build();
|
||||
|
||||
let pkg_listbox = 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| {
|
||||
show_installed_package(
|
||||
x.label().map(|x| x.to_string()).unwrap_or("".to_owned()),
|
||||
);
|
||||
});
|
||||
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn);
|
||||
|
||||
pkg_listbox.connect_edge_reached(|_, edge| {
|
||||
if edge == PositionType::Bottom {
|
||||
unsafe {
|
||||
for i in PKG_LIST_INDEX..(PKG_LIST_INDEX + 50) {
|
||||
let pkg_btn = Button::builder()
|
||||
.label(PKG_LIST[i].as_str())
|
||||
.css_classes(["package-name"])
|
||||
.build();
|
||||
|
||||
pkg_btn.connect_clicked(|x| {
|
||||
show_installed_package(
|
||||
x.label().map(|x| x.to_string()).unwrap_or("".to_owned()),
|
||||
);
|
||||
});
|
||||
PACKAGES_LIST.as_ref().unwrap().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();
|
||||
|
||||
unsafe { search(&f, &skip_list) }
|
||||
});
|
||||
|
||||
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.set_child(Some(unsafe { PACKAGES_LIST.as_ref().unwrap() }));
|
||||
|
||||
PKG_LISTBOX
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.set_child(Some(PACKAGES_LIST.as_ref().unwrap()));
|
||||
};
|
||||
packages_box
|
||||
}
|
||||
|
||||
/**
|
||||
Function called when a package is clicked on in the list,
|
||||
creates a new window over the top that displays the details
|
||||
of the package clicked on.
|
||||
|
||||
# Params
|
||||
`pkg`: The name of the package that was clicked on.
|
||||
*/
|
||||
fn show_installed_package(pkg: String) {
|
||||
/*
|
||||
Prevent creating empty windows and having failed
|
||||
`dnf` commands.
|
||||
*/
|
||||
if pkg.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let _pkg_window = Window::builder().build();
|
||||
// Create a window for the package details.
|
||||
let pkg_window = Window::builder().title(&pkg).build();
|
||||
let pkg_box = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.build();
|
||||
|
||||
// Use `dnf` to get information about the package.
|
||||
let info = String::from_utf8(
|
||||
Command::new("dnf")
|
||||
.args(["info", pkg.as_str()])
|
||||
.output()
|
||||
.unwrap()
|
||||
.stdout,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
/*
|
||||
Get the correct info for whether the package
|
||||
is installed or not and use the data from
|
||||
the installed one where available to be more
|
||||
accurate.
|
||||
|
||||
TODO: Make this differentiate between them
|
||||
and add a remove button instead of an
|
||||
install button on this menu.
|
||||
*/
|
||||
let info = if info.contains("Installed Packages") {
|
||||
let a = info.split_once("Installed Packages").unwrap().1.trim();
|
||||
a.split_once("Available Packages")
|
||||
.unwrap_or((a, ""))
|
||||
.0
|
||||
.trim()
|
||||
} else {
|
||||
info.split_once("Available Packages").unwrap().0.trim()
|
||||
};
|
||||
|
||||
// The details of the package, with default values just in case.
|
||||
let mut version = "1.0.0";
|
||||
let mut size = "Unknown";
|
||||
let mut license = "Unknown";
|
||||
|
||||
/*
|
||||
Sort through each of the lines and add the
|
||||
values where necessary, breaking upon reaching
|
||||
the description, since that can be a multi-line
|
||||
value.
|
||||
*/
|
||||
for line in info.lines() {
|
||||
if let Some(e) = line.trim().strip_prefix("Version") {
|
||||
version = e.split_once(":").unwrap().1.trim();
|
||||
} else if let Some(e) = line.trim().strip_prefix("Size") {
|
||||
size = e.split_once(":").unwrap().1.trim();
|
||||
} else if let Some(e) = line.trim().strip_prefix("License") {
|
||||
license = e.split_once(":").unwrap().1.trim();
|
||||
}
|
||||
if line.trim().starts_with("Description") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Get the description of the package, removing
|
||||
the colons and whitespace at the start of each
|
||||
line.
|
||||
*/
|
||||
let description = info
|
||||
.split_once("Description")
|
||||
.unwrap()
|
||||
.1
|
||||
.split_once(":")
|
||||
.unwrap()
|
||||
.1
|
||||
.trim()
|
||||
.lines()
|
||||
.map(|x| x.split_once(":").unwrap_or(("", x)).1.trim().to_owned() + "\n")
|
||||
.collect::<String>();
|
||||
|
||||
/*
|
||||
Append all of the parts to the box.
|
||||
*/
|
||||
pkg_box.append(
|
||||
&Label::builder()
|
||||
.label("Description: ".to_owned() + description.as_str())
|
||||
.build(),
|
||||
);
|
||||
pkg_box.append(
|
||||
&Label::builder()
|
||||
.label("Version: ".to_owned() + version)
|
||||
.build(),
|
||||
);
|
||||
pkg_box.append(&Label::builder().label("Size: ".to_owned() + size).build());
|
||||
pkg_box.append(
|
||||
&Label::builder()
|
||||
.label("License: ".to_owned() + license)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let buttons_box = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Horizontal)
|
||||
.css_classes(["buttons-box"])
|
||||
.build();
|
||||
|
||||
let install_btn = Button::builder().label("Install").build();
|
||||
|
||||
/*
|
||||
Make it so that when the install button
|
||||
is clicked the package is installed.
|
||||
*/
|
||||
install_btn.connect_clicked(move |_| {
|
||||
Command::new("pkexec")
|
||||
.args(["dnf", "install", pkg.as_str()])
|
||||
.spawn()
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
buttons_box.append(&install_btn);
|
||||
|
||||
pkg_box.append(&buttons_box);
|
||||
|
||||
// Append the box to the window and show it.
|
||||
pkg_window.set_child(Some(&pkg_box));
|
||||
pkg_window.present();
|
||||
}
|
||||
|
||||
unsafe fn search(f: &SearchEntry, skip_list: &Vec<&str>) {
|
||||
let pkg_list = String::from_utf8(
|
||||
Command::new("dnf")
|
||||
.args(["list", "--installed"])
|
||||
.output()
|
||||
.unwrap()
|
||||
.stdout,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
PKG_LIST = pkg_list
|
||||
.lines()
|
||||
.map(|x| {
|
||||
let y = x.to_string();
|
||||
let b = y
|
||||
.split_whitespace()
|
||||
.next()
|
||||
.unwrap_or("")
|
||||
.split(".")
|
||||
.next()
|
||||
.unwrap();
|
||||
b.to_string()
|
||||
})
|
||||
.filter(|x| {
|
||||
!x.trim().is_empty()
|
||||
&& !x.contains("=")
|
||||
&& !skip_list.contains(&x.to_lowercase().trim())
|
||||
&& x.to_lowercase()
|
||||
.contains(f.text().to_string().to_lowercase().as_str())
|
||||
})
|
||||
.collect();
|
||||
|
||||
std::mem::drop(PACKAGES_LIST.take());
|
||||
|
||||
PACKAGES_LIST = Some(
|
||||
ListBox::builder()
|
||||
.css_classes(["packages-list"])
|
||||
.valign(Align::Center)
|
||||
.halign(Align::Fill)
|
||||
.hexpand(true)
|
||||
.build(),
|
||||
);
|
||||
|
||||
PKG_LIST_INDEX = 0;
|
||||
|
||||
for pkg in PKG_LIST[0..(50.min(PKG_LIST.len()))].to_vec() {
|
||||
PKG_LIST_INDEX += 1;
|
||||
let pkg_btn = Button::builder()
|
||||
.label(pkg)
|
||||
.css_classes(["package-name"])
|
||||
.build();
|
||||
|
||||
pkg_btn.connect_clicked(|x| {
|
||||
show_installed_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned()));
|
||||
});
|
||||
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn)
|
||||
}
|
||||
|
||||
PKG_LISTBOX
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.set_child(Some(PACKAGES_LIST.as_ref().unwrap()));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue