style: rustfmt whole project

This commit is contained in:
əlemi 2022-06-14 00:54:46 +02:00
parent 32d58e4019
commit 21d664479a
No known key found for this signature in database
GPG key ID: BBCBFE5D7244634E
9 changed files with 184 additions and 137 deletions

View file

@ -1,12 +1,11 @@
pub mod source;
pub mod store;
use std::path::PathBuf;
use std::sync::{RwLock, Mutex};
use std::num::ParseFloatError;
use eframe::epaint::Color32;
use self::store::SQLiteDataStore;
use self::source::{Panel, Source};
use self::store::SQLiteDataStore;
use std::num::ParseFloatError;
use std::path::PathBuf;
use std::sync::{Mutex, RwLock};
#[derive(Debug)]
pub enum FetchError {
@ -18,20 +17,31 @@ pub enum FetchError {
ParseFloatError(ParseFloatError),
}
impl From::<ureq::Error> for FetchError {
fn from(e: ureq::Error) -> Self { FetchError::UreqError(e) }
impl From<ureq::Error> for FetchError {
fn from(e: ureq::Error) -> Self {
FetchError::UreqError(e)
}
}
impl From::<std::io::Error> for FetchError {
fn from(e: std::io::Error) -> Self { FetchError::IoError(e) }
impl From<std::io::Error> for FetchError {
fn from(e: std::io::Error) -> Self {
FetchError::IoError(e)
}
}
impl From::<String> for FetchError { // TODO wtf? why does JQL error as a String?
fn from(e: String) -> Self { FetchError::JQLError(e) }
impl From<String> for FetchError {
// TODO wtf? why does JQL error as a String?
fn from(e: String) -> Self {
FetchError::JQLError(e)
}
}
impl From::<ParseFloatError> for FetchError {
fn from(e: ParseFloatError) -> Self { FetchError::ParseFloatError(e) }
impl From<ParseFloatError> for FetchError {
fn from(e: ParseFloatError) -> Self {
FetchError::ParseFloatError(e)
}
}
impl From::<rusqlite::Error> for FetchError {
fn from(e: rusqlite::Error) -> Self { FetchError::RusqliteError(e) }
impl From<rusqlite::Error> for FetchError {
fn from(e: rusqlite::Error) -> Self {
FetchError::RusqliteError(e)
}
}
pub struct ApplicationState {
@ -45,13 +55,13 @@ pub struct ApplicationState {
}
impl ApplicationState {
pub fn new(path:PathBuf) -> Result<ApplicationState, FetchError> {
pub fn new(path: PathBuf) -> Result<ApplicationState, FetchError> {
let storage = SQLiteDataStore::new(path.clone())?;
let panels = storage.load_panels()?;
let sources = storage.load_sources()?;
return Ok(ApplicationState{
return Ok(ApplicationState {
run: true,
file_size: RwLock::new(std::fs::metadata(path.clone())?.len()),
file_path: path,
@ -63,22 +73,42 @@ impl ApplicationState {
}
pub fn add_panel(&self, panel: &Panel) -> Result<(), FetchError> {
let verified_panel = self.storage.lock().expect("Storage Mutex poisoned")
let verified_panel = self
.storage
.lock()
.expect("Storage Mutex poisoned")
.new_panel(
panel.name.as_str(),
panel.view_size,
panel.width,
panel.height,
self.panels.read().expect("Panels RwLock poisoned").len() as i32 // todo can this be made more compact and without acquisition?
self.panels.read().expect("Panels RwLock poisoned").len() as i32, // todo can this be made more compact and without acquisition?
)?; // TODO make values customizable and useful
self.panels.write().expect("Panels RwLock poisoned").push(verified_panel);
self.panels
.write()
.expect("Panels RwLock poisoned")
.push(verified_panel);
Ok(())
}
pub fn add_source(&self, source: &Source) -> Result<(), FetchError> {
let verified_source = self.storage.lock().expect("Storage Mutex poisoned")
.new_source(source.panel_id, source.name.as_str(), source.url.as_str(), source.query_x.as_str(), source.query_y.as_str(), source.color, source.visible)?;
self.sources.write().expect("Sources RwLock poisoned").push(verified_source);
let verified_source = self
.storage
.lock()
.expect("Storage Mutex poisoned")
.new_source(
source.panel_id,
source.name.as_str(),
source.url.as_str(),
source.query_x.as_str(),
source.query_y.as_str(),
source.color,
source.visible,
)?;
self.sources
.write()
.expect("Sources RwLock poisoned")
.push(verified_source);
return Ok(());
}
}

View file

@ -1,8 +1,8 @@
use std::sync::RwLock;
use chrono::{DateTime, Utc};
use eframe::egui::plot::{Values, Value};
use eframe::epaint::Color32;
use super::FetchError;
use chrono::{DateTime, Utc};
use eframe::egui::plot::{Value, Values};
use eframe::epaint::Color32;
use std::sync::RwLock;
pub struct Panel {
pub(crate) id: i32,
@ -59,7 +59,7 @@ impl Default for Source {
query_x: "".to_string(),
query_y: "".to_string(),
panel_id: -1,
data: RwLock::new(Vec::new())
data: RwLock::new(Vec::new()),
}
}
}
@ -74,21 +74,25 @@ impl Source {
Values::from_values(self.data.read().expect("Values RwLock poisoned").clone())
}
pub fn values_filter(&self, min_x:f64) -> Values {
pub fn values_filter(&self, min_x: f64) -> Values {
let mut values = self.data.read().expect("Values RwLock poisoned").clone();
values.retain(|x| x.x > min_x);
Values::from_values(values)
}
}
pub fn fetch(url:&str, query_x:&str, query_y:&str) -> Result<Value, FetchError> {
pub fn fetch(url: &str, query_x: &str, query_y: &str) -> Result<Value, FetchError> {
let res = ureq::get(url).call()?.into_json()?;
let x : f64;
let x: f64;
if query_x.len() > 0 {
x = jql::walker(&res, query_x)?.as_f64().ok_or(FetchError::JQLError("X query is null".to_string()))?; // TODO what if it's given to us as a string?
x = jql::walker(&res, query_x)?
.as_f64()
.ok_or(FetchError::JQLError("X query is null".to_string()))?; // TODO what if it's given to us as a string?
} else {
x = Utc::now().timestamp() as f64;
}
let y = jql::walker(&res, query_y)?.as_f64().ok_or(FetchError::JQLError("Y query is null".to_string()))?;
return Ok( Value { x, y } );
let y = jql::walker(&res, query_y)?
.as_f64()
.ok_or(FetchError::JQLError("Y query is null".to_string()))?;
return Ok(Value { x, y });
}

View file

@ -1,9 +1,12 @@
use crate::app::{data::source::{Panel, Source}, util::repack_color};
use crate::app::util::unpack_color;
use crate::app::{
data::source::{Panel, Source},
util::repack_color,
};
use chrono::{TimeZone, Utc};
use eframe::egui::{Color32, plot::Value};
use eframe::egui::{plot::Value, Color32};
use rusqlite::{params, Connection};
use std::sync::RwLock;
use crate::app::util::unpack_color;
pub trait DataStorage {
fn add_panel(&self, name: &str);
@ -60,8 +63,6 @@ impl SQLiteDataStore {
Ok(SQLiteDataStore { conn })
}
pub fn load_values(&self, source_id: i32) -> rusqlite::Result<Vec<Value>> {
let mut values: Vec<Value> = Vec::new();
let mut statement = self
@ -90,13 +91,9 @@ impl SQLiteDataStore {
)
}
pub fn load_sources(&self) -> rusqlite::Result<Vec<Source>> {
let mut sources: Vec<Source> = Vec::new();
let mut statement = self
.conn
.prepare("SELECT * FROM sources")?;
let mut statement = self.conn.prepare("SELECT * FROM sources")?;
let sources_iter = statement.query_map([], |row| {
Ok(Source {
id: row.get(0)?,
@ -134,7 +131,11 @@ impl SQLiteDataStore {
color: Color32,
visible: bool,
) -> rusqlite::Result<Source> {
let color_u32 : Option<u32> = if color == Color32::TRANSPARENT { None } else { Some(repack_color(color)) };
let color_u32: Option<u32> = if color == Color32::TRANSPARENT {
None
} else {
Some(repack_color(color))
};
self.conn.execute(
"INSERT INTO sources(name, url, interval, query_x, query_y, panel_id, color, visible) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
params![name, url, 60i32, query_x, query_y, panel_id, color_u32, visible],
@ -177,7 +178,11 @@ impl SQLiteDataStore {
color: Color32,
visible: bool,
) -> rusqlite::Result<usize> {
let color_u32 : Option<u32> = if color == Color32::TRANSPARENT { None } else { Some(repack_color(color)) };
let color_u32: Option<u32> = if color == Color32::TRANSPARENT {
None
} else {
Some(repack_color(color))
};
self.conn.execute(
"UPDATE sources SET name = ?, url = ?, interval = ?, query_x = ?, query_y = ?, panel_id = ?, color = ?, visible = ? WHERE id = ?",
params![name, url, interval, query_x, query_y, panel_id, color_u32, visible, source_id],
@ -190,7 +195,9 @@ impl SQLiteDataStore {
pub fn load_panels(&self) -> rusqlite::Result<Vec<Panel>> {
let mut panels: Vec<Panel> = Vec::new();
let mut statement = self.conn.prepare("SELECT * FROM panels ORDER BY position")?;
let mut statement = self
.conn
.prepare("SELECT * FROM panels ORDER BY position")?;
let panels_iter = statement.query_map([], |row| {
Ok(Panel {
id: row.get(0)?,
@ -214,7 +221,14 @@ impl SQLiteDataStore {
}
// jank! TODO make it not jank!
pub fn new_panel(&self, name: &str, view_size:i32, width: i32, height: i32, position: i32) -> rusqlite::Result<Panel> {
pub fn new_panel(
&self,
name: &str,
view_size: i32,
width: i32,
height: i32,
position: i32,
) -> rusqlite::Result<Panel> {
self.conn.execute(
"INSERT INTO panels (name, view_scroll, view_size, timeserie, width, height, limit_view, position) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
params![name, true, view_size, true, width, height, false, position]
@ -260,7 +274,4 @@ impl SQLiteDataStore {
// pub fn delete_panel(&self, id:i32) -> rusqlite::Result<usize> {
// self.conn.execute("DELETE FROM panels WHERE id = ?", params![id])
// }
}

View file

@ -1,2 +1,2 @@
pub mod source;
pub mod panel;
pub mod source;

View file

@ -1,7 +1,13 @@
use chrono::{Utc, Local};
use eframe::egui::{Ui, Layout, Align, plot::{Plot, Legend, Corner, Line, GridMark}, Slider, DragValue};
use chrono::{Local, Utc};
use eframe::egui::{
plot::{Corner, GridMark, Legend, Line, Plot},
DragValue, Layout, Slider, Ui,
};
use crate::app::{data::source::{Panel, Source}, util::timestamp_to_str};
use crate::app::{
data::source::{Panel, Source},
util::timestamp_to_str,
};
pub fn panel_edit_inline_ui(ui: &mut Ui, panel: &mut Panel) {
eframe::egui::TextEdit::singleline(&mut panel.name)
@ -27,16 +33,13 @@ pub fn panel_title_ui(ui: &mut Ui, panel: &mut Panel) {
ui.separator();
ui.checkbox(&mut panel.timeserie, "timeserie");
ui.separator();
ui.add(
Slider::new(&mut panel.height, 0..=500).text("height"),
);
ui.add(Slider::new(&mut panel.height, 0..=500).text("height"));
ui.separator();
});
});
});
}
pub fn panel_body_ui(ui: &mut Ui, panel: &mut Panel, sources: &Vec<Source>) {
let mut p = Plot::new(format!("plot-{}", panel.name))
.height(panel.height as f32)
@ -44,26 +47,20 @@ pub fn panel_body_ui(ui: &mut Ui, panel: &mut Panel, sources: &Vec<Source>) {
.legend(Legend::default().position(Corner::LeftTop));
if panel.view_scroll {
p = p
.include_x(Utc::now().timestamp() as f64);
p = p.include_x(Utc::now().timestamp() as f64);
if panel.limit {
p = p
.set_margin_fraction(eframe::emath::Vec2{x:0.0, y:0.1})
.include_x((Utc::now().timestamp() + ( panel.view_size as i64 * 3)) as f64);
.set_margin_fraction(eframe::emath::Vec2 { x: 0.0, y: 0.1 })
.include_x((Utc::now().timestamp() + (panel.view_size as i64 * 3)) as f64);
}
if panel.limit {
p = p.include_x(
(Utc::now().timestamp() - (panel.view_size as i64 * 60))
as f64,
);
p = p.include_x((Utc::now().timestamp() - (panel.view_size as i64 * 60)) as f64);
}
}
if panel.timeserie {
p = p
.x_axis_formatter(|x, _range| {
timestamp_to_str(x as i64, true, false)
})
.x_axis_formatter(|x, _range| timestamp_to_str(x as i64, true, false))
.label_formatter(|name, value| {
if !name.is_empty() {
return format!(
@ -111,8 +108,7 @@ pub fn panel_body_ui(ui: &mut Ui, panel: &mut Panel, sources: &Vec<Source>) {
if source.visible && source.panel_id == panel.id {
let line = if panel.limit {
Line::new(source.values_filter(
(Utc::now().timestamp()
- (panel.view_size as i64 * 60)) as f64,
(Utc::now().timestamp() - (panel.view_size as i64 * 60)) as f64,
))
.name(source.name.as_str())
} else {

View file

@ -1,7 +1,7 @@
use eframe::egui;
use eframe::egui::Ui;
use crate::app::data::source::{Source, Panel};
use crate::app::data::source::{Panel, Source};
pub fn source_edit_inline_ui(ui: &mut Ui, source: &mut Source, panels: &Vec<Panel>) {
eframe::egui::TextEdit::singleline(&mut source.name)
@ -25,11 +25,7 @@ pub fn source_edit_inline_ui(ui: &mut Ui, source: &mut Source, panels: &Vec<Pane
.width(70.0)
.show_ui(ui, |ui| {
for p in panels {
ui.selectable_value(
&mut source.panel_id,
p.id,
p.name.as_str(),
);
ui.selectable_value(&mut source.panel_id, p.id, p.name.as_str());
}
});
ui.checkbox(&mut source.visible, "visible");
@ -37,7 +33,6 @@ pub fn source_edit_inline_ui(ui: &mut Ui, source: &mut Source, panels: &Vec<Pane
ui.color_edit_button_srgba(&mut source.color);
}
pub fn source_ui(ui: &mut Ui, source: &mut Source, panels: &Vec<Panel>) {
ui.group(|ui| {
ui.horizontal(|ui| {
@ -66,11 +61,7 @@ pub fn source_ui(ui: &mut Ui, source: &mut Source, panels: &Vec<Panel>) {
.width(70.0)
.show_ui(ui, |ui| {
for p in panels {
ui.selectable_value(
&mut source.panel_id,
p.id,
p.name.as_str(),
);
ui.selectable_value(&mut source.panel_id, p.id, p.name.as_str());
}
});
ui.color_edit_button_srgba(&mut source.color);

View file

@ -3,21 +3,15 @@ pub mod gui;
pub mod util;
pub mod worker;
use chrono::{Local, Utc};
use eframe::egui;
use eframe::egui::plot::GridMark;
use eframe::egui::{
plot::{Line, Plot},
Color32,
};
use std::sync::Arc;
use tracing::error;
use self::data::source::{Panel, Source};
use self::data::ApplicationState;
use self::data::source::{Panel,Source};
use self::gui::panel::{panel_edit_inline_ui, panel_title_ui, panel_body_ui};
use self::gui::source::{source_ui, source_edit_inline_ui};
use self::util::{human_size, timestamp_to_str};
use self::gui::panel::{panel_body_ui, panel_edit_inline_ui, panel_title_ui};
use self::gui::source::{source_edit_inline_ui, source_ui};
use self::util::human_size;
use self::worker::native_save;
pub struct App {
@ -61,7 +55,11 @@ impl eframe::App for App {
}
ui.separator();
ui.label("+ source");
source_edit_inline_ui(ui, &mut self.input_source, &self.data.panels.read().expect("Panels RwLock poisoned"));
source_edit_inline_ui(
ui,
&mut self.input_source,
&self.data.panels.read().expect("Panels RwLock poisoned"),
);
if ui.button("add").clicked() {
if let Err(e) = self.data.add_source(&self.input_source) {
error!(target: "ui", "Error adding souce : {:?}", e);
@ -154,22 +152,22 @@ impl eframe::App for App {
ui.make_persistent_id(format!("panel-{}-compressable", panel.id)),
true,
)
.show_header(ui, |ui| {
if self.edit {
if ui.small_button(" + ").clicked() {
if index > 0 {
to_swap.push(index); // TODO kinda jank but is there a better way?
}
}
if ui.small_button(" - ").clicked() {
if index < panels_count - 1 {
to_swap.push(index + 1); // TODO kinda jank but is there a better way?
}
.show_header(ui, |ui| {
if self.edit {
if ui.small_button(" + ").clicked() {
if index > 0 {
to_swap.push(index); // TODO kinda jank but is there a better way?
}
}
panel_title_ui(ui, panel);
})
.body(|ui| panel_body_ui(ui, panel, &sources));
if ui.small_button(" - ").clicked() {
if index < panels_count - 1 {
to_swap.push(index + 1); // TODO kinda jank but is there a better way?
}
}
}
panel_title_ui(ui, panel);
})
.body(|ui| panel_body_ui(ui, panel, &sources));
}
});
});

View file

@ -1,10 +1,10 @@
use std::sync::Arc;
use tracing::warn;
use crate::app::data::{source::fetch, ApplicationState};
use chrono::Utc;
use eframe::egui::Context;
use crate::app::data::{ApplicationState, source::fetch};
use std::sync::Arc;
use tracing::warn;
pub fn native_save(state:Arc<ApplicationState>) {
pub fn native_save(state: Arc<ApplicationState>) {
std::thread::spawn(move || {
let storage = state.storage.lock().expect("Storage Mutex poisoned");
let panels = state.panels.read().expect("Panels RwLock poisoned");
@ -43,16 +43,16 @@ pub fn native_save(state:Arc<ApplicationState>) {
}
pub(crate) trait BackgroundWorker {
fn start(state:Arc<ApplicationState>, ctx:Context) -> Self; // TODO make it return an error? Can we even do anything without a background worker
fn stop(self); // TODO make it return an error? Can we even do anything without a background worker
fn start(state: Arc<ApplicationState>, ctx: Context) -> Self; // TODO make it return an error? Can we even do anything without a background worker
fn stop(self); // TODO make it return an error? Can we even do anything without a background worker
}
pub(crate) struct NativeBackgroundWorker {
worker : std::thread::JoinHandle<()>,
worker: std::thread::JoinHandle<()>,
}
impl BackgroundWorker for NativeBackgroundWorker {
fn start(state:Arc<ApplicationState>, ctx:Context) -> Self {
fn start(state: Arc<ApplicationState>, ctx: Context) -> Self {
let worker = std::thread::spawn(move || {
let mut last_check = 0;
while state.run {
@ -66,25 +66,38 @@ impl BackgroundWorker for NativeBackgroundWorker {
for j in 0..sources.len() {
let s_id = sources[j].id;
if !sources[j].valid() {
let mut last_update = sources[j].last_fetch.write().expect("Sources RwLock poisoned");
let mut last_update = sources[j]
.last_fetch
.write()
.expect("Sources RwLock poisoned");
*last_update = Utc::now();
let state2 = state.clone();
let url = sources[j].url.clone();
let query_x = sources[j].query_x.clone();
let query_y = sources[j].query_y.clone();
std::thread::spawn(move || { // TODO this can overspawn if a request takes longer than the refresh interval!
std::thread::spawn(move || {
// TODO this can overspawn if a request takes longer than the refresh interval!
match fetch(url.as_str(), query_x.as_str(), query_y.as_str()) {
Ok(v) => {
let store = state2.storage.lock().expect("Storage mutex poisoned");
let store =
state2.storage.lock().expect("Storage mutex poisoned");
if let Err(e) = store.put_value(s_id, v) {
warn!(target:"background-worker", "Could not put sample for source #{} in db: {:?}", s_id, e);
} else {
let sources = state2.sources.read().expect("Sources RwLock poisoned");
sources[j].data.write().expect("Source data RwLock poisoned").push(v);
let mut last_update = sources[j].last_fetch.write().expect("Source last update RwLock poisoned");
let sources =
state2.sources.read().expect("Sources RwLock poisoned");
sources[j]
.data
.write()
.expect("Source data RwLock poisoned")
.push(v);
let mut last_update = sources[j]
.last_fetch
.write()
.expect("Source last update RwLock poisoned");
*last_update = Utc::now(); // overwrite it so fetches comply with API slowdowns and get desynched among them
}
},
}
Err(e) => {
warn!(target:"background-worker", "Could not fetch value from {} : {:?}", url, e);
}
@ -102,12 +115,12 @@ impl BackgroundWorker for NativeBackgroundWorker {
}
});
return NativeBackgroundWorker {
worker
};
return NativeBackgroundWorker { worker };
}
fn stop(self) {
self.worker.join().expect("Failed joining main worker thread");
self.worker
.join()
.expect("Failed joining main worker thread");
}
}
}

View file

@ -1,23 +1,26 @@
mod app;
use crate::app::{
data::ApplicationState,
util::InternalLogger,
worker::{BackgroundWorker, NativeBackgroundWorker},
App,
};
use std::sync::Arc;
use tracing_subscriber::prelude::*;
use crate::app::{App, util::InternalLogger, data::ApplicationState, worker::{BackgroundWorker, NativeBackgroundWorker}};
// When compiling natively:
#[cfg(not(target_arch = "wasm32"))]
fn main() -> ! {
use tracing::metadata::LevelFilter;
use tracing::metadata::LevelFilter;
let native_options = eframe::NativeOptions::default();
let mut store_path = dirs::data_dir().unwrap_or(std::path::PathBuf::from(".")); // TODO get cwd more consistently?
store_path.push("dashboard.db");
let store = Arc::new(
ApplicationState::new(store_path).expect("Failed creating application state")
);
let store =
Arc::new(ApplicationState::new(store_path).expect("Failed creating application state"));
tracing_subscriber::registry()
.with(LevelFilter::INFO)
@ -25,7 +28,8 @@ fn main() -> ! {
.with(InternalLogger::new(store.clone()))
.init();
eframe::run_native( // TODO replace this with a loop that ends so we can cleanly exit the background worker
eframe::run_native(
// TODO replace this with a loop that ends so we can cleanly exit the background worker
"dashboard",
native_options,
Box::new(move |cc| {