feat: client-server mode

This commit is contained in:
electria 2026-08-16 12:15:46 -07:00
commit a689366324
Signed by: electria
SSH key fingerprint: SHA256:8LlB3ucPbBHqozqkhsNbaV5oG3SlzzqUj8FZDL6IPQs
7 changed files with 119 additions and 28 deletions

25
Cargo.lock generated
View file

@ -292,6 +292,9 @@ name = "bitflags"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
dependencies = [
"serde_core",
]
[[package]]
name = "block"
@ -1380,6 +1383,8 @@ name = "itop"
version = "0.1.0"
dependencies = [
"iced",
"ron",
"serde",
"smol",
"sysinfo",
]
@ -2482,6 +2487,20 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
[[package]]
name = "ron"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81116b9531d61eabc41aeb228e4b6b2435bcca3233b98cf3b3077d4e6e9debb3"
dependencies = [
"bitflags 2.13.1",
"once_cell",
"serde",
"serde_derive",
"typeid",
"unicode-ident",
]
[[package]]
name = "roxmltree"
version = "0.20.0"
@ -3091,6 +3110,12 @@ dependencies = [
"core_maths",
]
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "uds_windows"
version = "1.2.1"

View file

@ -6,6 +6,8 @@ version = "0.1.0"
edition = "2024"
[dependencies]
ron = "0.12.2"
serde = "1.0.229"
smol = "2.0.2"
sysinfo = "0.39.6"

View file

@ -1,28 +1,63 @@
use std::env;
use iced::{
Task,
futures::{self, SinkExt},
stream,
};
use smol::Timer;
use ron::de;
use smol::{Timer, unblock};
use crate::sysinfo_snapshot::SysinfoSnapshot;
use crate::{app, sysinfo_snapshot::SysinfoSnapshot};
pub fn create() -> impl futures::Stream<Item = SysinfoSnapshot> {
stream::channel(
1,
async |mut sender: futures::channel::mpsc::Sender<SysinfoSnapshot>| {
let mut sys = sysinfo::System::new();
let refreshes = sysinfo::RefreshKind::nothing()
.with_memory(sysinfo::MemoryRefreshKind::everything())
.with_cpu(sysinfo::CpuRefreshKind::nothing().with_cpu_usage());
pub fn create() -> Task<app::Message> {
if env::args()
.nth(1)
.is_some_and(|command| command == "client")
{
Task::run(
stream::channel(
1,
async |mut sender: futures::channel::mpsc::Sender<SysinfoSnapshot>| {
let mut buf = String::new();
loop {
buf = unblock(move || {
std::io::stdin().read_line(&mut buf).unwrap();
buf
})
.await;
sys.refresh_specifics(refreshes);
Timer::after(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL).await;
match de::from_str(&buf) {
Ok(info) => sender.send(info).await.unwrap(),
Err(e) => eprintln!("{e}"),
}
buf.clear();
}
},
),
app::Message::Info,
)
} else {
Task::run(
stream::channel(
1,
async |mut sender: futures::channel::mpsc::Sender<SysinfoSnapshot>| {
let mut sys = sysinfo::System::new();
let refreshes = sysinfo::RefreshKind::nothing()
.with_memory(sysinfo::MemoryRefreshKind::everything())
.with_cpu(sysinfo::CpuRefreshKind::nothing().with_cpu_usage());
loop {
sys.refresh_specifics(refreshes);
sender.send(SysinfoSnapshot::from(&sys)).await.unwrap();
Timer::after(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL).await;
}
},
)
sys.refresh_specifics(refreshes);
Timer::after(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL).await;
loop {
sys.refresh_specifics(refreshes);
sender.send(SysinfoSnapshot::from(&sys)).await.unwrap();
Timer::after(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL).await;
}
},
),
app::Message::Info,
)
}
}

View file

@ -1,6 +1,6 @@
use std::time::Duration;
use iced::{Color, Element, Length, Renderer, Task, Theme, application, theme, widget};
use iced::{Color, Element, Error, Length, Renderer, Task, Theme, application, theme, widget};
use crate::{app::graph::Graph, sysinfo_snapshot::SysinfoSnapshot};
@ -23,10 +23,7 @@ struct State {
impl State {
fn new() -> (Self, Task<Message>) {
(
Self::default(),
Task::run(info_stream::create(), Message::Info),
)
(Self::default(), info_stream::create())
}
fn update(&mut self, message: Message) -> Task<Message> {
match message {
@ -95,7 +92,7 @@ impl State {
}
}
pub fn run() -> Result<(), impl std::error::Error> {
pub fn run() -> Result<(), Error> {
application(State::new, State::update, State::view)
.title(State::title)
.theme(theme::Theme::custom(

View file

@ -1,6 +1,15 @@
use std::env;
mod app;
mod server;
mod sysinfo_snapshot;
fn main() -> Result<(), impl std::error::Error> {
app::run()
fn main() {
match env::args().nth(1).as_deref() {
Some("server") => server::run().unwrap(),
None | Some("client") => app::run().unwrap(),
Some(s) => panic!("invalid argument: {s}"),
}
}

23
src/server/mod.rs Normal file
View file

@ -0,0 +1,23 @@
use std::{io::Write, thread};
use ron::{Error, ser};
use crate::sysinfo_snapshot::SysinfoSnapshot;
pub fn run() -> Result<(), Error> {
let mut sys = sysinfo::System::new();
let refreshes = sysinfo::RefreshKind::nothing()
.with_memory(sysinfo::MemoryRefreshKind::everything())
.with_cpu(sysinfo::CpuRefreshKind::nothing().with_cpu_usage());
sys.refresh_specifics(refreshes);
thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL);
let mut stdout = std::io::stdout();
loop {
sys.refresh_specifics(refreshes);
stdout.write_all((ser::to_string(&SysinfoSnapshot::from(&sys))? + "\n").as_bytes())?;
thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL);
}
}

View file

@ -1,6 +1,6 @@
use std::time::Duration;
#[derive(Clone, Debug)]
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct SysinfoSnapshot {
uptime: u64,