diff --git a/src/terms/consent_state.rs b/src/terms/consent_state.rs index 88f60df..6a9ccaf 100644 --- a/src/terms/consent_state.rs +++ b/src/terms/consent_state.rs @@ -3,41 +3,71 @@ use crate::{ doc::Doc, terms_checker, terms_getter::{Type, get_current_docs, get_newest_docs}, + terms_updater, }, - util::file_util::load_file, + util::file_util::{load_file, save_file}, }; use std::time::{SystemTime, UNIX_EPOCH}; +use tokio::task; pub struct ConsentManager; impl ConsentManager { pub async fn check() -> (bool, bool) { let file = load_file("", "agreements"); - let file_state = ConsentState::from_str(&file).sanitize(); - let accepted_state = if !&file_state.accepted_eula { - terms_checker::run_consent_ui(file_state).await - } else { - file_state - }; + let mut file_state = ConsentState::from_str(&file).sanitize(); - if let Some((current_eula, current_tos, current_privacy)) = get_current_docs().await { - if current_eula.equals_some(&accepted_state.eula) { - if current_tos.equals_some(&accepted_state.tos) - && current_privacy.equals_some(&accepted_state.pp) - { - (true, true) - } else { - (true, false) - } - } else { - (false, false) - } - } else { - println!("There was an error while loading our EULA, please retry later!"); - (false, false) + if !file_state.accepted_eula { + file_state = terms_checker::run_consent_ui(file_state).await; + + println!("{}", &file_state.clone().sanitize().to_string()); + println!("-----------------------"); + + save_file("", "agreements", &file_state.to_string()); } + + if !file_state.accepted_eula { + return (false, false); + } + + if let Some((eula_update, tos_update, privacy_update)) = Self::get_updates().await { + let is_forced = eula_update.is_err() || tos_update.is_err() || privacy_update.is_err(); + + let updater_logic = async move { + let state_to_update = terms_updater::run_consent_ui( + file_state, + eula_update, + tos_update, + privacy_update, + ) + .await; + + println!("{}", &state_to_update.clone().sanitize().to_string()); + + save_file("", "agreements", &state_to_update.sanitize().to_string()); + }; + + if is_forced { + updater_logic.await; + } else { + task::spawn(updater_logic); + } + } + + let final_state = ConsentState::from_str(&load_file("", "agreements")).sanitize(); + ( + final_state.accepted_eula, + final_state.accepted_tos && final_state.accepted_pp, + ) } - pub async fn check_updates() -> Option<(Option, Option, Option)> { + async fn get_updates() -> Option<( + // Ok(None) indicates no update + // Ok(Some) Indicates a future update + // Err indicates a update that has to be accepted before the programm can continue + Result, Doc>, + Result, Doc>, + Result, Doc>, + )> { let file = load_file("", "agreements"); let accepted_state = ConsentState::from_str(&file).sanitize(); @@ -46,7 +76,48 @@ impl ConsentManager { Some((newest_eula, newest_tos, newest_privacy)), ) = (get_current_docs().await, get_newest_docs().await) { - None + let eula_update: Result, Doc> = + if current_eula.equals_some(&accepted_state.eula) { + if current_eula.equals(&newest_eula) { + Ok(None) + } else { + Ok(Some(newest_eula)) + } + } else if newest_eula.equals_some(&accepted_state.eula) { + Ok(None) + } else { + Err(current_eula) + }; + + let tos_update: Result, Doc> = if newest_tos + .equals_some(&accepted_state.tos) + { + Ok(None) + } else if accepted_state.accepted_tos && current_tos.equals_some(&accepted_state.tos) { + if current_tos.equals(&newest_tos) { + Ok(None) + } else { + Ok(Some(newest_tos)) + } + } else { + Err(current_tos) + }; + + let privacy_update: Result, Doc> = + if newest_privacy.equals_some(&accepted_state.privacy) { + Ok(None) + } else if accepted_state.accepted_pp + && current_privacy.equals_some(&accepted_state.privacy) + { + if current_privacy.equals(&newest_privacy) { + Ok(None) + } else { + Ok(Some(newest_privacy)) + } + } else { + Err(current_privacy) + }; + Some((eula_update, tos_update, privacy_update)) } else { None } @@ -66,7 +137,7 @@ pub struct ConsentState { pub accepted_eula: bool, pub tos: Option, pub accepted_tos: bool, - pub pp: Option, + pub privacy: Option, pub accepted_pp: bool, } @@ -79,7 +150,7 @@ impl ConsentState { self } - async fn to_string(self) -> String { + pub fn to_string(&self) -> String { let current_secs = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() @@ -93,9 +164,13 @@ impl ConsentState { current_secs ); - if self.accepted_eula - && let Some(eula) = self.eula - { + if let Some(eula) = &self.eula { + println!( + "EULA: {}, {}, {}", + self.accepted_eula, + eula.get_version(), + eula.get_hash() + ); file_out.push_str(&format!("\ \n\"EULA=true\" indicates that you read, understood and accepted Tensamin's End User Licence agreement. You can find our EULA at https://legal.tensamin.net/eula/\ \nEULA={}\ @@ -104,17 +179,17 @@ impl ConsentState { ", self.accepted_eula, eula.get_version(), eula.get_hash())); if self.accepted_tos - && let Some(tos) = self.tos + && let Some(tos) = &self.tos { file_out.push_str(&format!("\ - \n\"ToS=true\" indicates that you read, understood and accepted Tensamin's Terms of Service. You can find our Terms of Serivce at https://legal.tensamin.net/tos/\ - \nToS={}\ - \nToS-VERSION={}\ - \nToS-HASH={}\ + \n\"Terms-of-Service=true\" indicates that you read, understood and accepted Tensamin's Terms of Service. You can find our Terms of Serivce at https://legal.tensamin.net/tos/\ + \nTerms-of-Service={}\ + \nTerms-of-Service-VERSION={}\ + \nTerms-of-Service-HASH={}\ ", self.accepted_tos, tos.get_version(), tos.get_hash())); } if self.accepted_pp - && let Some(pp) = self.pp + && let Some(pp) = &self.privacy { file_out.push_str(&format!("\ \n\"Privacy-Policy=true\" indicates that you read, understood and accepted Tensamin's Privacy Policy. You can find our Privacy Policy at https://legal.tensamin.net/privacy/\ @@ -152,17 +227,17 @@ impl ConsentState { eula_version = v.to_string(); } else if let Some(v) = line.strip_prefix("EULA-HASH=") { eula_hash = v.to_string(); - } else if let Some(v) = line.strip_prefix("ToS=") { + } else if let Some(v) = line.strip_prefix("Terms-of-Service=") { tos = v == "true"; - } else if let Some(v) = line.strip_prefix("ToS-VERSION=") { + } else if let Some(v) = line.strip_prefix("Terms-of-Service-VERSION=") { tos_version = v.to_string(); - } else if let Some(v) = line.strip_prefix("ToS-HASH=") { + } else if let Some(v) = line.strip_prefix("Terms-of-Service-HASH=") { tos_hash = v.to_string(); - } else if let Some(v) = line.strip_prefix("PrivacyPolicy=") { + } else if let Some(v) = line.strip_prefix("Privacy-Policy=") { pp = v == "true"; - } else if let Some(v) = line.strip_prefix("PrivacyPolicy-VERSION=") { + } else if let Some(v) = line.strip_prefix("Privacy-Policy-VERSION=") { pp_version = v.to_string(); - } else if let Some(v) = line.strip_prefix("PrivacyPolicy-HASH=") { + } else if let Some(v) = line.strip_prefix("Privacy-Policy-HASH=") { pp_hash = v.to_string(); } else if let Some(v) = line.strip_prefix("UNIX=") { unix = v.to_string(); @@ -173,7 +248,7 @@ impl ConsentState { accepted_eula: eula, eula: Some(Doc::new(eula_version, eula_hash, Type::EULA, unix)), accepted_pp: pp, - pp: Some(Doc::new(pp_version, pp_hash, Type::PP, unix)), + privacy: Some(Doc::new(pp_version, pp_hash, Type::PP, unix)), accepted_tos: tos, tos: Some(Doc::new(tos_version, tos_hash, Type::TOS, unix)), } diff --git a/src/terms/doc.rs b/src/terms/doc.rs index e2d6ceb..2365ba3 100644 --- a/src/terms/doc.rs +++ b/src/terms/doc.rs @@ -2,12 +2,12 @@ use json::{JsonValue, object::Object}; use crate::{terms::terms_getter::Type, util::file_util::load_file}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] #[allow(unused)] pub struct Doc { version: String, hash: String, - doc_type: Type, + pub doc_type: Type, timestamp: u64, } diff --git a/src/terms/focus.rs b/src/terms/focus.rs index f6691e4..632bbec 100644 --- a/src/terms/focus.rs +++ b/src/terms/focus.rs @@ -1,4 +1,4 @@ -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Focus { Eula, Tos, @@ -7,6 +7,7 @@ pub enum Focus { Continue, ContinueAll, } + impl Focus { pub fn next(&mut self, state: (bool, bool, bool)) { *self = match self { diff --git a/src/terms/terms_checker.rs b/src/terms/terms_checker.rs index 3334977..c385b21 100644 --- a/src/terms/terms_checker.rs +++ b/src/terms/terms_checker.rs @@ -2,7 +2,7 @@ use crate::terms::{ consent_state::{ConsentState, UserChoice}, focus::Focus, md_viewer::FileViewer, - terms_getter::{Type, get_link, get_terms}, + terms_getter::{Type, get_current_docs, get_link, get_terms}, }; use crossterm::event::{self, Event, KeyCode}; use ratatui::{ @@ -52,19 +52,19 @@ pub async fn run_consent_ui(mut consent: ConsentState) -> ConsentState { height: content_height, }); let eula_text = if size.width < 70 { - "EULA ¹ (https://legal.tensamin.net/eula/)" + "EULA ¹ (https://legal.tensamin.net/eula/newest/)" } else { - "End User Licence Agreement ¹ (https://legal.tensamin.net/eula/)" + "End User Licence Agreement ¹ (https://legal.tensamin.net/eula/newest/)" }; let tos_text = if size.width < 72 { - "ToS ² (https://legal.tensamin.net/terms-of-service/)" + "ToS ² (https://legal.tensamin.net/terms-of-service/newest/)" } else { - "Terms of Service ² (https://legal.tensamin.net/terms-of-service/)" + "Terms of Service ² (https://legal.tensamin.net/terms-of-service/newest/)" }; let pp_text = if size.width < 68 { - "PP ² (https://legal.tensamin.net/privacy-policy/)" + "PP ² (https://legal.tensamin.net/privacy-policy/newest/)" } else { - "Privacy Policy ² (https://legal.tensamin.net/privacy-policy/)" + "Privacy Policy ² (https://legal.tensamin.net/privacy-policy/newest/)" }; let (mut optional_lines, agree_lines): (Vec, Vec<&str>) = @@ -275,6 +275,12 @@ pub async fn run_consent_ui(mut consent: ConsentState) -> ConsentState { }; ratatui::restore(); + if let Some((eula, tos, privacy)) = get_current_docs().await { + consent.eula = Some(eula); + consent.tos = Some(tos); + consent.privacy = Some(privacy); + } + match result { UserChoice::AcceptAll => { consent.accepted_eula = true; @@ -292,6 +298,7 @@ pub async fn run_consent_ui(mut consent: ConsentState) -> ConsentState { consent.accepted_pp = false; } }; + consent } diff --git a/src/terms/terms_getter.rs b/src/terms/terms_getter.rs old mode 100644 new mode 100755 index 000e4aa..43b3c57 --- a/src/terms/terms_getter.rs +++ b/src/terms/terms_getter.rs @@ -2,7 +2,7 @@ use json::JsonValue::Object; use crate::terms::doc::Doc; -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Type { EULA, TOS, @@ -87,7 +87,6 @@ pub async fn get_newest_docs() -> Option<(Doc, Doc, Doc)> { None } } - pub async fn get_terms(terms_type: Type) -> Option { let body = reqwest::get(format!( "https://legal.tensamin.net/api/text/{}/", diff --git a/src/terms/terms_updater.rs b/src/terms/terms_updater.rs index 5a3b4ec..3d84b6f 100644 --- a/src/terms/terms_updater.rs +++ b/src/terms/terms_updater.rs @@ -1,5 +1,6 @@ use crate::terms::{ - consent_state::UserChoice, + consent_state::{ConsentState, UserChoice}, + doc::Doc, focus::Focus, md_viewer::FileViewer, terms_getter::{Type, get_link, get_terms}, @@ -13,10 +14,29 @@ use ratatui::{ }; use std::time::Duration; -pub async fn run_consent_ui() -> UserChoice { +pub async fn run_consent_ui( + mut consent: ConsentState, + consent_eula: Result, Doc>, + consent_tos: Result, Doc>, + consent_pp: Result, Doc>, +) -> ConsentState { let mut terminal = ratatui::init(); - - let (mut eula, mut tos, mut pp) = (false, false, false); + let (eula_needed, eula_future, future_eula) = match consent_eula { + Ok(Some(future)) => (true, true, Some(future)), + Ok(None) => (false, false, None), + Err(future) => (true, false, Some(future)), + }; + let (tos_needed, tos_future, future_tos) = match consent_tos { + Ok(Some(future)) => (true, true, Some(future)), + Ok(None) => (false, false, None), + Err(future) => (true, false, Some(future)), + }; + let (pp_needed, pp_future, future_privacy) = match consent_pp { + Ok(Some(future)) => (true, true, Some(future)), + Ok(None) => (false, false, None), + Err(future) => (true, false, Some(future)), + }; + let (mut eula, mut tos, mut pp) = (!eula_needed, !tos_needed, !pp_needed); let mut focus = Focus::Eula; let result = loop { @@ -51,28 +71,68 @@ pub async fn run_consent_ui() -> UserChoice { width: content_width, height: content_height, }); - let eula_text = if size.width < 70 { - "EULA ¹ (https://legal.tensamin.net/eula/)" - } else { - "End User Licence Agreement ¹ (https://legal.tensamin.net/eula/)" - }; - let tos_text = if size.width < 72 { - "ToS ² (https://legal.tensamin.net/terms-of-service/)" - } else { - "Terms of Service ² (https://legal.tensamin.net/terms-of-service/)" - }; - let pp_text = if size.width < 68 { - "PP ² (https://legal.tensamin.net/privacy-policy/)" - } else { - "Privacy Policy ² (https://legal.tensamin.net/privacy-policy/)" - }; + + + let mut text_lines = Vec::new(); + + + if eula_needed { + if eula_future { + if size.width < 70 { + text_lines.push(checkbox("EULA ¹³ (https://legal.tensamin.net/eula/newest/)", eula, focus == Focus::Eula, true)); + } else { + text_lines.push(checkbox("End User Licence Agreement ¹³ (https://legal.tensamin.net/eula/newest/)", eula, focus == Focus::Eula, true)); + } + } else { + if size.width < 70 { + text_lines.push(checkbox("EULA ¹ (https://legal.tensamin.net/eula/newest/)", eula, focus == Focus::Eula, true)); + } else { + text_lines.push(checkbox("End User Licence Agreement ¹ (https://legal.tensamin.net/eula/newest/)", eula, focus == Focus::Eula, true)); + } + } + } + + if tos_needed { + if tos_future { + if size.width < 70 { + text_lines.push(checkbox("ToS ²³ (https://legal.tensamin.net/terms-of-service/newest/)", tos, focus == Focus::Tos, eula)); + } else { + text_lines.push(checkbox("Terms of Service ²³ (https://legal.tensamin.net/terms-of-service/newest/)", tos, focus == Focus::Tos, eula)); + } + } else { + if size.width < 70 { + text_lines.push(checkbox("ToS ² (https://legal.tensamin.net/terms-of-service/newest/)", tos, focus == Focus::Tos, eula)); + } else { + text_lines.push(checkbox("Terms of Service ² (https://legal.tensamin.net/terms-of-service/newest/)", tos, focus == Focus::Tos, eula)); + } + } + } + + if pp_needed { + if pp_future { + if size.width < 70 { + text_lines.push(checkbox("PP ²³ (https://legal.tensamin.net/privacy-policy/newest/)", pp, focus == Focus::Pp, eula)); + } else { + text_lines.push(checkbox("Privacy Policy ²³ (https://legal.tensamin.net/privacy-policy/newest/)", pp, focus == Focus::Pp, eula)); + } + } else { + if size.width < 70 { + text_lines.push(checkbox("PP ² (https://legal.tensamin.net/privacy-policy/newest/)", pp, focus == Focus::Pp, pp)); + } else { + text_lines.push(checkbox("Privacy Policy ² (https://legal.tensamin.net/privacy-policy/newest/)", pp, focus == Focus::Pp, eula)); + } + } + } + + text_lines.push(Line::from("")); + text_lines.push(Line::from("¹ Necessary, ² Optional, ³ Future")); + text_lines.push(Line::from("")); let (mut optional_lines, agree_lines): (Vec, Vec<&str>) = if size.width > 143 { ( vec![7, 3, 7, 4], vec![ - "", "By selecting Continue, you confirm that you have read, understood and agree to the End User License Agreement and applicable Terms of Service.", "", "Tensamin services require acceptance of the Terms of Service and Privacy Policy.", @@ -97,7 +157,6 @@ pub async fn run_consent_ui() -> UserChoice { ( vec![8, 3, 8, 4], vec![ - "", "By selecting Continue, you confirm that you have read, understood and", "agree to the End User License Agreement and applicable Terms of Service.", "", @@ -111,7 +170,6 @@ pub async fn run_consent_ui() -> UserChoice { ( vec![9, 3, 10, 4], vec![ - "", "By selecting Continue, you confirm that you have", "read, understood and agree to the End User License", "Agreement and applicable Terms of Service.", @@ -124,13 +182,8 @@ pub async fn run_consent_ui() -> UserChoice { ] ) }; - let mut text_lines = vec![ - checkbox(eula_text, eula, focus == Focus::Eula, true), - checkbox(tos_text, tos, focus == Focus::Tos, eula), - checkbox(pp_text, pp, focus == Focus::Pp, eula), - Line::from(""), - Line::from("¹ Necessary, ² Optional"), - ]; + + for line in agree_lines { text_lines.insert(text_lines.len(), Line::from(line)); @@ -191,10 +244,10 @@ pub async fn run_consent_ui() -> UserChoice { } let consent_block = Paragraph::new(Text::from(text_lines)) - .block(Block::default().title(" Tensamin User Consent [Q to Quit] ").borders(Borders::ALL)); + .block(Block::default().title(format!(" Update Tensamin User Consent [Q to Quit] {}, {}, {}", pp_needed, eula_needed, tos_needed)).borders(Borders::ALL)); f.render_widget(consent_block, chunks[0]); - draw_buttons(f, chunks[1], (eula, tos, pp)); + draw_buttons(f, chunks[1], focus, (eula, tos, pp)); }) .unwrap(); @@ -275,7 +328,23 @@ pub async fn run_consent_ui() -> UserChoice { }; ratatui::restore(); - result + match result { + UserChoice::AcceptAll => { + consent.accepted_eula = true; + consent.accepted_tos = true; + consent.accepted_pp = true; + consent.eula = future_eula; + consent.tos = future_tos; + consent.privacy = future_privacy; + } + UserChoice::AcceptEULA => { + consent.accepted_eula = true; + consent.eula = future_eula; + } + UserChoice::Deny => {} + }; + + consent } #[allow(mismatched_lifetime_syntaxes)] @@ -313,7 +382,12 @@ fn draw_button(f: &mut ratatui::Frame, area: Rect, label: &str, style: Style) { .block(Block::default().borders(Borders::ALL)); f.render_widget(p, area); } -fn draw_buttons(f: &mut ratatui::Frame, area: Rect, state: (bool, bool, bool)) { +fn draw_buttons( + f: &mut ratatui::Frame, + area: Rect, + current_focus: Focus, + state: (bool, bool, bool), +) { let buttons = vec![ ("[Q] Cancel", Focus::Cancel), ("Continue", Focus::Continue), @@ -339,9 +413,11 @@ fn draw_buttons(f: &mut ratatui::Frame, area: Rect, state: (bool, bool, bool)) { }; x += width; + let is_focused = current_focus == *focus; + let style = match focus { Focus::Cancel => { - if focus == &Focus::Cancel { + if is_focused { Style::default() .fg(Color::Black) .bg(Color::Red) @@ -350,20 +426,22 @@ fn draw_buttons(f: &mut ratatui::Frame, area: Rect, state: (bool, bool, bool)) { Style::default().fg(Color::Red) } } + Focus::Continue => { - if focus == &Focus::Continue && state.0 { + if is_focused && state.0 { Style::default() .fg(Color::Black) .bg(Color::Green) .add_modifier(Modifier::BOLD) - } else if state.0 && state.1 && state.2 { - Style::default().fg(Color::Green) + } else if state.0 { + Style::default().fg(Color::Green) // enabled but not focused } else { Style::default().fg(Color::DarkGray) } } + Focus::ContinueAll => { - if focus == &Focus::ContinueAll && state.0 && state.1 && state.2 { + if is_focused && state.0 && state.1 && state.2 { Style::default() .fg(Color::Black) .bg(Color::Green) @@ -374,13 +452,13 @@ fn draw_buttons(f: &mut ratatui::Frame, area: Rect, state: (bool, bool, bool)) { Style::default().fg(Color::DarkGray) } } + _ => Style::default().fg(Color::DarkGray), }; draw_button(f, chunk, label, style); } } - fn compute_widths(area_width: u16, min_widths: &[u16]) -> Vec { let mut widths = vec![0; min_widths.len()]; let mut remaining: Vec = (0..min_widths.len()).collect();