Sync solved

This commit is contained in:
micialware committed 2026-09-06 23:55:50 +03:00
1 parent 22c05250aa
commit 1afb7493f6
9 files changed
+164 -158

No files matched your search

+1 -1
View File
@@ -5,7 +5,7 @@ create table states
word_id INTEGER not null
references words
on delete cascade,
set_id INTEGER not null
deck_id INTEGER not null
references decks
on delete cascade,
score INTEGER default 1 not null,
+9 -10
View File
@@ -1,8 +1,11 @@
use std::sync::Arc;
use crate::data_provider::decks::{DeckRepository};
use crate::data::repositories::Repository;
use crate::data_provider::card_states::CardStateRepository;
use crate::data_provider::decks::DeckRepository;
use crate::data_provider::history::HistoryRepository;
use crate::data_provider::settings::get_setting;
use crate::data_provider::sqlite::{SqliteConnection};
use crate::data_provider::words::{WordRepository};
use crate::data_provider::sqlite::SqliteConnection;
use crate::data_provider::word_groups::WordGroupRepository;
use crate::data_provider::words::WordRepository;
use crate::navigation::RootMessage::Keyboard;
use crate::navigation::{AppSettings, RootMessage, ScreenState};
use iced::{Subscription, keyboard, window};
@@ -13,10 +16,7 @@ use iced_core::{Font, Size, Theme};
use log::info;
use parking_lot::{Mutex, RwLock};
use rusqlite::Connection;
use crate::data::repositories::Repository;
use crate::data_provider::card_states::CardStateRepository;
use crate::data_provider::history::HistoryRepository;
use crate::data_provider::word_groups::WordGroupRepository;
use std::sync::Arc;
pub const USER_FONT: Font = Font::with_name("Noto Sans JP");
@@ -100,10 +100,9 @@ pub fn fill_state(state: &mut AppState) {
let setting = load_settings(&connection);
state.word_groups.write().start().unwrap();
state.dictionary.write().start().unwrap();
state.dictionary.write().start().unwrap();
state.decks.write().start().unwrap();
state.sync_data = Arc::new(RwLock::new(setting))
}
+4 -4
View File
@@ -164,9 +164,9 @@ impl DeckData {
self.set.len()
}
pub fn list(deck: &DeckSettings, state: &AppState) -> Result<Vec<EntityHandle<WordData>>, NotificationData> {
pub fn list(deck: &DeckSettings, state: &AppState) -> Vec<EntityHandle<WordData>> {
let time = Instant::now();
let mut list = vec![];
let list = vec![];
let engine = Engine::new();
let ast = engine.compile(&deck.filter);
@@ -201,9 +201,9 @@ impl DeckData {
info!("Collecting available words is {:?}", time.elapsed());
let handles =query.handles().collect();
drop(query);
Ok(handles)
handles
} else {
Ok(list)
list
}
}
}
+6 -1
View File
@@ -126,7 +126,7 @@ impl DeckSettings {
pub struct CardState {
pub id: Id,
pub word_id: Id,
pub set_id: Id,
pub deck_id: Id,
pub last_open: DateTime<Utc>,
pub score: u8,
pub fsrs_opened: NaiveDate,
@@ -200,6 +200,11 @@ impl<T> EntityHandle<T> {
_phantom: Default::default(),
}
}
pub fn from_id(id: Id) -> Self {
Self::new(id, id.0 as usize, false)
}
pub fn index(&self) -> usize {
(self.index << 1 >> 1) as usize
}
+6 -6
View File
@@ -15,7 +15,7 @@ pub type CardStateRepositoryInner = DataRepositoryInner<CardState, SqliteDbLayer
pub struct CardStatesQuerySet;
impl TableAccessQuerySet<CardState> for CardStatesQuerySet {
fn load() -> &'static str {
"SELECT id, word_id, set_id, score, last_opened, fsrs_opened, fsrs_memory, fsrs_open_day FROM states"
"SELECT id, word_id, deck_id, score, last_opened, fsrs_opened, fsrs_memory, fsrs_open_day FROM states"
}
fn save() -> &'static str {
@@ -23,7 +23,7 @@ impl TableAccessQuerySet<CardState> for CardStatesQuerySet {
}
fn add() -> &'static str {
"INSERT INTO states (word_id, set_id, score, last_opened, fsrs_opened, fsrs_memory, fsrs_open_day) VALUES (?1, ?2, ?3, ?4)"
"INSERT INTO states (word_id, deck_id, score, last_opened, fsrs_opened, fsrs_memory, fsrs_open_day) VALUES (?1, ?2, ?3, ?4)"
}
fn name() -> &'static str {
@@ -34,7 +34,7 @@ impl TableAccessQuerySet<CardState> for CardStatesQuerySet {
Ok(CardState {
id: row.get(0)?,
word_id: row.get(1)?,
set_id: row.get(2)?,
deck_id: row.get(2)?,
score: row.get(3)?,
fsrs_opened: NaiveDate::from_epoch_days(row.get::<usize, i32>(5)?)
.expect("Incorrect date encoding"),
@@ -48,7 +48,7 @@ impl TableAccessQuerySet<CardState> for CardStatesQuerySet {
fn insert_params(entity: &CardState) -> impl Params {
(
entity.word_id,
entity.set_id,
entity.deck_id,
entity.score,
entity.last_open.timestamp(),
entity.fsrs_opened.to_epoch_days(),
@@ -73,7 +73,7 @@ pub fn load_stats_of_deck(
Ok(CardState {
id: row.get(0)?,
word_id: row.get(1)?,
set_id: set.id,
deck_id: set.id,
score: row.get(2)?,
fsrs_opened: NaiveDate::from_epoch_days(row.get::<usize, i32>(4)?)
.expect("Incorrect date encoding"),
@@ -126,7 +126,7 @@ pub fn add_stat_list(stats: &mut [CardState], connection: &mut Connection) -> Ma
for stat in stats.iter() {
stmt.execute(params![
stat.word_id,
stat.set_id,
stat.deck_id,
stat.score,
stat.last_open.timestamp(),
stat.fsrs_opened.to_epoch_days(),
+3
View File
@@ -110,4 +110,7 @@ impl SqliteConnection {
pub fn finish(&mut self) {
self.0 = None;
}
pub fn restart(&mut self) {
self.0 = Some(default_connection());
}
}
+6 -8
View File
@@ -86,12 +86,10 @@ impl NavigatedPage<RepetitionSettingsMessage> for RepetitionSettingsState {
self.correct_filter = self.settings_data.check_filter();
self.filter_changed = true;
}
TryFilter => match DeckData::list(&self.settings_data, &self.state) {
Ok(list) => {
self.filtered_words = Some(list.len() as u32);
}
Err(err) => return push_error_task!(err),
},
TryFilter => {
let list = DeckData::list(&self.settings_data, &self.state);
self.filtered_words = Some(list.len() as u32);
}
DeleteSet => {
if !self.real_delete {
self.real_delete = true;
@@ -183,10 +181,10 @@ fn update_deck_stats(deck: &DeckSettings, state: &AppState) -> MaybeError {
let states_repo = &mut state.card_states.write();
let deck_id = deck.id;
let mut db_stats = states_repo
.query_par(Arc::new(move |s| s.set_id == deck_id))
.query_par(Arc::new(move |s| s.deck_id == deck_id))
.iter(&states_repo)
.collect::<Vec<_>>();
let filtered_words = DeckData::list(deck, state)?
let filtered_words = DeckData::list(deck, state)
.iter()
.map(|w| w.id)
.collect::<Vec<_>>();
+82 -95
View File
@@ -1,8 +1,9 @@
use crate::app::AppState;
use crate::core::deck::DeckData;
use crate::core::srs::{AppendMode, OrderMode};
use crate::data::Id;
use crate::data::entities::{CardState, DeckSettings};
use crate::data_provider::card_states::{add_stat_list, load_stats_of_deck};
use crate::data::entities::{CardState, DeckSettings, EntityHandle, WordData};
use crate::data::repositories::Repository;
use crate::data_provider::card_states::add_stat_list;
use crate::navigation::Page::{History, PreviousPage, Repetition, RepetitionSettings};
use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::notification::{MaybeError, NotificationRequest};
@@ -14,7 +15,7 @@ use crate::push_error;
use crate::push_error_task;
use crate::styling::*;
use chrono::{Days, Local, Utc};
use hashbrown::{HashMap, HashSet};
use hashbrown::HashSet;
pub use iced::widget::button::{Catalog, Style};
use iced::widget::button::{Status, danger};
use iced::widget::tooltip::Position::Top;
@@ -26,14 +27,12 @@ use iced::{Background, Border, Center, Color, Element, Fill, Length, Shadow, Tas
use iced_core::Padding;
use iced_core::border::Radius;
use iced_core::svg::Handle;
use parking_lot::Mutex;
use std::ops::Deref;
use std::sync::Arc;
#[derive(Clone)]
pub struct RepetitionsState {
word_id_index_map: HashMap<Id, usize>,
selected_deck_index: Option<usize>,
selected_deck_handle: Option<EntityHandle<DeckSettings>>,
decks: Vec<DeckViewData>,
state: AppState,
}
@@ -48,11 +47,10 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
self.state.clone(),
)))
} else if let GoToRepetition = message {
let clone = self.state.clone();
let mut state_clone = self.state.clone();
let selected_set = self.selected_deck_mut().unwrap();
if selected_set.append_mode == AppendMode::Full {
if let Err(_err) = Self::append_all_words(&mut clone.lock(), selected_set) {
if let Err(_err) = Self::append_all_words(&mut state_clone, selected_set) {
return None;
}
selected_set.existing_words_indices = selected_set.available_words_indices.clone();
@@ -65,7 +63,7 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
}
if let Ok(repetition_state) =
RepetitionState::new(selected_set.general_settings.clone(), clone)
RepetitionState::new(selected_set.general_settings.clone(), self.state.clone())
{
return Some(Repetition(repetition_state));
}
@@ -73,7 +71,7 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
None
} else if let GoToSettings = message {
Some(RepetitionSettings(RepetitionSettingsState::new(
self.selected_deck().unwrap().index.unwrap(),
EntityHandle::from_id(self.selected_deck().unwrap().id),
self.state.clone(),
)))
} else {
@@ -81,23 +79,20 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
}
}
fn navigated(&mut self) {
if let Some(index) = self.selected_deck_index {
let state = self.state.lock();
if let Some(global_deck) = state.decks.get(index) {
if global_deck.id != self.selected_deck().unwrap().id {
drop(state);
self.decks.remove(index);
self.clear_selection();
} else {
self.decks[index].general_settings = state.decks[index].clone();
drop(state);
let _ = self.fill_deck_word_indices();
}
} else {
drop(state);
self.decks.remove(index);
self.clear_selection()
if let Some(index) = self.selected_deck_handle {
let decks = self.state.decks.read();
if let None = decks.get_static(index) {
let position = self
.decks
.iter()
.position(|deck_view_data: &DeckViewData| deck_view_data.id == index.id)
.unwrap();
self.decks.remove(position);
drop(decks);
self.clear_selection();
}
} else {
let _ = self.fill_deck_word_indices();
}
}
fn update(&mut self, message: RepetitionsMessage) -> Task<RootMessage> {
@@ -116,25 +111,27 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
available_words_indices: Default::default(),
indices_ready: false,
append_mode: Default::default(),
index: None,
count: None,
open_mode: OrderMode::Default,
});
}
DeleteSet => {
let index = self.selected_deck_index.unwrap();
let index = self.selected_deck_handle.unwrap();
self.clear_selection();
let deck = self.decks.remove(index);
if deck.id.is_valid() {
let mut state = self.state.lock();
state.decks.remove(index);
self.decks = self
.decks
.clone()
.into_iter()
.map(|mut deck| {
deck.index = state.decks.iter().position(|d| d.id == deck.id);
deck
})
.collect();
let deck = self.decks.remove(
self.decks
.iter()
.position(|dvd| dvd.id == index.id)
.unwrap(),
);
if index.real() {
let mut repo = self.state.decks.write();
if let Err(err) = repo.delete(index) {
return push_error_task!(err);
}
let index = self.decks.iter().position(|dvd| dvd.id == index.id);
self.decks.remove(index.unwrap());
}
}
SetName(value) => {
@@ -152,20 +149,19 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
}
Save => {
let mut deck = self.selected_deck().unwrap().clone();
let mut state = self.state.lock();
{
if let Err(e) = update_deck(&mut deck.general_settings, &state.connection) {
return Task::done(RootMessage::PushNotification(
NotificationRequest::Error(e),
));
}
if let Some(index) = deck.index {
state.decks[index] = deck.general_settings.clone();
} else {
state.decks.push(deck.general_settings.clone());
deck.index = Some(state.decks.len() - 1);
self.decks[self.selected_deck_index.unwrap()] = deck.clone();
}
let handle = self.selected_deck_handle.unwrap();
let mut repo = self.state.decks.write();
repo.set_static(handle, *deck);
if let Err(err) = repo.save(&mut handle.clone()) {
return push_error_task!(err);
}
if let Some(index) = deck.index {
state.decks[index] = deck.general_settings.clone();
} else {
state.decks.push(deck.general_settings.clone());
deck.index = Some(state.decks.len() - 1);
self.decks[self.selected_deck_handle.unwrap()] = deck.clone();
}
}
SetForward(value) => {
@@ -181,23 +177,21 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
deck.general_settings.filter = value;
}
TryFilter => {
let state = self.state.lock();
let deck = self.selected_deck().unwrap();
let count = deck.get_word_list(&state).len();
drop(state);
let count = DeckData::list(&deck, &self.state).len();
let deck = self.selected_deck_mut().unwrap();
deck.general_settings.count = Some(count);
deck.count = Some(count);
}
SetOpenMode(mode) => {
let deck = self.selected_deck_mut().unwrap();
deck.general_settings.open_mode = mode;
deck.open_mode = mode;
}
SetAppendMode(mode) => {
let deck = self.selected_deck_mut().unwrap();
deck.append_mode = mode;
}
AppendWords(count) => {
let adding: Vec<usize>;
let adding: Vec<EntityHandle<WordData>>;
{
{
let deck = self.selected_deck().unwrap();
@@ -257,22 +251,9 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
impl RepetitionsState {
pub(crate) fn new(state: AppState) -> RepetitionsState {
let state_ = state.lock();
let mut map = HashMap::with_capacity(state_.dictionary.len());
state_
.dictionary
.iter()
.enumerate()
.for_each(|(index, word)| {
map.insert(word.id, index);
});
let decks = Self::get_decks_from_state(&state_);
drop(state_);
let decks = Self::get_decks_from_state(&state);
RepetitionsState {
word_id_index_map: map,
selected_deck_index: None,
selected_deck_handle: None,
decks,
state,
}
@@ -281,6 +262,7 @@ impl RepetitionsState {
fn get_decks_from_state(state: &AppState) -> Vec<DeckViewData> {
state
.decks
.read()
.iter()
.enumerate()
.map(|(index, deck)| DeckViewData {
@@ -289,7 +271,9 @@ impl RepetitionsState {
available_words_indices: Default::default(),
indices_ready: false,
append_mode: Default::default(),
count: None,
index: Some(index),
open_mode: OrderMode::Default,
})
.collect()
}
@@ -526,7 +510,7 @@ impl RepetitionsState {
text!("{} слова добавлено из {}", now, available).into()
}
fn count_view(&self, deck: &DeckSettings) -> Element<'_, RepetitionsMessage> {
if let Some(count) = deck.count {
if let Some(count) = self.decks.iter().find() {
return text!("Количество слов: {}", count).into();
}
space().into()
@@ -578,7 +562,7 @@ impl RepetitionsState {
fn append_words(
state: &mut AppState,
deck: &DeckSettings,
indices: impl Iterator<Item = usize>,
indices: impl Iterator<Item = EntityHandle<WordData>>,
) -> MaybeError {
let words = &state.dictionary;
let stats = &mut indices
@@ -591,7 +575,7 @@ impl RepetitionsState {
score: 1,
fsrs_opened: Utc::now().date_naive(),
fsrs_memory: None,
set_id: deck.id,
deck_id: deck.id,
fsrs_open_day: Utc::now().date_naive(),
}
})
@@ -605,32 +589,34 @@ impl RepetitionsState {
fn select_deck(&mut self, index: usize) {
if self.decks.get(index).is_some() {
self.selected_deck_index = Some(index);
self.selected_deck_handle = Some(index);
}
}
fn selected_deck(&self) -> Option<&DeckViewData> {
self.decks.get(self.selected_deck_index?)
self.decks.get(self.selected_deck_handle?)
}
fn selected_deck_mut(&mut self) -> Option<&mut DeckViewData> {
self.decks.get_mut(self.selected_deck_index?)
self.decks.get_mut(self.selected_deck_handle?)
}
fn clear_selection(&mut self) {
self.selected_deck_index = None;
self.selected_deck_handle = None;
}
fn fill_deck_word_indices(&mut self) -> MaybeError {
let state = &self.state.lock();
let card_states = self.state.card_states.read();
let deck_id = self.selected_deck().unwrap().id;
let added = card_states
.query_par(Arc::new(move |state| state.deck_id == deck_id))
.iter(&card_states)
.map(|state| EntityHandle::from_id(state.word_id))
.collect::<Vec<_>>();
let mut deck = self.selected_deck().unwrap().clone();
let added: Vec<_> = load_stats_of_deck(&deck, &state.connection)?
.iter()
.map(|c| self.word_id_index_map[&c.word_id])
.collect();
let total = deck.get_word_list(state);
let total = DeckData::list(&deck, &self.state)?;
deck.existing_words_indices = added;
deck.available_words_indices = total;
self.decks[self.selected_deck_index.unwrap()] = deck;
self.decks[self.selected_deck_handle.unwrap()] = deck;
Ok(())
}
}
@@ -658,11 +644,12 @@ pub enum RepetitionsMessage {
#[derive(Clone)]
struct DeckViewData {
general_settings: DeckSettings,
existing_words_indices: Vec<usize>,
available_words_indices: Vec<usize>,
existing_words_indices: Vec<EntityHandle<WordData>>,
available_words_indices: Vec<EntityHandle<WordData>>,
indices_ready: bool,
append_mode: AppendMode,
index: Option<usize>,
count: Option<usize>,
open_mode: OrderMode,
}
impl Deref for DeckViewData {
+47 -33
View File
@@ -1,4 +1,5 @@
use crate::app::{AppState, fill_state};
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::navigation::Page::PreviousPage;
@@ -12,8 +13,6 @@ 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 parking_lot::Mutex;
use std::sync::Arc;
use std::time::Duration;
#[derive(Clone)]
@@ -56,8 +55,7 @@ impl NavigatedPage<SyncMessage> for SyncState {
match message {
Back => {}
CopyKey => {
let state = self.state.lock();
let key = state.sync_data.key.clone().unwrap();
let key = self.state.sync_data.read().key.clone().unwrap();
return iced::clipboard::write(key)
.map(|_val: String| RootMessage::Sync(KeyCopied));
}
@@ -76,16 +74,20 @@ impl NavigatedPage<SyncMessage> for SyncState {
if !validate_id(&new_id) {
return Task::none();
}
let mut state = self.state.lock();
state.sync_data.key = Some(new_id.clone());
if let Err(err) = set_setting("SYNC_KEY".to_string(), new_id, &state.connection) {
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.lock().sync_data.key.clone().unwrap();
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 },
@@ -97,7 +99,7 @@ impl NavigatedPage<SyncMessage> for SyncState {
return tasks;
}
GetLast => {
let id = self.state.lock().sync_data.key.clone().unwrap();
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 },
@@ -122,26 +124,27 @@ impl NavigatedPage<SyncMessage> for SyncState {
}
NetworkFinished => {
self.frozen = false;
let mut updated_state = AppState::new();
fill_state(&mut updated_state);
{
let mut connection = self.state.connection.lock();
connection.restart();
}
if let Err(err) = self.reload_data() {
return push_error_task!(err);
}
let mut state = self.state.lock();
state.sync_data = updated_state.sync_data;
state.connection = updated_state.connection;
state.decks = updated_state.decks;
state.dictionary = updated_state.dictionary;
state.word_groups = updated_state.word_groups;
return Task::done(RootMessage::PushNotification(NotificationRequest::Info(
NotificationData::DataLoaded,
)));
}
Disable => {}
DisableSync => {
let mut state = self.state.lock();
state.sync_data.key = None;
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(), &state.connection)?;
delete_settings("AUTO_WEB_FETCH".to_string(), &state.connection)?;
delete_settings("SYNC_KEY".to_string(), &connection)?;
delete_settings("AUTO_WEB_FETCH".to_string(), &connection)?;
Ok::<(), NotificationData>(())
};
@@ -151,12 +154,14 @@ impl NavigatedPage<SyncMessage> for SyncState {
}
SwitchAutoSync => {
self.auto_fetch = !self.auto_fetch;
let mut state = self.state.lock();
state.sync_data.auto_web_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(),
&state.connection,
&connection,
) {
return push_error_task!(err);
}
@@ -178,10 +183,8 @@ impl NavigatedPage<SyncMessage> for SyncState {
impl SyncState {
pub fn new(state: AppState) -> SyncState {
let auto_fetch;
{
auto_fetch = state.lock().sync_data.auto_web_fetch;
}
let auto_fetch = state.sync_data.read().auto_web_fetch;
Self {
auto_fetch,
state,
@@ -190,6 +193,15 @@ impl SyncState {
}
}
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()?;
Ok(())
}
fn sync_column(&self) -> Element<'_, SyncMessage> {
let network_view: Element<'_, SyncMessage> = if self.frozen {
progress_bar(0.0..=5.0, self.progress).into()
@@ -197,7 +209,7 @@ impl SyncState {
space().into()
};
{
if let Some(key) = self.state.lock().sync_data.key.clone() {
if let Some(key) = self.state.sync_data.read().key.as_ref() {
column![
text!("Ваш ключ синхронизации"),
container(
@@ -254,13 +266,15 @@ impl SyncState {
}
self.frozen = true;
self.progress = 0.0;
let state = self.state.lock();
state
.connection
let mut connection = self.state.connection.lock();
connection
.query_row("PRAGMA wal_checkpoint(TRUNCATE)", [], |_| Ok(()))
.unwrap_or_else(|e| {
error!("Ошибка при checkpoint WAL: {e}");
});
connection.finish();
}
}