integrate client cli with server ui

This commit is contained in:
Ward from fusion-voyager-3 2024-06-27 00:30:40 +03:00
parent 3b7b69665b
commit 2e8b590ffd
12 changed files with 424 additions and 258 deletions

View File

@ -12,22 +12,10 @@ path = "src/gui/main.rs"
name = "pika_unixsocket_tools"
path = "src/pika_unixsocket_tools/lib.rs"
[[bin]]
name = "debug_server_update_percent"
path = "src/debug_server/update_percent.rs"
[[bin]]
name = "debug_server_update_status"
path = "src/debug_server/update_status.rs"
[[bin]]
name = "apt_update"
path = "src/apt_update/main.rs"
[[bin]]
name = "apt_get_upgradable"
path = "src/apt_get_upgradable/main.rs"
[dependencies]
adw = { version = "0.5.3", package = "libadwaita", features = ["v1_4"] }
gtk = { version = "0.7.3", package = "gtk4", features = ["v4_12"] }

View File

@ -1,55 +0,0 @@
use rust_apt::new_cache;
use rust_apt::cache::*;
use tokio::net::{UnixStream};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use serde::{Serialize};
#[derive(Serialize)]
struct AptPackageSocket {
name: String,
arch: String,
installed_version: String,
candidate_version: String
}
#[tokio::main]
async fn main() {
// Create upgradable list cache
let upgradable_cache = new_cache!().unwrap();
// Create pack sort from upgradable_cache
let upgradable_sort = PackageSort::default().upgradable().names();
for pkg in upgradable_cache.packages(&upgradable_sort) {
let package_struct = AptPackageSocket {
name: pkg.name().to_string(),
arch: pkg.arch().to_string(),
installed_version: pkg.installed().unwrap().version().to_string(),
candidate_version: pkg.candidate().unwrap().version().to_string()
};
// Path to the Unix socket file
let socket_path = "/tmp/pika_apt_get_upgradable.sock";
// Connect to the Unix socket
let mut stream = UnixStream::connect(socket_path).await.expect("Could not connect to server");
let message = serde_json::to_string(&package_struct).unwrap();
// Send the message to the server
stream.write_all(message.as_bytes()).await.expect("Failed to write to stream");
// Buffer to store the server's response
let mut buffer = [0; 2024];
// Read the response from the server
match stream.read(&mut buffer).await {
Ok(size) => {
// Print the received response
//println!("Response from Server on GTK4: {}", String::from_utf8_lossy(&buffer[..size]));
}
Err(e) => {
// Print error message if reading fails
//eprintln!("Failed to read Server on GTK4 with Error: {}", e);
}
}
}
}

View File

@ -1,55 +0,0 @@
use tokio::net::{UnixListener, UnixStream};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::task;
use std::path::Path;
use std::fs;
// Entry point of the server binary
#[tokio::main]
async fn main() {
// Path to the Unix socket file
let pika_apt_update_socket_path = "/tmp/pika_apt_get_upgradable.sock";
// Remove the socket file if it already exists
if Path::new(pika_apt_update_socket_path).exists() {
fs::remove_file(pika_apt_update_socket_path).expect("Could not remove existing socket file");
}
// Bind the Unix listener to the socket path
let pika_apt_update_listener = UnixListener::bind(pika_apt_update_socket_path).expect("Could not bind");
println!("Server listening on {}", pika_apt_update_socket_path);
// Loop to accept incoming connections
loop {
// Accept an incoming connection
match pika_apt_update_listener.accept().await {
Ok((stream, _)) => {
// Handle the connection in a separate task
task::spawn(handle_client(stream));
}
Err(e) => {
// Print error message if a connection fails
eprintln!("pika_apt_update: Connection failed: {}", e);
}
}
}
}
// Function to handle a single client connection
async fn handle_client(mut stream: UnixStream) {
// Buffer to store incoming data
let mut buffer = [0; 1024];
// Read data from the stream
match stream.read(&mut buffer).await {
Ok(size) => {
// Print the received message
println!("pika_apt_update: Received: {}", String::from_utf8_lossy(&buffer[..size]));
}
Err(e) => {
// Print error message if reading fails
eprintln!("Failed to read from stream: {}", e);
}
}
}

View File

@ -1,55 +0,0 @@
use tokio::net::{UnixListener, UnixStream};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::task;
use std::path::Path;
use std::fs;
// Entry point of the server binary
#[tokio::main]
async fn main() {
// Path to the Unix socket file
let pika_apt_update_socket_path = "/tmp/pika_apt_update_percent.sock";
// Remove the socket file if it already exists
if Path::new(pika_apt_update_socket_path).exists() {
fs::remove_file(pika_apt_update_socket_path).expect("Could not remove existing socket file");
}
// Bind the Unix listener to the socket path
let pika_apt_update_listener = UnixListener::bind(pika_apt_update_socket_path).expect("Could not bind");
println!("Server listening on {}", pika_apt_update_socket_path);
// Loop to accept incoming connections
loop {
// Accept an incoming connection
match pika_apt_update_listener.accept().await {
Ok((stream, _)) => {
// Handle the connection in a separate task
task::spawn(handle_client(stream));
}
Err(e) => {
// Print error message if a connection fails
eprintln!("pika_apt_update: Connection failed: {}", e);
}
}
}
}
// Function to handle a single client connection
async fn handle_client(mut stream: UnixStream) {
// Buffer to store incoming data
let mut buffer = [0; 1024];
// Read data from the stream
match stream.read(&mut buffer).await {
Ok(size) => {
// Print the received message
println!("pika_apt_update: Received: {}", String::from_utf8_lossy(&buffer[..size]));
}
Err(e) => {
// Print error message if reading fails
eprintln!("Failed to read from stream: {}", e);
}
}
}

View File

@ -1,55 +0,0 @@
use tokio::net::{UnixListener, UnixStream};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::task;
use std::path::Path;
use std::fs;
// Entry point of the server binary
#[tokio::main]
async fn main() {
// Path to the Unix socket file
let pika_apt_update_socket_path = "/tmp/pika_apt_update_status.sock";
// Remove the socket file if it already exists
if Path::new(pika_apt_update_socket_path).exists() {
fs::remove_file(pika_apt_update_socket_path).expect("Could not remove existing socket file");
}
// Bind the Unix listener to the socket path
let pika_apt_update_listener = UnixListener::bind(pika_apt_update_socket_path).expect("Could not bind");
println!("Server listening on {}", pika_apt_update_socket_path);
// Loop to accept incoming connections
loop {
// Accept an incoming connection
match pika_apt_update_listener.accept().await {
Ok((stream, _)) => {
// Handle the connection in a separate task
task::spawn(handle_client(stream));
}
Err(e) => {
// Print error message if a connection fails
eprintln!("pika_apt_update: Connection failed: {}", e);
}
}
}
}
// Function to handle a single client connection
async fn handle_client(mut stream: UnixStream) {
// Buffer to store incoming data
let mut buffer = [0; 1024];
// Read data from the stream
match stream.read(&mut buffer).await {
Ok(size) => {
// Print the received message
println!("pika_apt_update: Received: {}", String::from_utf8_lossy(&buffer[..size]));
}
Err(e) => {
// Print error message if reading fails
eprintln!("Failed to read from stream: {}", e);
}
}
}

View File

@ -0,0 +1,173 @@
use std::{cell::RefCell, default, sync::OnceLock};
use adw::*;
use adw::{prelude::*, subclass::prelude::*};
use glib::{subclass::Signal, Properties};
use gtk::{Align, glib, Orientation, SizeGroupMode, SelectionMode};
use crate::apt_update_page::AptPackageSocket;
// ANCHOR: custom_button
// Object holding the state
#[derive(Properties, Default)]
#[properties(wrapper_type = super::AptPackageRow)]
pub struct AptPackageRow {
#[property(get, set)]
package_name: RefCell<String>,
#[property(get, set)]
package_arch: RefCell<String>,
#[property(get, set)]
package_installed_version: RefCell<String>,
#[property(get, set)]
package_candidate_version: RefCell<String>
}
// ANCHOR_END: custom_button
// The central trait for subclassing a GObject
#[glib::object_subclass]
impl ObjectSubclass for AptPackageRow {
const NAME: &'static str = "AptPackageRow";
type Type = super::AptPackageRow;
type ParentType = adw::ActionRow;
}
// ANCHOR: object_impl
// Trait shared by all GObjects
#[glib::derived_properties]
impl ObjectImpl for AptPackageRow {
fn signals() -> &'static [Signal] {
static SIGNALS: OnceLock<Vec<Signal>> = OnceLock::new();
SIGNALS.get_or_init(|| vec![Signal::builder("row-deleted").build()])
}
fn constructed(&self) {
self.parent_constructed();
// Bind label to number
// `SYNC_CREATE` ensures that the label will be immediately set
let obj = self.obj();
let prefix_box = gtk::Box::new(Orientation::Horizontal, 0);
obj.add_prefix(&prefix_box);
obj.add_prefix(&create_version_badge("1.0-100-pika1".to_string(), "1.1-101-pika1".to_string()));
// Bind label to number
// `SYNC_CREATE` ensures that the label will be immediately set
//let obj = self.obj();
//obj.bind_property("package", &basic_expander_row_package_label, "label")
// .sync_create()
// .bidirectional()
// .build();
}
}
// Trait shared by all widgets
impl WidgetImpl for AptPackageRow {}
// Trait shared by all buttons
// Trait shared by all buttons
impl ListBoxRowImpl for AptPackageRow {}
impl PreferencesRowImpl for AptPackageRow {}
impl ActionRowImpl for AptPackageRow {}
fn create_version_badge(installed_version: String, candidate_version: String) -> gtk::ListBox {
let (base_version, installed_diff, candidate_diff) = get_diff_by_prefix(installed_version, candidate_version);
let badge_box = gtk::Box::builder().build();
let group_size = gtk::SizeGroup::new(SizeGroupMode::Both);
let installed_version_box = gtk::Box::new(Orientation::Horizontal, 0);
let installed_version_base_version_label = gtk::Label::builder()
.label(&base_version)
.margin_start(5)
.margin_end(5)
.margin_bottom(1)
.margin_top(1)
.valign(Align::Center)
.halign(Align::Start)
.hexpand(false)
.vexpand(true)
.build();
let installed_diff_label = gtk::Label::builder()
.label(installed_diff)
.margin_start(5)
.margin_end(5)
.margin_bottom(1)
.margin_top(1)
.valign(Align::Center)
.halign(Align::Start)
.hexpand(false)
.vexpand(true)
.build();
installed_diff_label.add_css_class("destructive-color-text");
installed_version_box.append(&installed_version_base_version_label.clone());
installed_version_box.append(&installed_diff_label);
group_size.add_widget(&installed_version_box);
let label_seprator = gtk::Separator::builder().build();
let candidate_version_box = gtk::Box::new(Orientation::Horizontal, 0);
let candidate_version_base_version_label = gtk::Label::builder()
.label(base_version)
.margin_start(5)
.margin_end(5)
.margin_bottom(1)
.margin_top(1)
.valign(Align::Center)
.halign(Align::Start)
.hexpand(false)
.vexpand(true)
.build();
let candidate_diff_label = gtk::Label::builder()
.label(candidate_diff)
.margin_start(5)
.margin_end(5)
.margin_bottom(1)
.margin_top(1)
.valign(Align::Center)
.halign(Align::Start)
.hexpand(false)
.vexpand(true)
.build();
candidate_diff_label.add_css_class("success-color-text");
candidate_version_box.append(&candidate_version_base_version_label);
candidate_version_box.append(&candidate_diff_label);
group_size.add_widget(&candidate_diff_label);
badge_box.append(&installed_version_box);
badge_box.append(&label_seprator);
badge_box.append(&candidate_version_box);
let boxedlist = gtk::ListBox::builder()
.selection_mode(SelectionMode::None)
.halign(Align::Center)
.valign(Align::End)
.margin_start(5)
.margin_end(5)
.margin_bottom(5)
.margin_top(5)
.build();
boxedlist.add_css_class("boxed-list");
boxedlist.append(&badge_box);
boxedlist
}
pub fn get_diff_by_prefix(xs: String, ys: String) -> (String, String, String) {
let mut count = String::new();
for (x,y) in xs.chars().zip(ys.chars()) {
if x == y {
count.push(x)
} else {
break
}
}
let count_clone0 = count.clone();
return(count_clone0, xs.trim_start_matches(&count.as_str()).to_string(), ys.trim_start_matches(&count.as_str()).to_string())
}

View File

@ -0,0 +1,34 @@
mod imp;
use glib::Object;
use gtk::glib;
use crate::apt_update_page::AptPackageSocket;
glib::wrapper! {
pub struct AptPackageRow(ObjectSubclass<imp::AptPackageRow>)
@extends adw::ActionRow, gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow,
@implements gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget;
}
impl AptPackageRow {
pub fn new(package: AptPackageSocket) -> Self {
Object::builder()
.property("package-name", package.name)
.property("package-arch", package.arch)
.property("package-installed-version", package.installed_version)
.property("package-candidate-version", package.candidate_version)
.build()
}
}
// ANCHOR_END: mod
impl Default for AptPackageRow {
fn default() -> Self {
Self::new(AptPackageSocket{
name: "name".to_string(),
arch: "arch".to_string(),
installed_version: "0.0".to_string(),
candidate_version: "0.0".to_string()
})
}
}

View File

@ -4,25 +4,29 @@ use rust_apt::*;
use rust_apt::cache::*;
use rust_apt::new_cache;
use std::process::Command;
use gtk::glib::prelude::*;
use gtk::glib::{clone, MainContext};
use gtk::prelude::*;
use gtk::glib::*;
use adw::prelude::*;
use gtk::*;
use adw::*;
use gtk::Orientation::Vertical;
use tokio::net::{UnixListener, UnixStream};
use tokio::io::AsyncReadExt;
use tokio::runtime::Runtime;
use tokio::task;
use pika_unixsocket_tools::*;
use crate::apt_package_row::AptPackageRow;
pub fn apt_update_page(window: &adw::ApplicationWindow) -> gtk::Box {
pub struct AptPackageSocket {
pub name: String,
pub arch: String,
pub installed_version: String,
pub candidate_version: String
}
pub fn apt_update_page(window: adw::ApplicationWindow) -> gtk::Box {
let (update_percent_sender, update_percent_receiver) = async_channel::unbounded::<String>();
let update_percent_sender = update_percent_sender.clone();
let (update_status_sender, update_status_receiver) = async_channel::unbounded::<String>();
let update_status_sender = update_status_sender.clone();
let (get_upgradable_sender, get_upgradable_receiver) = async_channel::unbounded::<String>();
let (get_upgradable_sender, get_upgradable_receiver) = async_channel::unbounded();
let get_upgradable_sender = get_upgradable_sender.clone();
thread::spawn(move || {
@ -33,16 +37,56 @@ pub fn apt_update_page(window: &adw::ApplicationWindow) -> gtk::Box {
Runtime::new().unwrap().block_on(update_status_socket_server(update_status_sender));
});
thread::spawn(move || {
Runtime::new().unwrap().block_on(get_upgradable_socket_server(get_upgradable_sender));
});
Command::new("pkexec")
.args(["/home/ward/RustroverProjects/project-leoali/target/debug/apt_update"])
.spawn();
let main_box = gtk::Box::builder()
.hexpand(true)
.vexpand(true)
.orientation(Orientation::Vertical)
.build();
let searchbar = gtk::SearchEntry::builder()
.search_delay(500)
.margin_bottom(15)
.margin_start(15)
.margin_end(30)
.margin_start(30)
.build();
searchbar.add_css_class("rounded-all-25");
let packages_boxedlist = gtk::ListBox::builder()
.selection_mode(SelectionMode::None)
.margin_bottom(15)
.margin_start(15)
.margin_end(15)
.margin_start(15)
.build();
packages_boxedlist.add_css_class("boxed-list");
let rows_size_group = gtk::SizeGroup::new(SizeGroupMode::Both);
packages_boxedlist.append(&AptPackageRow::new(AptPackageSocket{
name: "name".to_string(),
arch: "arch".to_string(),
installed_version: "0.0".to_string(),
candidate_version: "0.0".to_string()
}));
let packages_viewport = gtk::ScrolledWindow::builder()
.hscrollbar_policy(PolicyType::Never)
.vexpand(true)
.hexpand(true)
.margin_bottom(15)
.margin_start(15)
.margin_end(15)
.margin_start(15)
.height_request(390)
.child(&packages_boxedlist)
.build();
let apt_update_dialog_child_box = gtk::Box::builder()
.orientation(Vertical)
.orientation(Orientation::Vertical)
.build();
let apt_update_dialog_progress_bar = gtk::ProgressBar::builder()
@ -61,7 +105,7 @@ pub fn apt_update_page(window: &adw::ApplicationWindow) -> gtk::Box {
apt_update_dialog_child_box.append(&apt_update_dialog_progress_bar);
let apt_update_dialog = adw::MessageDialog::builder()
.transient_for(window)
.transient_for(&window)
.extra_child(&apt_update_dialog_child_box)
.heading(t!("apt_update_dialog_heading"))
.hide_on_close(true)
@ -70,11 +114,29 @@ pub fn apt_update_page(window: &adw::ApplicationWindow) -> gtk::Box {
let update_percent_server_context = MainContext::default();
// The main loop executes the asynchronous block
update_percent_server_context.spawn_local(clone!(@weak apt_update_dialog_progress_bar, @weak apt_update_dialog => async move {
update_percent_server_context.spawn_local(clone!(@weak apt_update_dialog_progress_bar, @weak apt_update_dialog, @strong get_upgradable_sender => async move {
while let Ok(state) = update_percent_receiver.recv().await {
match state.as_ref() {
"FN_OVERRIDE_SUCCESSFUL" => {
apt_update_dialog.close()
let get_upgradable_sender = get_upgradable_sender.clone();
thread::spawn( move || {
// Create upgradable list cache
let upgradable_cache = new_cache!().unwrap();
// Create pack sort from upgradable_cache
let upgradable_sort = PackageSort::default().upgradable().names();
for pkg in upgradable_cache.packages(&upgradable_sort) {
let package_struct = AptPackageSocket {
name: pkg.name().to_string(),
arch: pkg.arch().to_string(),
installed_version: pkg.installed().unwrap().version().to_string(),
candidate_version: pkg.candidate().unwrap().version().to_string()
};
get_upgradable_sender.send_blocking(package_struct).unwrap()
}
});
apt_update_dialog.close();
}
_ => {
apt_update_dialog_progress_bar.set_fraction(state.parse::<f64>().unwrap()/100.0)
@ -98,13 +160,16 @@ pub fn apt_update_page(window: &adw::ApplicationWindow) -> gtk::Box {
// The main loop executes the asynchronous block
get_upgradable_server_context.spawn_local(clone!(@weak window => async move {
while let Ok(state) = get_upgradable_receiver.recv().await {
println!("{}", state)
println!("{}", state.name)
}
}));
main_box.append(&searchbar);
main_box.append(&packages_viewport);
apt_update_dialog.present();
gtk::Box::new(Vertical, 0)
main_box
}

View File

@ -1,16 +1,72 @@
use adw::prelude::*;
use adw::*;
use gtk::Orientation;
use gtk::{Orientation, License};
use crate::apt_update_page;
use crate::apt_update_page::apt_update_page;
use crate::config::{APP_ICON, APP_ID};
use crate::config::{APP_ICON, APP_ID, APP_GITHUB, VERSION};
use gtk::glib::{clone, MainContext};
use std::thread;
use std::process::Command;
use std::cell::RefCell;
use std::rc::Rc;
pub fn build_ui(app: &adw::Application) {
// setup glib
gtk::glib::set_prgname(Some(t!("app_name").to_string()));
glib::set_application_name(&t!("app_name").to_string());
let window_child = gtk::Box::new(Orientation::Vertical, 0);
let internet_connected = Rc::new(RefCell::new(false));
let (internet_loop_sender, internet_loop_receiver) = async_channel::unbounded();
let internet_loop_sender = internet_loop_sender.clone();
std::thread::spawn(move || loop {
match Command::new("ping").arg("google.com").arg("-c 1").output() {
Ok(t) if t.status.success() => internet_loop_sender
.send_blocking(true)
.expect("The channel needs to be open"),
_ => internet_loop_sender
.send_blocking(false)
.expect("The channel needs to be open"),
};
thread::sleep(std::time::Duration::from_secs(5));
});
let window_banner = adw::Banner::builder().revealed(false).build();
let internet_connected_status = internet_connected.clone();
let internet_loop_context = MainContext::default();
// The main loop executes the asynchronous block
internet_loop_context.spawn_local(clone!(@weak window_banner => async move {
while let Ok(state) = internet_loop_receiver.recv().await {
let banner_text = t!("banner_text_no_internet").to_string();
if state == true {
*internet_connected_status.borrow_mut()=true;
if window_banner.title() == banner_text {
window_banner.set_revealed(false)
}
} else {
*internet_connected_status.borrow_mut()=false;
if window_banner.title() != t!("banner_text_url_error").to_string() {
window_banner.set_title(&banner_text);
window_banner.set_revealed(true)
}
}
}
}));
let window_headerbar = adw::HeaderBar::builder()
.title_widget(
&adw::WindowTitle::builder()
.title(t!("application_name"))
.build(),
)
.build();
let window_toolbar = adw::ToolbarView::builder().build();
window_toolbar.add_top_bar(&window_headerbar);
window_toolbar.add_top_bar(&window_banner);
// create the main Application window
let window = adw::ApplicationWindow::builder()
@ -24,15 +80,33 @@ pub fn build_ui(app: &adw::Application) {
// Minimum Size/Default
.width_request(700)
.height_request(500)
.content(&window_child)
.deletable(false)
.content(&window_toolbar)
// Startup
.startup_id(APP_ID)
// build the window
.build();
window_child.append(&apt_update_page::apt_update_page(&window));
let credits_button = gtk::Button::builder()
.icon_name("dialog-information-symbolic")
.build();
let credits_window = adw::AboutWindow::builder()
.application_icon(APP_ICON)
.application_name(t!("application_name"))
.transient_for(&window)
.version(VERSION)
.hide_on_close(true)
.developer_name(t!("developer_name"))
.license_type(License::Gpl20)
.issue_url(APP_GITHUB.to_owned() + "/issues")
.build();
window_headerbar.pack_end(&credits_button);
credits_button
.connect_clicked(clone!(@weak credits_button => move |_| credits_window.present()));
// show the window
window.present()
window.present();
window_toolbar.set_content(Some(&apt_update_page::apt_update_page(window)));
}

View File

@ -1,5 +1,5 @@
pub const APP_ID: &str = "com.github.pikaos-linux.pikafirstsetup";
pub const DISTRO_ICON: &str = "pika-logo";
//pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const APP_ICON: &str = "com.github.pikaos-linux.pikawelcome";
//pub const APP_GITHUB: &str = "https://github.com/PikaOS-Linux/pkg-pika-welcome";
pub const APP_GITHUB: &str = "https://github.com/PikaOS-Linux/pkg-pika-welcome";

View File

@ -2,6 +2,7 @@
mod config;
mod build_ui;
mod apt_update_page;
mod apt_package_row;
use std::env;
use adw::prelude::*;

View File

@ -0,0 +1,51 @@
.symbolic-accent-bg {
color: @accent_bg_color;
}
.size-20-font {
font-size: 20px;
}
.rounded-all-25 {
border-radius: 25px;
}
.background-accent-bg {
background: @accent_bg_color;
border-radius: 10px;
padding: 5px;
}
.background-green-bg {
background: green;
border-radius: 10px;
padding: 5px;
}
.background-red-bg {
background: #ff2a03;
border-radius: 10px;
padding: 5px;
}
.round-border-only-top {
border-top-right-radius: 15px;
border-top-left-radius: 15px;
border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px;
}
.round-border-only-bottom {
border-top-right-radius: 0px;
border-top-left-radius: 0px;
border-bottom-right-radius: 15px;
border-bottom-left-radius: 15px;
}
.destructive-color-text {
color: @destructive_bg_color;
}
.success-color-text {
color: @success_bg_color;
}