diff --git a/src/terms/consent_state.rs b/src/terms/consent_state.rs index 6a9ccaf..09146f4 100644 --- a/src/terms/consent_state.rs +++ b/src/terms/consent_state.rs @@ -8,7 +8,6 @@ use crate::{ util::file_util::{load_file, save_file}, }; use std::time::{SystemTime, UNIX_EPOCH}; -use tokio::task; pub struct ConsentManager; impl ConsentManager { @@ -30,7 +29,9 @@ impl ConsentManager { } 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 _is_forced = matches!(eula_update, UpdateDecision::Forced(_)) + || matches!(tos_update, UpdateDecision::Forced(_)) + || matches!(privacy_update, UpdateDecision::Forced(_)); let updater_logic = async move { let state_to_update = terms_updater::run_consent_ui( @@ -45,12 +46,14 @@ impl ConsentManager { save_file("", "agreements", &state_to_update.sanitize().to_string()); }; - - if is_forced { - updater_logic.await; - } else { - task::spawn(updater_logic); - } + // ======================================================== // + // TODO: Implement UI Drawing order, draw update As PoPup // + // ======================================================== // + //if is_forced { + updater_logic.await; + //} else { + //task::spawn(updater_logic); + //} } let final_state = ConsentState::from_str(&load_file("", "agreements")).sanitize(); @@ -64,9 +67,9 @@ impl ConsentManager { // 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>, + UpdateDecision, + UpdateDecision, + UpdateDecision, )> { let file = load_file("", "agreements"); let accepted_state = ConsentState::from_str(&file).sanitize(); @@ -76,46 +79,47 @@ impl ConsentManager { Some((newest_eula, newest_tos, newest_privacy)), ) = (get_current_docs().await, get_newest_docs().await) { - 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)) + let eula_update: UpdateDecision = if current_eula.equals_some(&accepted_state.eula) { + if current_eula.equals(&newest_eula) { + UpdateDecision::NoChange + } else { + UpdateDecision::Future { + newest: 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 if newest_eula.equals_some(&accepted_state.eula) { + UpdateDecision::NoChange } else { - Err(current_tos) + UpdateDecision::Forced(current_eula) }; - let privacy_update: Result, Doc> = + let tos_update: UpdateDecision = if newest_tos.equals_some(&accepted_state.tos) { + UpdateDecision::NoChange + } else if accepted_state.accepted_tos && current_tos.equals_some(&accepted_state.tos) { + if current_tos.equals(&newest_tos) { + UpdateDecision::NoChange + } else { + UpdateDecision::Future { newest: newest_tos } + } + } else { + UpdateDecision::Forced(current_tos) + }; + + let privacy_update: UpdateDecision = if newest_privacy.equals_some(&accepted_state.privacy) { - Ok(None) + UpdateDecision::NoChange } else if accepted_state.accepted_pp && current_privacy.equals_some(&accepted_state.privacy) { if current_privacy.equals(&newest_privacy) { - Ok(None) + UpdateDecision::NoChange } else { - Ok(Some(newest_privacy)) + UpdateDecision::Future { + newest: newest_privacy, + } } } else { - Err(current_privacy) + UpdateDecision::Forced(current_privacy) }; Some((eula_update, tos_update, privacy_update)) } else { @@ -131,14 +135,26 @@ pub enum UserChoice { AcceptAll, } +#[derive(Debug, Clone, PartialEq)] +pub enum UpdateDecision { + NoChange, + Future { newest: Doc }, + Forced(Doc), +} + #[derive(Debug, Clone)] pub struct ConsentState { pub eula: Option, pub accepted_eula: bool, + pub future_eula: Option, + pub tos: Option, pub accepted_tos: bool, + pub future_tos: Option, + pub privacy: Option, pub accepted_pp: bool, + pub future_privacy: Option, } impl ConsentState { @@ -165,12 +181,6 @@ impl ConsentState { ); 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={}\ @@ -204,6 +214,44 @@ impl ConsentState { \nEULA=false\ "); } + + if let Some(eula) = &self.future_eula { + file_out.push_str(&format!( + "\ + \nFUTURE-EULA-VERSION={}\ + \nFUTURE-EULA-HASH={}\ + \nFUTURE-EULA-TIME={}\ + ", + eula.get_version(), + eula.get_hash(), + eula.get_time() + )); + } + if let Some(tos) = &self.future_tos { + file_out.push_str(&format!( + "\ + \nFUTURE-Terms-of-Service-VERSION={}\ + \nFUTURE-Terms-of-Service-HASH={}\ + \nFUTURE-Terms-of-Service-TIME={}\ + ", + tos.get_version(), + tos.get_hash(), + tos.get_time() + )); + } + if let Some(pp) = &self.future_privacy { + file_out.push_str(&format!( + "\ + \nFUTURE-Privacy-Policy-VERSION={}\ + \nFUTURE-Privacy-Policy-HASH={}\ + \nFUTURE-Privacy-Policy-TIME={}\ + ", + pp.get_version(), + pp.get_hash(), + pp.get_time() + )); + } + file_out } @@ -218,6 +266,18 @@ impl ConsentState { let mut tos_version = String::new(); let mut tos_hash = String::new(); + let mut future_eula_version = String::new(); + let mut future_eula_hash = String::new(); + let mut future_eula_time = String::new(); + + let mut future_tos_version = String::new(); + let mut future_tos_hash = String::new(); + let mut future_tos_time = String::new(); + + let mut future_pp_version = String::new(); + let mut future_pp_hash = String::new(); + let mut future_pp_time = String::new(); + let mut unix = String::new(); for line in s.lines() { @@ -241,9 +301,30 @@ impl ConsentState { pp_hash = v.to_string(); } else if let Some(v) = line.strip_prefix("UNIX=") { unix = v.to_string(); + } else if let Some(v) = line.strip_prefix("FUTURE-EULA-VERSION=") { + future_eula_version = v.to_string(); + } else if let Some(v) = line.strip_prefix("FUTURE-EULA-HASH=") { + future_eula_hash = v.to_string(); + } else if let Some(v) = line.strip_prefix("FUTURE-EULA-TIME=") { + future_eula_time = v.to_string(); + } else if let Some(v) = line.strip_prefix("FUTURE-Terms-of-Service-VERSION=") { + future_tos_version = v.to_string(); + } else if let Some(v) = line.strip_prefix("FUTURE-Terms-of-Service-HASH=") { + future_tos_hash = v.to_string(); + } else if let Some(v) = line.strip_prefix("FUTURE-Terms-of-Service-TIME=") { + future_tos_time = v.to_string(); + } else if let Some(v) = line.strip_prefix("FUTURE-Privacy-Policy-VERSION=") { + future_pp_version = v.to_string(); + } else if let Some(v) = line.strip_prefix("FUTURE-Privacy-Policy-HASH=") { + future_pp_hash = v.to_string(); + } else if let Some(v) = line.strip_prefix("FUTURE-Privacy-Policy-TIME=") { + future_pp_time = v.to_string(); } } let unix: u64 = unix.parse::().unwrap_or(0); + let future_eula_time = future_eula_time.parse::().unwrap_or(0); + let future_tos_time = future_tos_time.parse::().unwrap_or(0); + let future_pp_time = future_pp_time.parse::().unwrap_or(0); Self { accepted_eula: eula, eula: Some(Doc::new(eula_version, eula_hash, Type::EULA, unix)), @@ -251,6 +332,36 @@ impl ConsentState { 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)), + future_eula: if !future_eula_version.is_empty() { + Some(Doc::new( + future_eula_version, + future_eula_hash, + Type::EULA, + future_eula_time, + )) + } else { + None + }, + future_tos: if !future_tos_version.is_empty() { + Some(Doc::new( + future_tos_version, + future_tos_hash, + Type::TOS, + future_tos_time, + )) + } else { + None + }, + future_privacy: if !future_pp_version.is_empty() { + Some(Doc::new( + future_pp_version, + future_pp_hash, + Type::PP, + future_pp_time, + )) + } else { + None + }, } .sanitize() } diff --git a/src/terms/focus.rs b/src/terms/focus.rs index 632bbec..669e936 100644 --- a/src/terms/focus.rs +++ b/src/terms/focus.rs @@ -9,45 +9,17 @@ pub enum Focus { } impl Focus { - pub fn next(&mut self, state: (bool, bool, bool)) { - *self = match self { - Focus::Eula => Focus::Tos, - Focus::Tos => Focus::Pp, - Focus::Pp => Focus::Cancel, - Focus::Cancel => { - if state.0 { - Focus::Continue - } else { - Focus::Eula - } - } - Focus::Continue => { - if state.0 && state.1 && state.2 { - Focus::ContinueAll - } else { - Focus::Eula - } - } - Focus::ContinueAll => Focus::Eula, - }; + pub fn next(&mut self, order: &[Focus]) { + if let Some(pos) = order.iter().position(|&f| f == *self) { + let next_pos = (pos + 1) % order.len(); + *self = order[next_pos]; + } } - pub fn prev(&mut self, state: (bool, bool, bool)) { - *self = match self { - Focus::Eula => { - if state.0 && state.1 && state.2 { - Focus::ContinueAll - } else if state.0 { - Focus::Continue - } else { - Focus::Cancel - } - } - Focus::Tos => Focus::Eula, - Focus::Pp => Focus::Tos, - Focus::Cancel => Focus::Pp, - Focus::Continue => Focus::Cancel, - Focus::ContinueAll => Focus::Continue, - }; + pub fn prev(&mut self, order: &[Focus]) { + if let Some(pos) = order.iter().position(|&f| f == *self) { + let prev_pos = if pos == 0 { order.len() - 1 } else { pos - 1 }; + *self = order[prev_pos]; + } } } diff --git a/src/terms/terms_checker.rs b/src/terms/terms_checker.rs index c385b21..0973a20 100644 --- a/src/terms/terms_checker.rs +++ b/src/terms/terms_checker.rs @@ -207,10 +207,17 @@ pub async fn run_consent_ui(mut consent: ConsentState) -> ConsentState { continue; } } + let mut possible_states = vec![Focus::Eula, Focus::Tos, Focus::Pp, Focus::Cancel]; + if eula { + possible_states.push(Focus::Continue); + if tos && pp { + possible_states.push(Focus::ContinueAll); + } + } match key.code { KeyCode::Esc => break UserChoice::Deny, - KeyCode::Up | KeyCode::Left => focus.prev((eula, tos, pp)), - KeyCode::Down | KeyCode::Right | KeyCode::Tab => focus.next((eula, tos, pp)), + KeyCode::Up | KeyCode::Left => focus.prev(&possible_states), + KeyCode::Down | KeyCode::Right | KeyCode::Tab => focus.next(&possible_states), KeyCode::Char('q') | KeyCode::Char('Q') => break UserChoice::Deny, KeyCode::Char('o') | KeyCode::Char('O') => { let terms_type = match focus { diff --git a/src/terms/terms_updater.rs b/src/terms/terms_updater.rs index 3d84b6f..c877b0e 100644 --- a/src/terms/terms_updater.rs +++ b/src/terms/terms_updater.rs @@ -1,5 +1,5 @@ use crate::terms::{ - consent_state::{ConsentState, UserChoice}, + consent_state::{ConsentState, UpdateDecision, UserChoice}, doc::Doc, focus::Focus, md_viewer::FileViewer, @@ -16,26 +16,29 @@ use std::time::Duration; pub async fn run_consent_ui( mut consent: ConsentState, - consent_eula: Result, Doc>, - consent_tos: Result, Doc>, - consent_pp: Result, Doc>, + consent_eula: UpdateDecision, + consent_tos: UpdateDecision, + consent_pp: UpdateDecision, ) -> ConsentState { let mut terminal = ratatui::init(); - 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 (eula_needed, eula_future, eula_for_future): (bool, bool, Option) = match consent_eula + { + UpdateDecision::NoChange => (false, false, None), + UpdateDecision::Future { newest } => (true, true, Some(newest)), + UpdateDecision::Forced(doc) => (true, false, Some(doc)), }; - 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 (tos_needed, tos_future, tos_for_future): (bool, bool, Option) = match consent_tos { + UpdateDecision::NoChange => (false, false, None), + UpdateDecision::Future { newest } => (true, true, Some(newest)), + UpdateDecision::Forced(doc) => (true, false, Some(doc)), }; - 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 (pp_needed, pp_future, pp_for_future): (bool, bool, Option) = match consent_pp { + UpdateDecision::NoChange => (false, false, None), + UpdateDecision::Future { newest } => (true, true, Some(newest)), + UpdateDecision::Forced(doc) => (true, false, Some(doc)), }; + let update_needed = eula_needed || tos_needed || pp_needed; let (mut eula, mut tos, mut pp) = (!eula_needed, !tos_needed, !pp_needed); let mut focus = Focus::Eula; @@ -247,7 +250,15 @@ pub async fn run_consent_ui( .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], focus, (eula, tos, pp)); + let downgrade_scenario = tos_needed || pp_needed; + draw_buttons( + f, + chunks[1], + focus, + (eula, tos && pp), + update_needed, + downgrade_scenario, + ); }) .unwrap(); @@ -260,10 +271,17 @@ pub async fn run_consent_ui( continue; } } + let mut possible_states = vec![Focus::Eula, Focus::Tos, Focus::Pp, Focus::Cancel]; + if eula { + possible_states.push(Focus::Continue); + if tos && pp { + possible_states.push(Focus::ContinueAll); + } + } match key.code { KeyCode::Esc => break UserChoice::Deny, - KeyCode::Up | KeyCode::Left => focus.prev((eula, tos, pp)), - KeyCode::Down | KeyCode::Right | KeyCode::Tab => focus.next((eula, tos, pp)), + KeyCode::Up | KeyCode::Left => focus.prev(&possible_states), + KeyCode::Down | KeyCode::Right | KeyCode::Tab => focus.next(&possible_states), KeyCode::Char('q') | KeyCode::Char('Q') => break UserChoice::Deny, KeyCode::Char('o') | KeyCode::Char('O') => { let terms_type = match focus { @@ -298,8 +316,8 @@ pub async fn run_consent_ui( KeyCode::Char(' ') => match focus { Focus::Eula => { eula = !eula; - tos = false; - pp = false; + tos = !tos_needed; + pp = !pp_needed; } Focus::Tos => { if eula { @@ -333,16 +351,16 @@ pub async fn run_consent_ui( consent.accepted_eula = true; consent.accepted_tos = true; consent.accepted_pp = true; - consent.eula = future_eula; - consent.tos = future_tos; - consent.privacy = future_privacy; + consent.future_eula = eula_for_future; + consent.future_tos = tos_for_future; + consent.future_privacy = pp_for_future; } UserChoice::AcceptEULA => { consent.accepted_eula = true; - consent.eula = future_eula; + consent.future_eula = eula_for_future; } UserChoice::Deny => {} - }; + } consent } @@ -386,11 +404,23 @@ fn draw_buttons( f: &mut ratatui::Frame, area: Rect, current_focus: Focus, - state: (bool, bool, bool), + state: (bool, bool), + update_needed: bool, + downgrade_scenario: bool, ) { + let cancel_text = if update_needed { + "[Q] Quit" + } else { + "[Q] Not now" + }; + let continue_text = if downgrade_scenario { + "Downgrade" + } else { + "Continue" + }; let buttons = vec![ - ("[Q] Cancel", Focus::Cancel), - ("Continue", Focus::Continue), + (cancel_text, Focus::Cancel), + (continue_text, Focus::Continue), ("Continue with Tensamin Services", Focus::ContinueAll), ]; @@ -434,19 +464,19 @@ fn draw_buttons( .bg(Color::Green) .add_modifier(Modifier::BOLD) } else if state.0 { - Style::default().fg(Color::Green) // enabled but not focused + Style::default().fg(Color::Green) } else { Style::default().fg(Color::DarkGray) } } Focus::ContinueAll => { - if is_focused && state.0 && state.1 && state.2 { + if is_focused && state.1 { Style::default() .fg(Color::Black) .bg(Color::Green) .add_modifier(Modifier::BOLD) - } else if state.0 && state.1 && state.2 { + } else if state.1 { Style::default().fg(Color::Green) } else { Style::default().fg(Color::DarkGray)