use crate::app::AppState; use crate::data::repositories::Repository; use crate::data_provider::settings::{delete_settings, set_setting}; use crate::data_provider::web_api::*; use crate::jl_button; use crate::navigation::Page::PreviousPage; use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::notification::{NotificationData, NotificationRequest}; use crate::pages::sync::SyncMessage::*; use crate::styling::*; use crate::{push_error, push_error_task}; use iced::widget::button::danger; use iced::widget::container::rounded_box; use iced::widget::{button, column, container, progress_bar, row, space, text, toggler}; use iced::{Center, Element, Fill, Font, Length, Task}; use log::error; use std::time::Duration; use crate::notification::NotificationData::LocalVersionChanged; #[derive(Clone)] pub enum SyncMessage { Back, CopyKey, KeyCopied, IdReceived(String), InitSync, GetKey, Send, GetLast, NextAnimation, NetworkFinished, Disable, DisableSync, SwitchAutoSync, ResetRealLoad, } pub struct SyncState { state: AppState, auto_fetch: bool, frozen: bool, real_load: bool, progress: f32, } impl NavigatedPage for SyncState { fn navigate(&mut self, message: &SyncMessage) -> Option { if let Back = message && !self.frozen { Some(PreviousPage) } else { None } } fn navigated(&mut self) {} fn update(&mut self, message: SyncMessage) -> Task { match message { Back => {} CopyKey => { let key = self.state.sync_data.read().key.clone().unwrap(); return iced::clipboard::write(key) .map(|_val: String| RootMessage::Sync(KeyCopied)); } InitSync => { return Task::perform(first_sync(), |id| match id { Ok(id) => RootMessage::Sync(IdReceived(id)), Err(err) => push_error!(err), }); } GetKey => { return iced::clipboard::read() .map(|key| RootMessage::Sync(IdReceived(key.unwrap_or_else(String::new)))); } KeyCopied => {} IdReceived(new_id) => { if !validate_id(&new_id) { return Task::none(); } let mut settings = self.state.sync_data.write(); settings.key = Some(new_id.clone()); if let Err(err) = set_setting( "SYNC_KEY".to_string(), new_id, &self.state.connection.lock(), ) { return push_error_task!(err); } } Send => { self.prepare_to_db_interaction(); let id = self.state.sync_data.read().key.clone().unwrap(); let tasks = Task::batch([ Task::perform( async { tokio::time::sleep(Duration::from_millis(200)).await }, |_| RootMessage::Sync(NextAnimation), ), Task::perform(send_data(id), |_| RootMessage::Sync(NetworkFinished)), ]); return tasks; } GetLast => { if !self.real_load { self.real_load = true; return Task::batch([ Task::future(async { tokio::time::sleep(Duration::from_millis(3000)).await; RootMessage::Sync(ResetRealLoad) }), Task::done(RootMessage::PushNotification(NotificationRequest::Warning(LocalVersionChanged))) ]); } self.real_load = false; let id = self.state.sync_data.read().key.clone().unwrap(); self.state.connection.lock().finish(); let tasks = Task::batch([ Task::perform( async { tokio::time::sleep(Duration::from_millis(200)).await }, |_| RootMessage::Sync(NextAnimation), ), Task::perform(load_data(id, false), |load_result| match load_result { Ok(_) => RootMessage::Sync(NetworkFinished), Err(error) => push_error!(error) }), ]); return tasks; } NextAnimation => { self.progress += 1.0; if self.progress > 5.0 { self.progress = 0.0; } if self.frozen { return Task::perform( async { tokio::time::sleep(Duration::from_millis(200)).await }, |_| RootMessage::Sync(NextAnimation), ); } } NetworkFinished => { self.frozen = false; { let mut connection = self.state.connection.lock(); connection.restart(); } if let Err(err) = self.reload_data() { return push_error_task!(err); } return Task::done(RootMessage::PushNotification(NotificationRequest::Info( NotificationData::DataSynced, ))); } Disable => {} DisableSync => { let mut sync_data = self.state.sync_data.write(); let connection = self.state.connection.lock(); sync_data.key = None; let save_result = || { delete_settings("SYNC_KEY".to_string(), &connection)?; delete_settings("AUTO_WEB_FETCH".to_string(), &connection)?; Ok::<(), NotificationData>(()) }; if let Err(error) = save_result() { return push_error_task!(error); } } SwitchAutoSync => { self.auto_fetch = !self.auto_fetch; let mut sync_data = self.state.sync_data.write(); let connection = self.state.connection.lock(); sync_data.auto_web_fetch = self.auto_fetch; if let Err(err) = set_setting( "AUTO_WEB_FETCH".to_string(), self.auto_fetch.to_string(), &connection, ) { return push_error_task!(err); } } ResetRealLoad => { self.real_load = false; } } Task::none() } fn view(&self) -> Element<'_, SyncMessage> { back_overlay( row![space().width(Fill), self.sync_column(), space().width(Fill)] .spacing(DEFAULT_SPACING) .width(Fill) .into(), Back, ) } } impl SyncState { pub fn new(state: AppState) -> SyncState { let auto_fetch = state.sync_data.read().auto_web_fetch; Self { auto_fetch, state, frozen: false, real_load: false, progress: 0.0, } } fn reload_data(&mut self) -> Result<(), NotificationData> { self.state.card_states.write().reload()?; self.state.word_groups.write().reload()?; self.state.decks.write().reload()?; self.state.dictionary.write().reload()?; self.state.history.write().reload()?; self.state.exam.write().reload()?; self.state.exam_entries.write().reload()?; Ok(()) } fn sync_column(&self) -> Element<'_, SyncMessage> { let network_view: Element<'_, SyncMessage> = if self.frozen { progress_bar(0.0..=5.0, self.progress).into() } else { space().into() }; { if let Some(key) = self.state.sync_data.read().key.as_ref() { column![ text!("Ваш ключ синхронизации"), container( container( text!("{}", key) .size(ACCENT_FONT_SIZE) .font(Font::MONOSPACE) ) .padding(3) ) .style(rounded_box), jl_button!("Скопировать в буфер обмена") .on_press(CopyKey) .width(Fill), row![ jl_button!("↑ Отправить").on_press(Send), space().width(Fill), jl_button!("↓ Скачать").on_press(GetLast) ] .spacing(DEFAULT_SPACING), toggler(self.auto_fetch) .label("Автоматическая синхронизация") .on_toggle(|_| SwitchAutoSync), container(network_view).width(Fill), button("Отключить синхронизацию") .style(danger) .on_press(DisableSync), ] } else { column![ jl_button!("Создать сохранение").on_press(InitSync), jl_button!("Вставить ключ из буфера").on_press(GetKey), ] } } .spacing(DEFAULT_SPACING) .align_x(Center) .width(Length::Shrink) .into() } /* fn app_updater(&self) -> Element<'_, SyncMessage> { column![].width(Fill).into() }*/ fn prepare_to_db_interaction(&mut self) { if self.frozen { return; } self.frozen = true; self.progress = 0.0; let mut connection = self.state.connection.lock(); connection .query_row("PRAGMA wal_checkpoint(TRUNCATE)", [], |_| Ok(())) .unwrap_or_else(|e| { error!("Ошибка при checkpoint WAL: {e}"); }); connection.execute("VACUUM;", []).unwrap_or_else(|e| { error!("Ошибка при checkpoint WAL: {e}"); 0 }); connection .execute("PRAGMA optimize;", []) .unwrap_or_else(|e| { error!("Ошибка при checkpoint WAL: {e}"); 0 }); connection.finish(); } } #[inline] fn validate_id(id: &str) -> bool { id.len() == 24 && id.chars().all(|c| c.is_ascii_alphanumeric()) }