From 3fcfe95720e0d3d84cf37df35f3a3cb9bf7da962 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Sun, 23 Aug 2026 22:16:02 +0300 Subject: [PATCH] More notifications --- src/data_provider/web_api.rs | 34 ++++++++++++++++++---------------- src/navigation.rs | 35 ++++++++++++++++++++--------------- src/notification.rs | 4 ++++ src/sync.rs | 2 +- 4 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/data_provider/web_api.rs b/src/data_provider/web_api.rs index 983d54f..0d6ac18 100644 --- a/src/data_provider/web_api.rs +++ b/src/data_provider/web_api.rs @@ -4,14 +4,16 @@ use reqwest::Error; use tokio::fs::{File, OpenOptions}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use zstd::{DEFAULT_COMPRESSION_LEVEL, Decoder, Encoder}; +use crate::notification::NotificationData; +use crate::notification::NotificationData::{PermissionDenied, WebApiUnavailable}; // const API_URL: &str = "http://127.0.0.1:8089/"; const API_URL: &str = "https://learning.micialware.ru/"; -pub async fn send_data(id: String) -> Result<(), ()> { +pub async fn send_data(id: String) -> Result<(), NotificationData> { let path = app_data_dir(); let db_file = path.join("data.db"); - let data = tokio::fs::read(db_file).await.map_err(|_| ())?; + let data = tokio::fs::read(db_file).await.map_err(|_| WebApiUnavailable)?; let data = compress(data); let api_url = API_URL.to_string(); @@ -22,10 +24,10 @@ pub async fn send_data(id: String) -> Result<(), ()> { .post(&id_url) .body(data) .send() - .await.map_err(|_| ())? + .await.map_err(|_| WebApiUnavailable)? .text() - .await.map_err(|_| ())?; - set_local_version(new_version.parse::().unwrap()).await; + .await.map_err(|_| WebApiUnavailable)?; + set_local_version(new_version.parse::().unwrap_or_default()).await; Ok(()) } @@ -35,16 +37,16 @@ fn compress(data: Vec) -> Vec { encoder.finish().unwrap() } -pub async fn load_data(id: String, temp: bool) -> Result<(), Error> { +pub async fn load_data(id: String, temp: bool) -> Result<(), NotificationData> { let api_url = API_URL.to_string(); let id_url = format!("{api_url}download/{id}"); let client = reqwest::Client::new(); let data = client .get(&id_url) .send() - .await? + .await.map_err(|_| WebApiUnavailable)? .bytes() - .await? + .await.map_err(|_| WebApiUnavailable)? .to_vec(); let data = decompress(data); @@ -52,15 +54,15 @@ pub async fn load_data(id: String, temp: bool) -> Result<(), Error> { let file_name = if temp { "data.db.tmp" } else { "data.db" }; let db_file = path.join(file_name); - tokio::fs::write(db_file, data).await.unwrap(); + tokio::fs::write(db_file, data).await.map_err(|_| PermissionDenied)?; let version_url = format!("{API_URL}{id}/version"); let version = client .get(&version_url) .send() - .await? + .await.map_err(|_| WebApiUnavailable)? .text() - .await?; + .await.map_err(|_| WebApiUnavailable)?; set_local_version(version.parse::().unwrap_or_default()).await; Ok(()) } @@ -75,7 +77,7 @@ pub fn decompress(data: Vec) -> Vec { decompressed } -pub async fn first_sync() -> Result { +pub async fn first_sync() -> Result { let id_url = API_URL.to_string() + "generate"; let client = reqwest::Client::new(); @@ -83,18 +85,18 @@ pub async fn first_sync() -> Result { .get(&id_url) .send() .await - .map_err(|_| ())? + .map_err(|_| WebApiUnavailable)? .text() .await - .map_err(|_| ())?; + .map_err(|_| WebApiUnavailable)?; println!("id: {}", id); Ok(id) } -pub async fn get_web_version(key: &str) -> Result { +pub async fn get_web_version(key: &str) -> Result { let id_url = format!("{API_URL}{key}/version"); let client = reqwest::Client::new(); - let version = client.get(&id_url).send().await?.text().await?; + let version = client.get(&id_url).send().await.map_err(|_| WebApiUnavailable)?.text().await.map_err(|_| WebApiUnavailable)?; println!("version: {}", version); Ok(version.parse::().unwrap_or_default()) } diff --git a/src/navigation.rs b/src/navigation.rs index fa65d57..b217691 100644 --- a/src/navigation.rs +++ b/src/navigation.rs @@ -1,3 +1,4 @@ +use crate::app::{fill_state, AppState}; use crate::data_provider::history::{get_history_of_set, history_dir}; use crate::data_provider::sqlite::{create_db, default_connection}; use crate::data_provider::web_api::{get_local_version, get_web_version, load_data, set_local_version}; @@ -8,8 +9,8 @@ use crate::import::{ImportMessage, ImportState}; use crate::lang::Id; use crate::message_navigation; use crate::navigation::Page::*; -use crate::navigation::RootMessage::{DataLoaded, Keyboard, PushNotification, UpdateData, Notification}; -use crate::notification::{NotificationRequest, NotificationManager, NotificationMessage}; +use crate::navigation::RootMessage::{DataLoaded, Keyboard, Notification, PushNotification, UpdateData}; +use crate::notification::{NotificationData, NotificationManager, NotificationMessage, NotificationRequest}; use crate::quiz::{QuizMessage, QuizState}; use crate::randomizer::{RandomizerMessage, RandomizerState}; use crate::repetition::{RepetitionMessage, RepetitionState}; @@ -22,18 +23,16 @@ use crate::sync::{SyncMessage, SyncState}; use crate::view_navigation; use crate::word::{WordMessage, WordState}; use crate::writing::{WritingMessage, WritingState}; -use crate::app::{fill_state, AppState}; use chrono::NaiveDate; use hashbrown::HashMap; use iced::keyboard::Event; use iced::widget::stack; use iced::{Element, Task}; +use iced_core::Length::Fill; use parking_lot::Mutex; -use reqwest::Error; use rusqlite::Connection; use std::sync::Arc; use std::time::Instant; -use iced_core::Length::Fill; impl Default for ScreenState { fn default() -> Self { @@ -108,15 +107,21 @@ impl ScreenState { let key = state_guard.sync_data.key.clone().unwrap(); final_task = Task::batch([ reading_additional_task, - Task::perform(Self::load_web_backup(key), |result| { - if let Ok(update) = result - && update - { - UpdateData - } else { - RootMessage::None + Task::perform( + Self::load_web_backup(key), + |result| { + match result { + Ok(update) => { + if update { + UpdateData + }else { + RootMessage::None + } + } + Err(err) => PushNotification(NotificationRequest::Warning(err)) + } } - }), + ), ]); } else { final_task = reading_additional_task; @@ -153,7 +158,7 @@ impl ScreenState { DataLoaded(map) } - async fn load_web_backup(string: String) -> Result { + async fn load_web_backup(string: String) -> Result { let local = get_local_version().await; let web = get_web_version(string.as_str()).await?; println!("{} {}", local, web); @@ -198,7 +203,7 @@ impl ScreenState { if let Notification(message) = message { return self.notification_manager.update(message); } - + if let PushNotification(notification) = &message { return self.notification_manager.push(notification.clone()) } diff --git a/src/notification.rs b/src/notification.rs index 14ec5ec..c76644a 100644 --- a/src/notification.rs +++ b/src/notification.rs @@ -154,6 +154,9 @@ impl NotificationManager { NotificationData::PermissionDenied => { text!("У приложение недостаточно прав") } + NotificationData::WebApiUnavailable => { + text!("Не получается подключиться к Api") + } } .width(Fill) .into() @@ -175,4 +178,5 @@ pub enum NotificationData { IncorrectAnkiFile, OpenFileError, VoiceVoxUnavailable, + WebApiUnavailable, } diff --git a/src/sync.rs b/src/sync.rs index 72b3b77..f32952a 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -63,7 +63,7 @@ impl NavigatedPage for SyncState { return Task::perform(first_sync(), |id| { match id { Ok(id) => RootMessage::Sync(IdReceived(id)), - Err(_) => RootMessage::None + Err(err) => RootMessage::PushNotification(NotificationRequest::Error(err)) } }); }