Working connection and display of info

This commit is contained in:
Franz Dietrich 2024-01-19 21:12:52 +01:00
parent a7b3ecf178
commit a667c6d98b
18 changed files with 264 additions and 1565 deletions

3
.cargo/config.toml Normal file
View File

@ -0,0 +1,3 @@
[build]
rustflags = ["--cfg", "tokio_unstable"]

3
.gitignore vendored
View File

@ -1 +1,4 @@
/target
/vendor
/Cargo.lock
.vscode/launch.json

1379
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -22,11 +22,16 @@ pki-types = { package = "rustls-pki-types", version = "1.0", features = [
tokio-rustls = { version = "0.25" }
env_logger = "0.10.1"
log = "0.4.20"
tokio = { version = "1.34.0", features = ["full"] }
tokio = { version = "1.34.0", features = ["full", "tracing"] }
webpki-roots = "0.26.0"
nom = "7.1.3"
managesieve = { path = "../managesieve" }
anyhow = "1.0"
thiserror = "1.0"
gtk4 = { version = "0.7.3", features = ["gnome_45"] }
gtk4 = { version = "0.7", features = ["gnome_45", "blueprint"] }
serde = { version = "1.0.193", features = ["derive"] }
libadwaita = { version = "0.5.3", features = ["gtk_v4_10", "v1_4"] }
gtk-blueprint = "0.2"
phf = "0.11"
tracing = "0.1"
console-subscriber = "0.2"

3
build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
println!("cargo:rerun-if-changed=**/*.blp");
}

View File

@ -1,36 +0,0 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd">
<cambalache-project version="0.13.1" target_tk="gtk-4.0">
<ui>
(1,None,"sieverman.ui","sieverman.ui",None,None,"Franz Dietrich",None,None,None,None)
</ui>
<object>
(1,1,"GtkApplicationWindow","sieverman",None,None,None,None,-1,None),
(1,2,"GtkBox",None,1,None,None,None,-1,None),
(1,3,"GtkBox",None,2,None,None,None,1,None),
(1,4,"GtkActionBar",None,2,None,None,None,None,None),
(1,5,"GtkStatusbar",None,2,None,None,None,2,None),
(1,6,"GtkSpinner",None,3,None,None,None,1,None),
(1,7,"GtkLabel","messages",3,None,None,None,None,None),
(1,9,"GtkFrame",None,3,None,None,None,2,None),
(1,10,"GtkLabel","serverstatus_2",9,None,None,None,None,None)
</object>
<object_property>
(1,2,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
(1,3,"GtkBox","baseline-child","0",None,None,None,None,None,None,None,None,None),
(1,3,"GtkBox","homogeneous","True",None,None,None,None,None,None,None,None,None),
(1,6,"GtkSpinner","spinning","True",None,None,None,None,None,None,None,None,None),
(1,7,"GtkLabel","label","Loading",None,None,None,None,None,None,None,None,None),
(1,10,"GtkLabel","label","Loading…",None,None,None,None,None,None,None,None,None)
</object_property>
<object_data>
(1,3,"GtkWidget",1,1,None,None,None,None,None,None),
(1,3,"GtkWidget",2,2,None,1,None,None,None,None),
(1,3,"GtkWidget",2,3,None,1,None,None,None,None),
(1,3,"GtkWidget",2,4,None,1,None,None,None,None),
(1,3,"GtkWidget",2,5,None,1,None,None,None,None),
(1,3,"GtkWidget",2,6,None,1,None,None,None,None),
(1,3,"GtkWidget",2,7,None,1,None,None,None,None),
(1,3,"GtkWidget",2,8,None,1,None,None,None,None)
</object_data>
</cambalache-project>

45
gui/main_window.blp Normal file
View File

@ -0,0 +1,45 @@
using Gtk 4.0;
using Adw 1;
Adw.ApplicationWindow window {
default-width: 900;
default-height: 500;
content: Gtk.Box {
orientation: vertical;
Adw.HeaderBar{
title-widget: Adw.WindowTitle {
title: "Sieverman";
};
Gtk.Button { label: "Neu";}
}
Gtk.Box {
orientation: horizontal;
vexpand: true;
halign: fill;
Gtk.Label{
label: "Log information";
hexpand: true;
}
Gtk.ScrolledWindow server_info {
width-request: 100;
Adw.PreferencesPage {
Adw.PreferencesGroup server_settings{
vexpand: true;
valign: center;
title: "Server Information";
description: "The information the server published on connection";
}}
}
}
Gtk.Statusbar{Gtk.Label{
label: "Status";
}}
};
}

12
gui/server_info_row.blp Normal file
View File

@ -0,0 +1,12 @@
using Gtk 4.0;
using Adw 1;
Adw.ActionRow server_info_row {
title: "Server2";
Gtk.Label server_info_row_value{
label: "Stalwart";
}}

View File

@ -1,41 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.16.0 -->
<interface>
<!-- interface-name sieverman.ui -->
<!-- interface-authors Franz Dietrich -->
<requires lib="gtk" version="4.12"/>
<object class="GtkApplicationWindow" id="sieverman">
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkActionBar"/>
</child>
<child>
<object class="GtkBox">
<property name="baseline-child">0</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkLabel" id="messages">
<property name="label">Loading</property>
</object>
</child>
<child>
<object class="GtkSpinner">
<property name="spinning">True</property>
</object>
</child>
<child>
<object class="GtkLabel" id="serverstatus">
<property name="label">Loading…</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkStatusbar"/>
</child>
</object>
</child>
</object>
</interface>

View File

@ -1,36 +1,29 @@
use log::{info, trace};
use sieverman::ConnectionInfo;
use tracing::info;
use crate::protocol::BackToFront;
pub(crate) async fn run(
to_frontend_tx: tokio::sync::mpsc::Sender<BackToFront>,
) -> anyhow::Result<()> {
trace!("Starting up sieverman…");
info!("Starting up sieverman…");
info!("Creating connection info");
let info = ConnectionInfo::new("teilgedanken.de", 4190);
let mut connected = info.connect().await.unwrap();
info!("connected to the server");
loop {
match connected.read_introduction().await {
Ok(sieverman::IsComplete::Yes(c)) => {
connected = c;
break;
}
Ok(sieverman::IsComplete::No(c)) => {
trace!("Incomplete data waiting for some time…");
connected = c
}
Err(e) => return Err(anyhow::anyhow!("something went wrong: {}", e)),
};
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
connected.log_buffer().await;
}
trace!("Fully read the introduction:");
connected.log_buffer().await;
connected.log_server_settings().await;
info!("connecting…");
let future_info = info.connect();
info!("waiting for the connection to be established");
let mut connected = match future_info.await {
Ok(v) => v,
Err(e) => {
info!("Failed to connect: {:?}", e);
panic!("Something went wrong");
}
};
info!("Fully read the introduction:");
to_frontend_tx
.send(BackToFront::ServerConnected(
connected.get_server_capabilities(),
connected.get_server_capabilities().clone(),
))
.await
.unwrap();

View File

@ -1,11 +1,14 @@
use std::{cell::RefCell, fmt::Display, rc::Rc};
use std::{cell::RefCell, rc::Rc};
use crate::__COMPILED_BLUEPRINT_MAP__;
use gtk4::{
glib,
prelude::{ApplicationExt, BoxExt, GtkWindowExt, WidgetExt},
Application, ApplicationWindow, Builder, Label,
prelude::{ApplicationExt, GtkWindowExt},
Application,
};
use log::trace;
use gtk_blueprint::get_blp;
use libadwaita::prelude::{PreferencesGroupExt, PreferencesRowExt};
use tracing::trace;
use crate::protocol::BackToFront;
@ -25,50 +28,30 @@ fn build_ui(
app: &gtk4::Application,
from_backend_rx: Rc<RefCell<Option<tokio::sync::mpsc::Receiver<BackToFront>>>>,
) {
let action_content_status = gtk4::Box::builder()
.orientation(gtk4::Orientation::Vertical)
.spacing(5)
.build();
let action = gtk4::ActionBar::new();
let status = gtk4::Statusbar::new();
let main_log_server_info = gtk4::Box::builder()
.orientation(gtk4::Orientation::Horizontal)
.hexpand(true)
.vexpand(true)
.halign(gtk4::Align::Fill)
.build();
action_content_status.append(&action);
action_content_status.append(&main_log_server_info);
action_content_status.append(&status);
let log = gtk4::Label::builder().label("Log").hexpand(true).build();
let server_info_content = gtk4::Label::builder().label("Server Info").build();
let server_info_container = gtk4::ScrolledWindow::builder()
.child(&server_info_content)
.width_request(400)
.build();
main_log_server_info.append(&log);
main_log_server_info.append(&server_info_container);
let main_window = gtk4::ApplicationWindow::builder()
.application(app)
.title("Sieverman")
.height_request(400)
.width_request(400)
.child(&action_content_status)
.build();
main_window.present();
let server_info = Rc::new(RefCell::new(server_info_content));
let builder = gtk4::Builder::new();
builder
.add_from_string(get_blp!("gui/main_window.blp"))
.expect("Failed to parse blueprint");
let main_window = builder
.object::<libadwaita::ApplicationWindow>("window")
.unwrap();
let prefrences_group = builder
.object::<libadwaita::PreferencesGroup>("server_settings")
.unwrap();
main_window.set_application(Some(app));
let future = {
let mut data_event_receiver = from_backend_rx.take().expect("data_event_reciver");
async move {
while let Some(event) = data_event_receiver.recv().await {
trace!("data event: {:?}", event);
match event {
BackToFront::ServerConnected(message) => {
server_info
.borrow_mut()
.set_label(&format!("{:#?}", message));
BackToFront::ServerConnected(caps) => {
prefrences_group.add(&row_in_settings("Greeting", &caps.implementation));
prefrences_group
.add(&row_in_settings("Authentication", &caps.sasl.join(",")));
prefrences_group
.add(&row_in_settings("Starttls", &caps.starttls.to_string()));
prefrences_group.add(&row_in_settings("Version", &caps.version));
}
}
}
@ -78,4 +61,22 @@ fn build_ui(
let c = glib::MainContext::default();
c.spawn_local(future);
main_window.present();
trace!("Window is visible");
}
fn row_in_settings(name: &str, value: &str) -> libadwaita::ActionRow {
let builder = gtk4::Builder::new();
builder
.add_from_string(get_blp!("gui/server_info_row.blp"))
.expect("Failed to parse blueprint");
let row = builder
.object::<libadwaita::ActionRow>("server_info_row")
.unwrap();
row.set_title(name);
let label = builder
.object::<gtk4::Label>("server_info_row_value")
.unwrap();
label.set_label(value);
row
}

48
src/bin/old/bluep_test.rs Normal file
View File

@ -0,0 +1,48 @@
use gtk4::prelude::{ApplicationExt, ApplicationExtManual, GtkWindowExt};
// We're using gtk-builder feature here
use gtk_blueprint::get_blp;
use libadwaita::prelude::PreferencesGroupExt;
gtk_blueprint::gen_blp_map!("gui");
fn main() {
gtk4::init().expect("GTK initialization failed");
libadwaita::init().expect("Adwaita initialization failed");
// Create app
let application = gtk4::Application::builder()
.application_id("de.teilgedanken.sieverman")
.build();
// Init app window and show it
application.connect_activate(|app| {
// You also can parse blueprint with Parser::parse
// and then use it in gtk4::Builder
let builder = gtk4::Builder::new();
builder
.add_from_string(get_blp!("gui/main_window.blp"))
.expect("Failed to parse blueprint");
let window = builder
.object::<libadwaita::ApplicationWindow>("window")
.unwrap();
let settings: libadwaita::PreferencesGroup = builder
.object("server_settings")
.expect("get server settings");
for _ in 0..5 {
let builder = gtk4::Builder::new();
builder
.add_from_string(get_blp!("gui/headerbar.blp"))
.expect("Failed to parse blueprint");
let widget: libadwaita::PreferencesRow = builder
.object("server_info_row")
.expect("Couldn't get widget");
settings.add(&widget);
}
window.set_application(Some(app));
window.present();
});
// Run app
application.run();
}

View File

@ -1,4 +1,3 @@
use log::info;
use nom::branch::alt;
use nom::bytes::complete::is_not;
use nom::character::is_space;
@ -12,6 +11,7 @@ use nom::{character, number};
use sieverman::parser::parse_server_config;
use sieverman::{Methods, ServerSettings};
use std::error::Error;
use tracing::info;
fn main() -> Result<(), Box<dyn Error>> {
env_logger::init();

View File

@ -9,7 +9,6 @@
/// that is sensible outside of example code.
use std::sync::Arc;
use log::{error, info, trace};
use managesieve::{Capability, MSResult, Response};
use nom::{error::ParseError, IResult};
use sieverman::parser::parse_server_config;
@ -23,6 +22,7 @@ use tokio_rustls::{
rustls::{ClientConfig, KeyLogFile, RootCertStore},
TlsConnector,
};
use tracing::{error, info, trace};
#[macro_export]
macro_rules! parse_response {

View File

@ -1,7 +1,7 @@
use managesieve::Capability;
use managesieve::Capabilities;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum BackToFront {
ServerConnected(Vec<Capability>),
ServerConnected(Capabilities),
}

View File

@ -1,15 +1,27 @@
mod backend;
mod gui;
pub mod protocol;
use std::{cell::RefCell, rc::Rc, thread};
use std::{cell::RefCell, rc::Rc, thread, time::Duration};
use gtk4::{glib, prelude::ApplicationExtManual as _};
use tracing::{info, trace};
gtk_blueprint::gen_blp_map!("gui");
fn main() -> glib::ExitCode {
env_logger::init();
console_subscriber::ConsoleLayer::builder()
// set how long the console will retain data from completed tasks
.retention(Duration::from_secs(60))
// set the address the server is bound to
.server_addr(([127, 0, 0, 1], 6669))
.init();
gtk4::init().expect("Failed to initialize GTK");
libadwaita::init().expect("Adwaita initialization failed");
let (to_frontent_tx, from_backend_rx) = tokio::sync::mpsc::channel(5);
let _handle = thread::spawn(move || {
info!("Running Backend");
tokio::runtime::Builder::new_current_thread()
.enable_io()
.enable_time()
@ -21,5 +33,8 @@ fn main() -> glib::ExitCode {
let app = gui::main_window::get_app(Rc::new(RefCell::new(Some(from_backend_rx))));
app.run()
let res = app.run();
trace!("End");
res
}

View File

@ -1,20 +1,18 @@
use std::{any, net::SocketAddr, sync::Arc, time::Duration};
use std::{sync::Arc, time::Duration};
use log::{error, info, trace};
use managesieve::Capability;
use std::net::ToSocketAddrs;
use managesieve::Capabilities;
use thiserror::Error;
use tokio::{
io::{split, AsyncBufReadExt, BufReader, ReadHalf, WriteHalf},
io::{split, AsyncBufReadExt, BufReader, WriteHalf},
net::TcpStream,
sync::Mutex,
task::JoinHandle,
};
use tokio_rustls::{
client::TlsStream,
rustls::{ClientConfig, KeyLogFile, RootCertStore},
TlsConnector,
};
use tracing::{error, info, trace};
pub mod parser;
@ -32,6 +30,7 @@ impl ConnectionInfo {
}
}
pub async fn connect(self) -> Result<ConnectionConnected, anyhow::Error> {
info!("connecting to the server");
trace!("Building Cert Store");
let mut root_store = RootCertStore::empty();
root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
@ -48,11 +47,11 @@ impl ConnectionInfo {
let domain = pki_types::ServerName::try_from(self.domain.clone())?.to_owned();
trace!("Attach the connector");
let stream = connector.connect(domain, stream).await?;
let (reader, mut writer) = split(stream);
let (reader, writer) = split(stream);
let buffer = Arc::new(Mutex::new(String::new()));
let spawned_buffer = buffer.clone();
let join_handle = tokio::spawn(async move {
let _join_handle = tokio::spawn(async move {
let mut buf = String::new();
let mut reader = BufReader::new(reader);
loop {
@ -67,13 +66,36 @@ impl ConnectionInfo {
}
buf.clear();
};
tokio::time::sleep(Duration::from_millis(50)).await;
}
});
let caps = loop {
let mut buf = buffer.lock().await;
trace!("reading new input:\n{}", buf);
match managesieve::response_capability(&buf.to_string()) {
Ok((rest, caps, resp)) => match resp.tag {
managesieve::OkNoBye::Ok => {
buf.clear();
buf.push_str(rest);
info!("Read the introduction");
break caps;
}
managesieve::OkNoBye::No | managesieve::OkNoBye::Bye => {
trace!("Connection closed!");
//panic!("Invalid Response")
}
},
Err(e) => trace!("(temporary) error: {}", e),
}
tokio::time::sleep(Duration::from_millis(50)).await;
};
trace!("Capabilities read: {:#?}", &caps);
Ok(ConnectionConnected {
info: self,
writer: Arc::new(writer), //server_settings: todo!(),
writer: Arc::new(writer),
buffer,
server_settings: Vec::new(), //join_handle: Arc::new(join_handle),
server_settings: caps,
})
}
}
@ -85,7 +107,7 @@ pub struct ConnectionConnected {
writer: Arc<WriteHalf<TlsStream<TcpStream>>>,
buffer: Arc<Mutex<String>>,
//pub join_handle: Arc<JoinHandle<()>>,
server_settings: Vec<Capability>,
server_settings: Capabilities,
}
#[derive(Error, Debug)]
@ -95,7 +117,7 @@ pub enum ReadError {
#[error("The data the server responded was invalid")]
InvalidResponse(#[source] managesieve::Error),
}
#[derive(Debug)]
pub enum IsComplete<T> {
Yes(T),
No(T),
@ -103,11 +125,12 @@ pub enum IsComplete<T> {
impl ConnectionConnected {
pub async fn log_buffer(&self) {
trace!("Buffer is: \n{}", self.buffer.lock().await)
info!("Buffer is: \n{}", self.buffer.lock().await)
}
pub async fn log_server_settings(&self) {
trace!("Serversettings:\n{:?}", self.server_settings)
info!("Serversettings:\n{:?}", self.server_settings)
}
#[tracing::instrument(name = "gettin the intro")]
pub async fn read_introduction(self) -> Result<IsComplete<Self>, anyhow::Error> {
let Self {
info,
@ -116,12 +139,11 @@ impl ConnectionConnected {
buffer,
server_settings,
} = self;
tokio::time::sleep(Duration::from_millis(500)).await;
tokio::time::sleep(Duration::from_millis(50)).await;
let mut bf = buffer.lock().await;
let bf_str = bf.clone();
match managesieve::response_capability(&bf_str) {
let response = match managesieve::response_capability(&bf.to_string()) {
Ok((rest, capability, response)) => {
trace!("{:?}", response);
info!("Successfully read the introduction {:?}", response);
bf.clear();
bf.push_str(rest);
drop(bf);
@ -132,12 +154,15 @@ impl ConnectionConnected {
server_settings: capability,
}))
}
Err(managesieve::Error::IncompleteResponse) => Ok(IsComplete::No(Self {
info,
writer,
buffer: buffer.clone(),
server_settings,
})),
Err(managesieve::Error::IncompleteResponse) => {
trace!("incomplete introduction");
Ok(IsComplete::No(Self {
info,
writer,
buffer: buffer.clone(),
server_settings,
}))
}
Err(managesieve::Error::InvalidResponse) => {
error!("invalid response");
Err(managesieve::Error::InvalidResponse)?
@ -146,18 +171,20 @@ impl ConnectionConnected {
error!("invalid input");
Err(managesieve::Error::InvalidResponse)?
}
}
}
pub fn get_greeting(&self) -> Option<String> {
for c in self.server_settings.iter() {
if let Capability::Implementation(s) = c {
return Some(s.clone());
Err(managesieve::Error::MissingLine(line)) => {
error!("a capability line was missing: {:?}", line);
Err(managesieve::Error::InvalidResponse)?
}
}
None
};
response
}
pub fn get_server_capabilities(&self) -> Vec<Capability> {
self.server_settings.clone()
pub fn get_greeting(&self) -> String {
self.server_settings.implementation.to_string()
}
pub fn get_server_capabilities(&self) -> &Capabilities {
&self.server_settings
}
}

View File

@ -1,4 +1,3 @@
use log::{info, trace};
use nom::{
branch::alt,
bytes::complete::is_not,
@ -11,6 +10,7 @@ use nom::{
sequence::preceded,
IResult,
};
use tracing::trace;
use crate::{parser::utils::key_value, Methods, ServerSettings};